把 C++ 学习笔记写成可以运行的东西
我以前写 C++ 笔记的方式是:看一段教程,抄一段代码,在旁边写“这里用了引用”。过两周回来,代码编译不过,我也说不清是环境变了,还是当时就抄错了。
后来我把标准改了一点:每个出现在笔记里的例子,都要能单独编译;每个输出,都要是实际运行得到的。 这听起来只是多跑一次编译器,实际上会改变写笔记的方式。
例子要能独立存在
最开始的例子常写成这样:
// 省略头文件和 main
std::vector<int> values{3, 1, 4};
std::sort(values.begin(), values.end());读者需要自己补 #include、命名空间和 main,还要猜 values 最后是什么。这些“显然”的部分,恰恰是初学者最容易卡住的地方。
现在我会把例子写成完整文件:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{3, 1, 4};
std::sort(values.begin(), values.end());
for (int value : values) {
std::cout << value << ' ';
}
std::cout << '\n';
}再附上实际输出:
1 3 4
多出来的几行不是噪音,而是把“你可以直接运行”这件事变成真的。
给每个例子一个位置
如果文章里只有一个例子,贴代码块就够了。但当一章有十几个例子时,散在正文里的代码很快会失去上下文。我的做法是给每篇笔记一个独立目录:
notes/
└── 03-references/
├── index.qmd
├── 01-basic.cpp
├── 02-const-ref.cpp
└── expected.txt
正文通过代码块引用文件,构建前用一个脚本把源码和输出同步进去。这样例子可以被单独编译,笔记也不会因为复制粘贴而和源码走散。
脚本本身不需要复杂:
for file in notes/*/*.cpp; do
g++ -std=c++20 -Wall -Wextra -pedantic "$file" -o /tmp/example
/tmp/example > "${file%.cpp}.out"
done真正重要的是把它放进发布流程。只要有一次提交没有通过编译,构建就会失败,而不是等到读者发现。
解释“为什么”,而不是复述语法
能运行的例子解决了“对不对”,但没有解决“为什么”。我尽量避免这种句子:
const表示常量。
它没有错,但读者看完仍然不知道什么时候该用。更有用的写法是先给出一个会出问题的场景,再说明约束:
如果函数参数写成
std::vector<int> values,每次调用都会复制整个数组。数据量大时,这个复制会出现在性能分析里,而不是语法错误里。改成const std::vector<int>&可以避免复制,同时承诺函数不会修改它。
前者是定义,后者是判断。笔记的价值通常在后半部分。
一个可以照着做的检查清单
写完一篇 C++ 笔记,我会过一遍:
- 每个代码块都有完整的
#include和main,或者明确说明它只是片段; - 代码用
-Wall -Wextra -pedantic编译过; - 输出是实际运行结果,不是手写的“应该输出”;
- 至少有一个地方解释“不这样写会怎样”;
- 文中的文件名、命令和目录结构,与示例仓库一致;
- 读者不需要先读上一篇,也能运行当前例子。
这几条不保证文章写得好,但能保证它不会在读者第一次尝试时就失败。对学习笔记来说,这已经是一个不错的起点。
这些例子会陆续放进 cpp-notes。如果你发现某个例子在你的编译器上跑不通,欢迎写信告诉我编译器和版本。