1.5 初识源文件的预处理
在 C++ 中,源文件是一个文本文件,其中包含了 C++ 程序的源代码。源文件的扩展名通常是 .cpp
,但也可以是 .cc
、.cxx
、.c++
等。
源文件的处理是一个比较复杂的过程,在这里我们先介绍一下入门时阅读和编写代码必须需要接触到的知识。
合并行
在 C++ 中,行末的 \
符号表示该行未结束,下一行是该行的延续。例如:
std::println("Hello, \
World!");
这两行代码等价于:
std::println("Hello, World!");
空白字符
在 C++ 中,源文件里的空格、制表符(按下键盘上Tab默认输入的字符)都被称为空白字符。空白字符在 C++ 中是“没有意义”的,编译器会认为空白字符将其他的字符分隔开,然后忽略这些空白字符。
只要不破坏由多个字符组成的标识符、标点符号和字面量,可以在源文件中任意添加空白字符,而不会影响程序表现。
例如,下面的代码是等价的:
std::println("Hello, World!");
与下面的代码
std :: println ( "Hello, World!" ) ;
上面的代码中,"Hello, World!"
是一个完整的字符串字面量,如果在其中添加空白字符,其含义会发生变化。
此外,在上面的代码中,println
是一个标识符,如果在其中添加空白字符,会把标识符分成两个不同的标识符,这也会导致程序的含义发生变化。
注释
在 C++ 中,注释用于在源文件中添加对代码的解释。注释不会被编译器处理,可以用于添加代码的解释、调试信息等。编译器会将注释转换成一个空格,然后如前文空白字符所述,忽略这些空格。
C++ 中有两种注释方式:
- 单行注释:以
//
开头,直到行末为止。 - 多行注释:以
/*
开头,以*/
结束。
注意,对于单行注释,如果行末有合并行的\
字符,这一行会和下一行合并,注释会直到合并后的行末。例如:
// 这是一个单行注释
std::println("Hello, World!"); // 这也是一个\
单行注释
/*
这是一个多行注释
这是第二行
*/
std::println("Hello, World!"); /* 这也是一个多行注释 */
预处理指令
预处理指令是一行以 #
开头的代码,用于告诉编译器在编译源文件中需要做的一些预先操作。C++ 标准规定了预处理指令的语法,因此,预处理指令是 C++ 语言的一部分。
此外,C++ 中用于指示模块操作的指令也是预处理指令。
举例而言,下列都是预处理指令:
#include <iostream>
#define PI 3.1415926
#line 10
模块操作的指令也是预处理指令,例如:
module;
import std;
注意,即使一行只有一个#
,这一整行也是预处理指令。例如:
#
但显然这个预处理指令没有任何功能。
需要注意,上文中提到的“一行”是在进行了上述的合并行操作之后的一行。例如:
#define PRINT_MESSAGE(header, name) \
void name(string str) { \
std::println(string(header) + str); \
}
这里的 \
用于将宏定义的内容延续到下一行。这是因为 #define
这个预处理指令必须在同一行内,因此后面的内容必须只有一行。去掉换行之后,上面的代码等价于:
#define PRINT_MESSAGE(header, name) void name(string str) { std::println(string(header) + str); }
预处理指令中也是可以添加空白字符的,但注意在# 指令
后面的部分存在有特殊的规则,不能随意添加空白字符。
举例而言,下面的代码
# include <iostream>
等价于
#include <iostream>
但是,下面的代码却不与上面的等价(因为指令后面的部分有特定的规则)
#include < iostream >
源码文件的结构
在后面的章节更深入地讨论预处理指令之前,读者可以暂时认为,源码文件里总是先有一系列预处理指令,然后是由标点符号、关键字、标识符、字面量等组成的程序代码。
也即,源文件的结构是这样的:
// 前面的部分是预处理指令
module;
#include <abc>
#define a
#line 10
// 后面的部分是程序代码
int a = 10;
int main() {
a = 1;
}