14 预处理器
14.1 预处理器
预处理器是一个程序,在源代码通过编译器之前,他先对源代码进行处理。他是在称为预处理器命令行或指令的控制下操作。预处理器指令放在源程序的main函数之前。在源代码通过编译器之前,由预处理器检查所有预处理指令。如果有预处理指令,则采取相应的动作,然后再把源程序交给编译器。
表14.1 预处理指令
指令 | 作用 |
#define | 定义一个宏替换 |
#undef | 取消一个宏定义 |
#ifdef | 测试某个宏已定义 |
#ifndef | 测试某个宏未定义 |
#include | 指定要包含的文件 |
#if | 测试一个编译时条件 |
#else | 当#if测试失败时,指定另一个测试 |
#endif | 表示#if的结束 |
这些指令分为三类:
- 宏替换指令
- 文件包含指令
- 编译器控制指令
宏替换是程序中的标识符被预定义的字符串取代的过程。一般形式:#define identifier string
如果该语句位于程序的开头,那么预处理器把源代码中每个identifier都用string来替代。关键字#define位于该行的第一列,后面跟identifier和string,他们相互之间至少有一个空格。注意,该定义不以分号结尾。
14.2.1 简单宏替换
通常用于定义常量。如:#define PI 3.14159
将把程序中所有的PI都替换为3.14159(从宏定义行开始直到程序结尾)。但是,字符串中的宏不会被替换。如下面两行语句:
area = PI * r * r;printf("PI = %f", PI);
预处理后,上面两行语句变成:area = 3.14159 * r * r;printf("PI = %f", 3.14159);
宏定义还可以包含表达式,但当我们使用表达是替换时,应小心防止意外的计算顺序。如下:
#define D 45 - 22
#define A 78 + 32
计算 ratio = D / A;经过预处理,结果为: ratio = 45 - 22 / 78 + 32;
这显然与所期望的表达式不同,通过使用括号定义可得正确结果:
#define D (45 - 22)
#define A (78 + 32)
所以为宏定义的表达式使用括号是明智之举。正如前面所介绍的那样,对所有已定义的宏,预处理器都将执行
文本替换,这就解释了为什么#define语句不能以分号结尾.(分号也会参与替换)
14.2.2 含参数的宏
预处理器允许使用更复杂的替换形式:#define identifier(f1, f2, ..., fn) string
注意,在宏identifier与左括号之间没有空格。
含参数的替换称为宏调用(类似于函数调用)。当调用宏时,预处理器将替换该字符串,即用实参替换形参。因此,字符串就像一个模板。示例如下:
#define CUBE(x) (x * x * x)
如果在程序中出现语句:volume = CUBE(side);经过预处理后,volume = (side * side * side);
如果volume = CUBE(a + b);经过预处理后,volume = (a + b * a + b * a + b);
这显然不是我们想要的结果。所以要记得为每个形参和整个string加括号。
volume = CUBE( (a) + (b) );
下面是一些常用的带形参的宏定义:
#define MAX(a, b) (((a) > (b) ) ? (a) : (b))
#define MIN(a, b) (((a) < (b) ) ? (a) : (b))
#define ABS(x) (((x) > 0) ) ? (x) : (-x))
14.2.3 宏嵌套
我们可以再宏的定义中使用另一个宏。示例如下:
#define SQUARE(x) ((x) * (x))
#define CUBE(x) (SQUARE(x) * (x))
#define SIXTH(x) (CUBE(x) * CUBE(x))
预处理器将扩展每个#define宏,直到文本中不再有宏为止。如,最后一个宏定义的第一次扩展:
((SQUARE(x) * (x)) * (SQUARE(x) * (x)))
由于SQUARE(x) 仍然是一个宏,因而进一步扩展:
((((x) * (x)) * (x)) * (((x) * (x)) * (x)))
一个宏还可以用做另一个宏的参数。如我们前面定义:
#define MAX(a, b) (((a) > (b) ) ? (a) : (b))
我们可以通过嵌套来得出x,y,z三者最大值。MAX(x, MAX(y,z));
14.2.4 取消宏定义
对于已经定义的宏,可以使用后面语句取消宏定义:#undef identifier
14.3 文件包含
- 第一种形式如:#include "filename"
其中,filename为含有所需宏定义或函数的文件名。此时,预处理器把filename的整个内容插入到程序的源代码之中。当filename包含在双引号中时,首先从当前目录中查找该文件,然后再在标准目录中查找。
- 第二种形式如:#include <filename>