1.1 注释
1. 定义
注释的作用是在代码内写文档供人类阅读;编译器会忽略注释(技术上,是把注释替换成一个空格)。
注释在翻译阶段被替换为空格
在翻译阶段 3,每个注释会被替换成一个空格。注释在记号化之前被移除,不参与语法和语义分析,也不是运行时行为。
2. 分类
- 行内注释:以
//开始,编译器会忽略从//开始到行末的所有字符。 - 块注释:以
/*开始,以*/结束。编译器会忽略从/*开始到*/结束的所有字符,包括/*和*/。
形式化语法
2.1 行内注释
any-char-except-newline:除换行符(如\n或\r)以外的任何字符。
line-comment ::= "//" line-content
line-content ::= any-char-except-newline line-content
| empty
empty ::= ""2
3
4
5
6
2.2 块注释
non-star-char:除星号*以外的任何字符。non-star-non-slash-char:除星号*和斜杠/以外的任何字符。
/* 块注释由 "/*" 开始,进入状态 S0 */
block-comment ::= "/*" state-S0
/* 状态 S0:上一个读入的字符不是 "*" */
state-S0 ::= non-star-char state-S0
| "*" state-S1
/* 状态 S1:上一个读入的字符是 "*" */
state-S1 ::= "/"
| "*" state-S1
| non-star-non-slash-char state-S02
3
4
5
6
7
8
9
10
11
3. 示例
#include <stdio.h>
int main(void) {
// 这是行内注释
/* 这是块注释 */
puts("Hello World 01");
// puts("Hello World 02");
/*
puts("Hello World 03");
puts("Hello World 04");
puts("Hello World 05");
*/
/*
* 为了整齐美观,也可以这样写:
* puts("Hello World 06");
* puts("Hello World 07");
* puts("Hello World 08");
*/
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可能的输出:
Hello World 01
4. 常见错误
块注释不能嵌套
块注释不能嵌套,也就是 /* ... /* ... */ ... */ 这种写法不合法。
5. 习题
5.1 巩固
下面的程序里有五次 puts 调用,其中一部分被注释掉了。先不运行,写出你预计的输出。
#include <stdio.h>
int main(void) {
puts("one"); // 行尾注释
// puts("two");
/* puts("three");
puts("four"); */
puts("five /* six */");
return 0;
}2
3
4
5
6
7
8
9
10
- 实际执行的
puts有哪几次? - 最后一行的字符串里出现了
/*和*/,它们为什么没有把后面的内容注释掉? - 把最后一行改成
puts("five " /* six */);,重新构建运行,输出是什么?six为什么不再出现?注释替换出的那个空格落在字符串内部还是外部? - 如果写成
puts("http://example.com");,字符串里的//会被当作行内注释的开始吗?
运行程序核对答案。
注释在翻译阶段被替换成一个空格,原来紧挨着的记号因此被空格隔开。建立四个源文件,开头都是 #include <stdio.h>,main 的函数体里各只有一行:
int/**/x = 1; /* a.c */
int y = 1 +/**/+ 2; /* b.c */
ret/**/urn 0; /* c.c */
puts/**/("A"); /* d.c */2
3
4
逐个用下面的命令构建并运行,记录哪一段编译失败、第一条 error 是什么,以及能通过的各段运行后的输出。只出现警告不算失败,
a.c和b.c报的-Wunused-variable可以忽略。textgcc -std=c17 -Wall -Wextra a.c -o a1其余三段把
a换成对应的文件名。写出注释换成空格后每一行的记号序列。
a.c若直接写成intx = 1;会得到什么结果?b.c若写成int y = 1 ++ 2;为什么反而编译不过?d.c里函数名和(之间多出一个空格,为什么调用照常成功?对
b.c运行gcc -std=c17 -E -P b.c -o b.i,在b.i末尾找到main,抄下原来那一行。注释不见了,留下来的记号是什么?
验收:四段中恰有一段编译失败;b.c 的预处理结果里该行是 int y = 1 + + 2;,两个 + 之间有一个空格。
命令行编译在第二部分正式讲,这里的两条命令照抄即可;用 IDE 时找「只做预处理」或「预处理到文件」的选项。
5.2 编程
下面的 flow.c 用五行输出表示一个流程的五个步骤。
#include <stdio.h>
int main(void) {
puts("step 1: open input");
puts("step 2: parse header");
puts("step 3: compute");
puts("step 4: write output");
puts("step 5: close files");
return 0;
}2
3
4
5
6
7
8
9
10
只增删注释,不改动任何
puts的实参,也不删除语句,从这份源文件做出三个程序:flow_all.c:五行都输出;flow_skip2.c:跳过解析,不输出 step 2,其余四行照常;flow_min.c:只保留首尾,中间三行用一条块注释一次注释掉。
给
flow_all.c加一段文件头块注释,写明用途、构建命令和三个程序各自的预期输出;再给 step 3 那一行加一条行内注释,说明这一步在流程里的作用,不要复述代码。加完注释后输出必须与第 1 步完全相同。三个文件分别用
gcc -std=c17 -Wall -Wextra flow_all.c -o flow_all这样的命令构建(文件名相应替换),要求没有警告,运行输出与预期逐行相同。
5.3 综合与挑战
下面的 comment.c 想把中间两行代码注释掉,编译却失败了。
#include <stdio.h>
int main(void) {
puts("begin");
/*
puts("/* 与 */ 是块注释的定界符");
puts("这一行也被注释掉了");
*/
puts("done");
return 0;
}2
3
4
5
6
7
8
9
10
11
(1) [2.1] 用 gcc -std=c17 -Wall -Wextra comment.c -o comment 构建,抄下第一条 warning 和第一条 error,带上行号。
(2) [3.2] 按块注释的扫描规则说明:第 5 行的 /* 打开注释后,注释在第 6 行的哪一处结束?结束后第 6 行剩下的文字被当成什么,missing terminating " character 由此产生?第 8 行的 */ 又处于什么状态?
(3) [3.2] 不删除中间那两条 puts 语句,只在行首加注释符,改用行内注释达到原意,使程序只输出 begin 和 done,并且 -Wall -Wextra 下没有警告。说明这里为什么不能继续用块注释。
(4) [5.3**] 行内注释有一种会吞掉下一行的写法:
#include <stdio.h>
int main(void) {
// 这一行注释以反斜杠结尾 \
puts("这一行会被注释吞掉");
puts("这一行会输出");
return 0;
}2
3
4
5
6
7
8
构建并运行,抄下警告和实际输出,解释第二条 puts 为什么没有执行。提示:反斜杠与换行的处理发生在注释被替换之前。