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。

image-20260122221529858

几乎是必须安装:C/C++ Compile Run插件。您也可以选择Code Runner之类的插件,功能基本相同,都是让您可以方便地运行C++的插件。

image-20260122221601347

[!WARNING]

这个插件会覆盖调试快捷键F5,可能使您的断点不生效。以下是解决方法:按下快捷键Ctrl+Shift+P,键入shortcut,找到Preferences: Open Keyboard Shortcuts,键入F5(注意不是按F5键),在搜索结果中,凡是右边Source项为C/C++ Compile Run这个插件的一律删除(右键单击后,左键单击Remove Keybinding)。

image-20260122221832220

其他推荐的插件: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。此时应当会启动终端并运行您的程序。如果提示

image-20260122223554487

点击Change compiler path并输入您编译器可执行文件的位置(例如D:\msys64\mingw64\bin\g++.exe),然后再次尝试F6。此时底部应当出现这样的小窗口:

image-20260123004640051

您可以任意输入一行字符然后按下回车。此时程序应当将您的输入原封不动地打印一遍。

如果以上都没有问题,我们就可以进入下一步了。

在第7行,也就是std::cout << s << std::endl;这一行,左端行号左侧,鼠标悬停在上面会出现暗红色实心圆地地方点击一下,这样就添加一个断点。

image-20260123004725817

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

image-20260123005633823

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

image-20260123005800541

点击那个悬浮的几个图标中一个竖加一个三角形的那个,或按下F5,程序将继续执行直至结束。

如果以上步骤没有问题,我们的配置就彻底完成了。

Vim/Emacs

自己配置去罢。

记事本

狠人。

Sublime Text

集成开发环境(Integrated Development Environment, IDE)

竞赛中一般不使用,因为通常比较笨重,而且要求您创建项目。仅作介绍:

  • Visual Studio
  • CLion
  • Code::Blocks(考场多见)
  • Dev-C++(狗屎,旧时代的垃圾,能不用就不用,如果考场只有这个就提前适应一下)
  • 小熊猫Dev-C++/小熊猫C++(Dev-C++现代升级,不错)

在线编译器

如果您的环境始终没有配好,不必担心,您可以临时使用在线编译器,例如洛谷在线IDECompiler Explorer等。

常用网站

  • 洛谷:中国最大的算法竞赛刷题网站,以OI风格为主。
  • Codeforces:(大约是全球最大最知名的)线上算法竞赛网站,题目丰富,多为思维题,但比赛时间通常比较阴间。
  • AtCoder:日本网站,与Codeforces类似,规模相对较小,但由于中日时区接近,时间一般比较合适。
  • Virtual Judge:不是OJ,但集成各大OJ平台,可以方便组题单和开练习赛。

基本数据类型

C++的基本数据类型与C语言几乎完全相同,仅添加bool类型。于是一共有以下基本类型:boolcharshortintlonglong longfloatdoublelong double。其中,charshortintlonglong long可以用unsignedsigned修饰,用于标明是否有符号(存储符号会占用一个二进制位),默认都是有符号的。

C++和C语言一样,语言本身只规定类型的最小长度。我强烈推荐使用<cstdint>头中的类型别名,例如int32_tuint64_t等,它们以简洁的形式标注了数据的长度、类型、符号,兼具跨平台和简洁的优点。

C++的布尔值和C语言一样,“非零即为真”

类型转换

很多时候会发生自动/隐式类型转换。例如

  • 与范围更大的类型的值运算时,范围较小的类型的变量会自动向上转换。
  • 赋值(包括值传递的参数)时,可以转换的类型会自动转换。

自动类型转换使代码更简洁,但代价是更容易出错。C++高度依赖程序员的自觉性。

C++风格的强制/显式类型转换有static_castdynamic_castconst_castreinterpret_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>中的printfscanf了。这里我们优先介绍一下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设置了缓冲区,也就是说,当你用printfstd::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基础,您应当知道fopenopen函数。重定向函数使用类似的访问权限字符串。其用法为

std::freopen("test.in", "r", stdin);   // 将标准输入流重定向到文件test.in,用只读模式打开
std::freopen("test.out", "w", stdout); // 将标准输出流重定向到文件test.out,用只写模式打开,会清空原有文件内容

以上两句分别重定向stdinstdout,由于cincout分别读写这两个流,因此它们也会被重定向。

在调试时,我们习惯将它们重定向到文件以免手动输入的麻烦。这时为了方便查看中间变量的值,可以用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/scanfprintf慢得多。但这并不是不可改变的。只需一点点设置,即可让两者速度基本相当,甚至有时cin/cout会更胜一筹。

关闭同步

默认情况下,C++ 为了兼容性,将 cin/coutscanf/printf 进行了同步(synchronize),在每一次I/O操作时,C++流会立即将此操作应用于对应的C缓冲区中。如果关闭同步,C++流将仅使用自己的缓冲区,这样I/O效率会显著提高。

std::ios::sync_with_stdio(false);

关闭同步后,绝对不可以同时使用cinscanf,也不能同时使用coutprintf,因为输出顺序会乱掉(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语句

禁器。它可以有很妙的用途,但绝对不要使用!它会破坏程序的顺序结构,容易出错的同时降低可读性,还会使编译器难以优化。除非您在编写操作系统之类的东西,否则绝对避免!

posted @ 2026-01-23 03:08  我就是蓬蒿人  阅读(4)  评论(0)    收藏  举报