C语言的预处理器(Preprocessor)理论上是编译的第一步,通过预处理器来提供了一些特性,例如宏、使用
##
串联参数、条件包含(Conditional Inclusion)等。
宏替换(Macro Substitution)
C语言在预处理器中提供了宏替换(Macro Substitution)的使用。宏的作用域是从
#define
到文件结束。宏最简单的定义格式如下:
如果后续代码出现了
name
,那么就会用
replacement text
的内容去替换
name
。但是需要注意的是,这个替换只可以替代token(标记),不能替代被引用字符串中的一部分。举个例子来说明一下这个问题,假设
name
部分为
MAXVAL
,如果后面我们使用
printf("MAXVAL")
或者出现
MAXVALUE
,都是不会发生替换的。
这里的
name
部分可以按照变量的格式来写,也可以带参数(只要参数能都是同样的类型,那么宏能使用任何的数据类型);
replacement text
的部分可以是整数、浮点数、布尔值、一串代码甚至调用其他之前定义好的宏。例如:
尽管这个宏看起来很像函数调用,但是max(A, B)使用的时候会扩展成内连代码(也就是编译的时候,编译器会在这写上这写代码,然后编译,而不是调用一个函数)。
而且需要注意的是,这个地方有个“陷阱”,就是这个宏在使用的时候会判断两次。所以需要注意,宏在使用的时候带参数,会运行两次。尽管如此,这个“陷阱”也有好处,由于会自动多运行一次,这会减少代码的运行时间。
通常我们定义一个宏只需要一行代码,但是如果需要定义比较长的代码的情况下,需要在每一行的结束写上
\
来表示这行结束了,并且继续。举个例子,我们来交换
t
类型的
x
和
y
两个参数的值:
#include <stdio.h>
#define swap(t, x, y) { t c;\
c = y;\
y = x;\
x = c;}
int main()
{
int x = 1;
int y = 2;
swap(int, x, y);
printf("x is %d, y is %d\n", x, y);
return 0;
}
我们可以看到
#define
的每一行最后都是以反斜杠
\
结尾,然后再进行下一行。由于这里反斜杠已经被用于表示每行的结束,如果想使用反斜杠
\
,就使用两个反斜杠
\\
。由于双引号
"
也可以表示字符串的开始或者结尾,也可以结束
#define
,所以我们想使用双引号
"
的时候就使用一个反斜杠和一个双引号
\"
来表示。
串联参数
除此之外,C还提供了一些预操作器中的特性。
预操作符
##
提供了一种方式,在宏扩展(也就是使用的时候)来串联一些参数(将其串联成一个字符串,并且忽略
##
及周围的空格)。举个例子:
那么
paste(program, 1)
的就会表示
program1
。
##
嵌套比较神奇,所以不建议使用嵌套。
不过这里还是解释一下
##
嵌套为什么神奇。我们继续上面的代码,这次我们这样使用:
paste(paste(1, 2), 3)
。由于
paste(paste(1, 2), 3)
是从来没被定义过的,所以这时候按照我们之前定义的,代码变成了
paste(1, 2)3
,也就是说代码把
(paste(1, 2)
当作一个参数和
3
串联在一起。然后由于
)3
不是一个合规的表示(第一个参数的最后一个标记和第二个参数的第一个标记串联在一起,这里指的第几个参数是指的原本最外部的宏的参数),所以
##
制止了第二次扩展。
不能使用嵌套的话,可以定义一个新的宏来调用这个宏。
条件选择(Conditional Inclusion)
条件选择就是在
#define
的部分使用的if-else,语法稍有不同:
使用
#if
来做第一个判断;
#elif
进行下一个判断,等同于if-else语句中的
else if
;
#endif
表示判断部分结束。
和if-else语句一样,通过判断后面的式子的值是否为0,进而判断真假。如果是非0的值,则为真,执行
#if
到
#elif
或者
#endif
的代码;如果值是0,就跳过这部分。
条件选择的好处有两个:
- 可以避免重复定义宏
- 可以通过代码,让每个文件自己处理自己依赖的头文件,不用使用者自己去处理相互依赖的关系。