2026寒假讲课 Day 1
2026寒假讲课 Day 1
这部分内容讲起来应该比较头痛,我在写的时候就已经感受到了。大家尽量多自己找教程。
环境搭建
如果您有Linux系统,那再好不过了。使用Linux系统的同学自行安装。
但这对绝大多数同学都不现实,下面只考虑Windows系统。
终端环境
在Windows 7及以上版本的Windows系统中,推荐使用Powershell而不是CMD。您可以用Win+R快捷键打开“运行”,输入powershell然后按下回车,弹出的那个黑色蓝色窗口就是Powershell终端;如果您的系统比较落后,例如Windows XP系统(那很复古了),请在输入时用cmd代替powershell,弹出的黑色窗口就是CMD终端。
编译器、调试器
C/C++以其乱七八糟的编译器和五花八门的包管理器而臭名昭著。
MSYS 2(推荐)
关于MSYS 2的详细介绍可以参考《MinGW 和 MSYS2 介绍》。按照官网提供的步骤下载和安装即可,也可以参考教程如《VSCODE-01-MSYS2、g++安装保姆级讲解》。看不懂英文的同学自行向翻译软件或人工智能寻求帮助。有其他问题,优先自行查找教程或向人工智能寻求帮助,实在没有解决的可以找我。按照教程安装好gcc包和gdb包,即编译器和调试器。
WSL 2(很推荐但有门槛)
WSL(Windows Subsystem for Linux)是让您能双飞两种操作系统的一个不错方案,我自己就是用的WSL。但由于其运行Linux,需要您熟悉Linux命令。
它本质上是将Windows系统也变成虚拟机,于是您的计算机便可以同时运行Windows和Linux。安装好后,根据您选择的发行版自行安装g++和gdb即可。您都装Linux了,应当会查教程罢?
VC++
这是微软推出的编译器,在Windows系统下,使用VC++其实才是正统。但由于竞赛环境通常为g++,我们一般不使用。
在Visual Studio官网下载即可。注意不要用最新的VS 2026,据说bug比较多。用2022就行。
MinGW-w64
可以参考教程《最新!MinGW-w64的下载与安装(超详细!!!)》。
无论您选择哪一种方案,务必将您的编译器和调试器所在目录添加到PATH环境变量。以下默认您安装了g++和gdb。
编辑器
VS Code(大力推荐)
下载、安装
在官网下载并安装即可。推荐下载“System Installer”版本。如果您屁事多,想要完全开源版本,请您移步VS Codium。
安装完成后,启动VS Code。
安装插件
您可以选择安装中文插件:在左边栏的图表中找到四个方块的那个,搜索Chinese,点击Install。安装完后,可能会提示需要重启编辑器,重启便是。但我个人推荐适应英文环境。
必须安装:C/C++插件。搜索C++后点击Install。
几乎是必须安装:C/C++ Compile Run插件。您也可以选择Code Runner之类的插件,功能基本相同,都是让您可以方便地运行C++的插件。
[!WARNING]
这个插件会覆盖调试快捷键F5,可能使您的断点不生效。以下是解决方法:按下快捷键Ctrl+Shift+P,键入
shortcut,找到Preferences: Open Keyboard Shortcuts,键入F5(注意不是按F5键),在搜索结果中,凡是右边Source项为C/C++ Compile Run这个插件的一律删除(右键单击后,左键单击Remove Keybinding)。
其他推荐的插件:Code Translate、CodeSnap、Competitive Programming Helper (cph)、One Dark Pro。
配置
在设置(左下角齿轮 - Settings)中,找到Extensions - C/C++ Compile Run,将Cpp-flags设置为
-Wall -Wextra -g3
将Output-location清空。
在您的电脑上合适的位置创建一个文件夹,作为工作目录(workspace),您的代码将存放在此目录及其子目录中。在VS Code左上角File菜单中选择Open Folder,找到这个文件夹并打开。如果不进行这一步,您将无法启动调试。
左侧创建一个C++文件test.cpp。进入后,输入以下内容:
#include <iostream>
#include <string>
int main() {
std::string s;
std::getline(std::cin, s);
std::cout << s << std::endl;
return 0;
}
按下F6。此时应当会启动终端并运行您的程序。如果提示
点击Change compiler path并输入您编译器可执行文件的位置(例如D:\msys64\mingw64\bin\g++.exe),然后再次尝试F6。此时底部应当出现这样的小窗口:

