7 编译预处理
编译预处理,是在编译源程序之前,对源代码进行的加工和处理工作。
编译预处理的作用是对源文件中的预处理命令进行处理,生成中间文件,编译器再对中间文件进行编译,进而生成目标代码。目标代码中并不包含预处理命令。
C++ 中的预处理功能,主要包括 宏定义 、 文件包含 和条件编译 三种。
7.1 宏定义
在 C++ 语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“ 宏名 ”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为“ 宏代换 ”或“ 宏展开 ”。
7.1.1 不带参数的宏定义
无参宏定义的一般形式为
# define 宏名 字符串
其中的“#”表示这是一条预处理命令。“define”为宏定义命令。“宏名”为用户定义的标识符。
说明:
- 宏展开只是机械地进行文本替换,不是语句,不需要在其后加 “;”。如果有分号,分号也会成为替换文本的一部分,容易出现语法错误。
# define PI 3.14;
double area = PI * r * r;
// 经过宏展开后
// area = 3.14; * r * r;
- 宏定义的有效范围是定义处到本源文件结束。可以使用
#undef
提前终止其作用域。
# define PI 3.14;
double f1(){...}
# undef
- 宏定义不会替换代码中的字符串内容。
7.1.2 带参数的宏定义
C++ 语言允许宏带有参数。在宏定义中的参数称为 形式参数 ,在宏调用中的参数称为 实际参数 。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
带参宏定义的一般形式为
# define 宏名(形参表) 字符串
带参宏调用的一般形式为
宏名(实参表)
示例:
# define S(a, b) a * b
double area = S(3, 2);
// 经过宏展开后
// area = 3 * 2;
说明:
- 需要警惕生产语意错误。
# define S(a, b) a * b
double area = S(1 + 3, 2 + 4);
// 经过宏展开后
// area = 1 + 3 * 2 + 4;
// 而我们希望的结果是
// area = (1 + 3) * (2 + 4);
- 带参数的宏定义与函数是不同的。宏定义在编译前被使用,不会存在于最终程序中;而函数在被调用时使用,存在于最终程序中。
7.2 文件包含
文件包含的作用,是将指定的源文件嵌入到当前源文件的该文件包含指令位置处。
文件包含命令的一般形式为
# include "文件名"
或 # include <文件名>
文件包含命令非常重要。因为有些公用的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。
说明:
- 一个 include 命令引入一个文件
- 包含的文件必须是文本文件,如 C++ 源文件(如:
"xxx.cpp"
)和系统库文件(如:<iostream>
)。不可以是可执行文件或者目标程序。 # include <文件名>
格式,预编译器会在 C++ 系统的目录中查找要包含的文件,如果找不到则会预处理失败。# include "文件名"
格式,预编译器会在当前被编译文件所在文件夹中寻找目标文件,或者程序员可以指定首先搜索的目录,如:# include "D:/xxx/x.cpp"
,找不到再去 C++ 系统目录中寻找。
7.3 条件编译
有时,源文件中的代码并不是全部都需要进行编译。例如,我们在调试程序时,往往需要大量的输出语句,用来了解程序执行过程,而在供用户使用的程序版本中,这些输出语句就显得非常多余。
条件编译有三种形式,下面分别介绍:
7.3.1 宏名作为编译条件
# ifdef 标识符
程序段 1
<# else
程序段 2>
# endif
<...>
表示这部分可以没有。
它的功能是,如果标识符已被 #define 命令定义过则对程序段 1 进行编译;否则对程序段 2 进行编译。
7.3.2 宏名作为不编译条件
与第一种形式的区别是将 ifdef
改为 ifndef
。它的功能是,如果标识符未被# define
命令定义过则对程序段 1 进行编译,否则对程序段 2 进行于编译。这与第一种形式的功能正相反。
7.3.3 表达式的值作为编译条件
#if 常量表达式
程序段 1
<#else
程序段 2>
#endif
<...>
表示这部分可以没有。
它的功能是,如常量表达式的值为真(非0),则对程序段1 进行编译,否则对程序段2进行编译。