天天看点

C——预处理器(Preprocessor)和宏替换(Macro Substitution)以及其他特性的使用和注意事项

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,就跳过这部分。

条件选择的好处有两个:

  1. 可以避免重复定义宏
  2. 可以通过代码,让每个文件自己处理自己依赖的头文件,不用使用者自己去处理相互依赖的关系。

继续阅读