C++ 注释
注释是写给人和工具看的说明,编译器在预处理阶段就会把它整段丢掉——理解「什么时候丢、丢成什么」,你才能踩准它的每一条边界。
学完你能做什么
- 能写出单行
//和多行/* */注释,并准确说出它们各自在哪一行结束。 - 能解释「注释被替换成一个空格」这条规则,并预测
int/**/x = 1;能否通过编译。 - 能说清为什么
/* */不能嵌套,而两种注释互相包含却没问题。 - 能识别「行末反斜杠吞掉下一行」这个经典陷阱,并知道用
#if 0屏蔽大段代码。
先建立直觉:注释是便利贴,不是代码
想象你在给同事留一张便利贴,贴在电路板上。贴上写着「这里电压是 5V」。焊接机器人只看铜箔走线,便利贴上的字它一个字都不读,也不影响电路工作。
注释就是这张便利贴:写给人看,机器忽略。所以判断注释好坏的标准只有一个——读者看完是否更省力,而不是「写得多」。
从最简单的用法开始
单行注释以 // 开始,一直到本行结尾。它后面的内容全是说明文字。
▶ 在线运行:改一改再点“运行”试试(本站沙箱执行,登录后可运行)。
预期输出:
36多行注释以 /* 开始,以 */ 结束,中间可以换行:
/* 计算总价:
单价乘以数量,
这段文字不会参与运算 */
| 顺序 | 这一步做什么 | 与注释的关系 |
|---|---|---|
| ① 续行合并 | 先把「行末反斜杠 + 换行」拼成同一行 | 发生在去注释之前,所以 // 说明 \ 会把下一行并进注释 |
| ② 去注释 | 把每段注释替换成一个空格 | int/**/x = 1; 变成 int x = 1;;注释里的 "、/* 都只是普通文字 |
| ③ 词法 / 语法分析 | 切分成 token 并解析语法,随后才展开宏 | 这时已无注释,且宏展开晚于第 ② 步,#define SLASH / 拼不出注释 |
口诀:先接续行,再变空格,最后才分词。
再往下看:编译器什么时候把注释丢掉
C++ 标准把「从源码到程序」拆成若干翻译阶段,和注释相关的顺序是这样的:
- 先把「行末反斜杠 + 换行」拼成同一行(续行合并)。
- 再把每段注释替换成一个空格。
- 之后才是真正的词法与语法分析。
这个顺序解释了三件事:
- 注释是「一个空格」,不是「消失」:
int/**/x = 1;预处理后变成int x = 1;,合法。如果注释真的什么都不剩,就会粘成intx,反而报错。 - 行末反斜杠是陷阱:续行合并在注释处理之前发生。写
// 说明 \,反斜杠会把下一行并进本行,于是下一行代码也被注释吃掉。 - 注释里的引号无害:注释在词法分析前就被替换掉了,里面的
"只是普通字符,不会「打开一个字符串」。
还要分清两层:注释在哪个阶段被移除、替换成空格,是语言规则;编译器是否对可疑注释发警告、文档工具是否把 /// 当文档,属于实现细节,换编译器就可能不同。
对比:两种注释,一句口诀
| 特性 | // 行注释 | /* */ 块注释 |
|---|---|---|
| 结束位置 | 本行行末 | 遇到第一个 */ |
| 能否跨行 | 不能 | 能 |
| 能否嵌套同类 | 不能(本身就是到行末) | 不能(后者会提前结束前者) |
| 能否包含另一种的符号 | 能,/* 只是文字 | 能,// 只是文字 |
口诀:单行到行尾,块注释找第一个「星斜杠」;两种可以互装,同类绝不嵌套。
实战:临时屏蔽一段代码
预期输出:
done| 易错点 | 为什么会错 | 正确做法 |
|---|---|---|
以为 /* */ 能嵌套 | 块注释遇到第一个 */ 就结束,不认识「层次」,后面代码变真代码 | 用 #if 0 … #endif 屏蔽代码,可嵌套 |
| 行末反斜杠吞掉下一行 | 续行合并在去注释之前,b = 2; 被并入注释行 | 别让以反斜杠结尾的注释后面还跟着严肃代码 |
| 以为注释「消失」 | 注释其实替换成一个空格,若真消失会粘成 intx | 记住是「一个空格」,空格隔开两个 token 才合法 |
| 拿注释当版本库 | 大段废弃代码让读者分不清有效逻辑 | 注释写为什么,历史交给版本控制 |
| 想用宏拼出注释 | 去注释在第 ② 步,宏展开更晚,// 拼出来是除号 | 注释不能由宏生成,只能直接写 |
口诀:同类不嵌套,末尾不留杠;注释是空格,历史归版本。
易错点(先错后对)
错误 1:以为 /* */ 能嵌套
/* 外层开始
/* 想再嵌一层 */ <- 注释在这里就结束了
这一行已经是真正的代码了
*/原因:块注释遇到第一个 */ 就收尾,它不认识「层次」。第三行变成了真代码,编译报错。正确做法是屏蔽代码用预处理条件:
#if 0
// 这里可以放心地再嵌 /* */ 和 #if
#endif错误 2:行末反斜杠吞掉下一行
int b = 1; // 设置初值 \
b = 2;续行合并让第二行并入第一行,b = 2; 也进了注释,b 最终仍是 1。正确做法:别让以反斜杠结尾的注释后面还跟着严肃代码。
错误 3:拿注释当版本库
把废弃代码大段注释留在文件里,会让读者分不清「哪些是有效逻辑」。注释解释为什么,历史交给版本控制。
再深入一点:工具为什么爱注释
注释在被忽略之前,还是一段可读文本,于是工具链会去「捡」它:Doxygen 等文档工具读取 /// 或 /** */ 生成 API 文档;静态分析工具读取 // NOLINT 之类的指令来抑制告警。这些约定不是语言规则,离开对应工具就失效。
另外,注释不能由宏「拼」出来:注释在第 2 阶段就被替换掉了,宏展开发生在更晚的阶段。所以写 #define SLASH / 再拼出 //,得到的是除号,而不是注释。
自测练习
- 下面两句都能通过编译吗?说明理由。
int/**/a = 1;与int a/**/= 1; - 执行后
b的值是多少?int b = 1; // 设置初值 \ b = 2; - 你要屏蔽 200 行代码,其中本身含有多处
/* ... */注释。直接用一对/* */包起来会出什么问题?正确做法是什么?
参考答案
1. 都能。注释被替换成一个空格:第一句展开为 int a = 1;,第二句展开为 int a = 1;,都是合法声明。
2. b 等于 1。行末反斜杠触发续行合并,b = 2; 被并入上一行、整句落入注释,因此赋值从未执行。
3. 外层块注释会在内部第一个 */ 处提前结束,剩下的代码变成真代码,编译器就会报一堆错。正确做法是用 #if 0 … #endif,它可以嵌套且不受反斜杠影响。
一句话记住
注释在预处理阶段被替换成一个空格:它对程序零影响,但对读代码的人可能是全部价值。
延伸阅读
本文为免费文章,欢迎转载。转载请注明出处:一木科技(https://www.pingquan.vip/2026/09/24/cpp-comments/),并保留原文链接。
还没有评论,来说两句?