您可以任意输入一行字符然后按下回车。此时程序应当将您的输入原封不动地打印一遍。
如果以上都没有问题,我们就可以进入下一步了。
在第7行,也就是std::cout << s << std::endl;这一行,左端行号左侧,鼠标悬停在上面会出现暗红色实心圆地地方点击一下,这样就添加一个断点。

按下F5,选择C++ (GDB/LLDB),选择C/C++: g++.exe build and debug active file。这时底部应该也会弹出终端(也可能停在Debug Console,手动点一下Terminal即可):

任意键入一行字符,回车。这时程序应当会在断点处暂停。点击左边的虫趴在三角形上的图标(Run and Debug),应当可以看到局部变量s = "...",内容是您刚才输入的那一行。鼠标悬浮在代码中所有s上时(例如第7行)也会显示。

点击那个悬浮的几个图标中一个竖加一个三角形的那个,或按下F5,程序将继续执行直至结束。
如果以上步骤没有问题,我们的配置就彻底完成了。
Vim/Emacs
自己配置去罢。
记事本
狠人。
Sublime Text
集成开发环境(Integrated Development Environment, IDE)
竞赛中一般不使用,因为通常比较笨重,而且要求您创建项目。仅作介绍:
- Visual Studio
- CLion
- Code::Blocks(考场多见)
- Dev-C++(狗屎,旧时代的垃圾,能不用就不用,如果考场只有这个就提前适应一下)
- 小熊猫Dev-C++/小熊猫C++(Dev-C++现代升级,不错)
在线编译器
如果您的环境始终没有配好,不必担心,您可以临时使用在线编译器,例如洛谷在线IDE、Compiler Explorer等。
常用网站
- 洛谷:中国最大的算法竞赛刷题网站,以OI风格为主。
- Codeforces:(大约是全球最大最知名的)线上算法竞赛网站,题目丰富,多为思维题,但比赛时间通常比较阴间。
- AtCoder:日本网站,与Codeforces类似,规模相对较小,但由于中日时区接近,时间一般比较合适。
- Virtual Judge:不是OJ,但集成各大OJ平台,可以方便组题单和开练习赛。
基本数据类型
C++的基本数据类型与C语言几乎完全相同,仅添加bool类型。于是一共有以下基本类型:bool、char、short、int、long、long long、float、double、long double。其中,char、short、int、long、long long可以用unsigned或signed修饰,用于标明是否有符号(存储符号会占用一个二进制位),默认都是有符号的。
C++和C语言一样,语言本身只规定类型的最小长度。我强烈推荐使用<cstdint>头中的类型别名,例如int32_t、uint64_t等,它们以简洁的形式标注了数据的长度、类型、符号,兼具跨平台和简洁的优点。
C++的布尔值和C语言一样,“非零即为真”。
类型转换
很多时候会发生自动/隐式类型转换。例如
- 与范围更大的类型的值运算时,范围较小的类型的变量会自动向上转换。
- 赋值(包括值传递的参数)时,可以转换的类型会自动转换。
自动类型转换使代码更简洁,但代价是更容易出错。C++高度依赖程序员的自觉性。
C++风格的强制/显式类型转换有static_cast、dynamic_cast、const_cast、reinterpret_cast四种,其中第一个最常用。其写法为
int a = 114514, b = 1919810;
double f = static_cast<double>(a) / b; // b会自动转换为double
这个词还是比较噜苏的。竞赛中可以选择#define SC static_cast或者使用C风格(工程上不推荐):
f = (double)a / b;
数据范围
三年OI一场空,不开
long long见祖宗。
注意根据数据范围选择合适的数据类型,以免发生溢出(overflow)。有符号整数的溢出是未定义的行为(undefined behavior, UB)。UB也是C/C++臭名昭著的一点,运气不好时甚至可以让你的代码直接运行时出错但调试时却正确,或者本地正确但评测机报错,等等。
使用Linux的同学可以尝试在编译时加上-fsanitize=address,undefined,这样你的程序如果在运行时发生内存错误或未定义的行为就会报错。这条编译选项有可能有副作用,请您自己折腾。
浮点误差
浮点数运算是有误差的。如果要进行浮点数的比较,必须设定精度\(\varepsilon\)。
std::cout << (0.1 + 0.2 == 0.3) << std::endl // false
<< (std::abs(0.3 - (0.1 + 0.2)) < 1e-9) << std::endl // true
<< std::setprecision(17) << (0.1 + 0.2) << std::endl; // 0.30000000000000004
基本I/O
如果您学过C语言,您一定已经非常熟悉<stdio.h>中的printf和scanf了。这里我们优先介绍一下C++的输入输出。
<iostream>头的基本使用
关于
std::"There are only two hard things in Computer Science: cache invalidation and naming things." --- Phil Karlton
这是C++引入的一个非常好的概念,命名空间(namespace),旨在减少想不出名字的问题。
例如,C语言标准库
<time.h>有一个函数叫time。如果我想要在引入这个头文件的地方自定义一个全局变量/类型/函数叫time就会爆炸——编译器会告诉你time被重新声明了,这是不允许的;如果是局部变量,虽然不会有语法错误,但那个time函数也却用不了了。命名空间很好的解决了这个问题。在C++中,你可以这样:
#include <ctime> int main() { int time = 0; std::time(nullptr); // 标准库的time函数 return 0; }不过,到处
std::可能会稍显噜苏。C++也提供了两种选择:using std::time; using namespace std;第一种是告诉编译器,当前语境中,我所说的
time全都是std::time;第二种是告诉编译器,当前语境中,所有没见过的东西都尝试加个std::前缀,再看看是不是有定义的。这种做法虽然可以简化代码,但是副作用也很明显。特别是由于C++预处理器直接复制粘贴的特性,绝对不要在头文件中这么写。工程上通常要求仅在局部(如函数体中)允许这种
using或完全禁止;竞赛中你想怎样就怎样罢,没人管,怎么快怎么来,自己分得清就行。
输入
int integer;
char character;
double floating_point;
std::string string;
std::cin >> integer >> character >> floating_point >> string;
std::cin读作“C in”,其中“C”代指console(控制台),它是标准头<iostream>中的一个对象(object)。它重载(override)的>>运算符会从标准输入流(stdin)(通常是您的终端,但也可以重定向到文件等)读入,并将值存储到变量中。它可以在编译时自动识别出您给的变量的类型(不是用户输入的类型),从而无需您像scanf那样设置格式字符串。它可以像上面那样“流式”输入,因此中文名实际上叫“控制台输入流”,它的类型是std::istream(输入流),这个头文件叫做iostream(输入输出流)。
C语言中,我们根据scanf的返回值判断是否正常读入;C++中,std::cin重载了转换为bool类型的运算符,因此可以通过if (std::cin >> x) {}来判断读入是否合法。
输出
类似地,这个头中还有一个std::ostream(输出流)类型的对象std::cout,称为“控制台输出流”,读作“C out”。它读取变量的值,并输出到标准输出流(stdout)(通常是您的终端,但也可以重定向到文件等)。其用法也是类似的,同样也能在编译期间识别变量类型,同样可以流式输出,只不过符号的方向变为了<<。
int integer = 114514;
char character = 'a';
double floating_point = 3.14159;
std::string string = "Hello World";
std::cout << integer << ' ' << character << '\n'
<< floating_point << '\n'
<< string << std::endl;
注意,我们上面使用了两种换行的方式:一种是'\n'也就是换行符,另一种是std::endl。前者与输出一个普通字符没有区别,后者等价于
std::cout << '\n'; // 输出换行符
std::cout.flush(); // 刷新缓冲区
两行。
I/O操作相对于内存操作来说是很费时的。为了节省时间,C/C++都为I/O设置了缓冲区,也就是说,当你用printf或std::cout输出时,程序不会直接将内容输出到控制台,而是先保存在内存中一定区域(称为缓冲区(buffer)),等到缓冲区满了或代码显式要求的时候才将其输出到控制台,并清空缓冲区,这个操作称为“刷新(flush)缓冲区”。
因此,频繁std::cout << std::endl会导致程序变慢;而如果只用std::cout << '\n'的话,对于控制台程序,用户可能无法立刻看到程序输出的内容。在算法竞赛中,绝大多数情况下(除了交互题),评测机都是等我们的程序执行完毕后才读取输出,因此我们可以总是使用'\n'。
<cstdio>头的基本使用
虽然<iostream>提供了方便的类型识别,但它在设计上具有一定缺陷,因此有时C风格的格式化输入输出更好用。例如,输出固定位数的浮点数,需要这样:
std::cout << std::setprecision(10) << std::fixed << 3.14159265358979;
如果要频繁切换输出的位数,std::cout将很不方便。如果要固定输出长度,不足的用空格填充的话,就更麻烦了。在这些情况下,printf展现出巨大优势。
C++23新增了被寄予巨大希望的std::print,它将给出现代的格式化输出方案,但很多竞赛目前仍在C++14甚至11。
重定向
算法竞赛中,文件流重定向也是很常用的。如果您有C或Python基础,您应当知道fopen或open函数。重定向函数使用类似的访问权限字符串。其用法为
std::freopen("test.in", "r", stdin); // 将标准输入流重定向到文件test.in,用只读模式打开
std::freopen("test.out", "w", stdout); // 将标准输出流重定向到文件test.out,用只写模式打开,会清空原有文件内容
以上两句分别重定向stdin和stdout,由于cin和cout分别读写这两个流,因此它们也会被重定向。
在调试时,我们习惯将它们重定向到文件以免手动输入的麻烦。这时为了方便查看中间变量的值,可以用stderr(标准错误流)打印。由于该流并未被重定向,它的输出将直接到达控制台。
int x = 3;
std::fprintf(stderr, "fprintf %d\n", x);
std::cerr << "cerr " << x << '\n';
stderr设计初心是立刻展示错误信息,因此它默认情况下没有缓冲区,它的'\n'与std::endl没有区别。
交题时记得将这两行注释掉!否则您将爽吃罚时(ACM赛制)或爆零(NOI赛制)。
<iostream>加速
默认情况下,cin/cout/scanf与printf慢得多。但这并不是不可改变的。只需一点点设置,即可让两者速度基本相当,甚至有时cin/cout会更胜一筹。
关闭同步
默认情况下,C++ 为了兼容性,将 cin/cout 与 scanf/printf 进行了同步(synchronize),在每一次I/O操作时,C++流会立即将此操作应用于对应的C缓冲区中。如果关闭同步,C++流将仅使用自己的缓冲区,这样I/O效率会显著提高。
std::ios::sync_with_stdio(false);
关闭同步后,绝对不可以同时使用cin和scanf,也不能同时使用cout和printf,因为输出顺序会乱掉(C和C++的流刷新缓冲区的时间不同步)。
解绑
在默认的情况下std::cin关联的是&std::cout,因此每次进行格式化输入的时候都要调用std::cout.flush()清空输出缓冲区,这样会增加I/O负担。无需交互时,我们可以解除关联,进一步加快执行效率。
std::cin.tie(nullptr);
解绑后,执行cin时不再保证其前面的cout都从缓冲区输出,但对于除交互题以外的题目都没有副作用。
二者是可以合并起来写的:
std::cin.tie(nullptr)->sync_with_stdio(false);
在极端情况下,我们会在<cstdio>基础上自己写快读/快写函数,以追求极致效率,但这通常没有必要(除非您在试图卡常)。这里我们不做介绍。感兴趣的同学可以移步OI-wiki。
流程控制
分支
if else语句
if (condition1) {
// do sth
} else if (condition2) {
// do sth else
} else {
// do another thing
}
switch case语句
用得相对比较少,功能也比较弱。
int x;
std::cin >> x;
switch (x) {
case 0:
std::cout << "0\n";
case 1:
std::cout << "0 or 1\n";
break;
case 2:
std::cout << "2\n";
break;
default:
std::cout << "else\n";
}
while语句
while (condition) {
// do sth
}
while语句会在每次循环开始前检查条件是否成立。
do while语句
do {
// do sth
} while (condition);
do while语句会先在每次循环结束后再检查条件是否成立。
for语句
for (int i = 0; i < 10; ++i) {
// do sth
}
不同于蟒蛇,C/C++/Java的for语句要求很宽松。里面可以放入任何形式的三个statement(只要中间那条能转换成bool)。例如,以下代码是完全符合语法的:
int x = 1;
for (x = std::cin.get(); std::cin >> x; std::cout << x << std::endl);
另外,C++11引入了基于范围的for语句(range based for loop),形如
for (auto element : container) {
// do sth
}
这个等我们以后讲了容器再说罢。
goto语句
禁器。它可以有很妙的用途,但绝对不要使用!它会破坏程序的顺序结构,容易出错的同时降低可读性,还会使编译器难以优化。除非您在编写操作系统之类的东西,否则绝对避免!


浙公网安备 33010602011771号