4.11.2 宏替换(macro substitution)
宏定义的形式:
#define 名字 替换文本
这是一种最简单的宏替换——后续所有出现名字记号的地方都将被替换成替换文本。#define指令中的名字[的命名方式]与变量名命名方式相同;替换文本是任(程序员)意的。通常,替换文本是define指令行尾部的所有剩余部分,但一条较长的宏定义可通过在待续的行末尾加个反斜杠符\而分成若干行。#define指令定义的名字的作用域从其定义点开始,到被编译的源文件(指令所在的源文件?)的末尾处结束。宏定义中也可以使用前面[宏定义过]的宏定义。替换只对记号进行,对锁在引号中的字符串不起作用。如,若 YES 是通过#define指令定义过的名字,则在printf("YES")或YESMAN中将不执行替换。
[任何名字都可以被定义成任何替换文本]替换文本可以是任意的,如:
#define forever for (;;) /* Infinite loop.*/
为无限循环for(;;)定义了新名字forever。
宏定义也可带参数,可对不同的宏调用使用不同的替换文本。举例,定义名为max的宏:
#define max(A, B) ((A) > (B) ? (A) : (B))
尽管它看起来像是一个函数调用,使用宏max直接将替换文本插入到代码中。形式参数(在此为A或B)的每次出现都将被替换成对应的实际参数。因此,指令行
x = max(p+q, r+s);
将被替换成:
x = ((p+q) > (r+s) ? (p+q) : (r+s));
只要对各类参数的处理是一致的,同一个宏可应用于任何数据类型;无须,像函数那样针对不同的数据类型去定义不同类型的max宏。
检查max的展开式,发现一些缺陷。作为参数的表达式被重复计算两次;如果表达式存在(比如含有自增运算符或输入/输出的)副作用就不好了。例如:
max(i++, j++) /*WRONG*/
将对每个参数执行两次自增操作。同时还需注意适当使用圆括号以保证计算顺序的正确性。考虑下列宏定义:
#define square(x) x * x /*WRONG*/
当用 square(z+1)调用该宏定义时会出现什么情况?
虽然如此,宏是很有价值的。<stdio.h>头文件中有个很实用的例子:getchar与putchar函数在实际中常被定义为宏以避免处理字符时调用函数所需的运行时间的开销。<ctype.h>头文件中定义的函数也常是通过宏实现的。
名字可以通过#undef指令来取消宏定义,这可保证后续的调用是函数调用,而不是宏调用:
#undef getchar
int getchar(void) {...}
形式参数不能被替换为用带引号的字符串。但,若在替换文本中的参数名是以#作为前缀,则结果将被扩展为“由实际参数替换该参数的”带引号的字符串。例如,可将它与“字符串连接运算”结合起来编写一个调试打印宏:
#define dprint(expr) printf(#expr " = %g\n", expr)
使用语句
dprintf (x/y)
调用该宏时,该宏将被扩展为:
printf("x/y" " = &g\n", x/y); /* why changed '%' to '&' */
其中的字符串被连接起来了,该宏调用的效果等价于:
printf("x/y = &g\n", x/y);
在实际参数中,每个 " 将被替代为 \" ,每个 \ 将被替换为 \\ ,因此替换后的字符串是合法的字符串常量。
预处理器运算符 ## 为宏拓展提供了连接实际参数的方法。若替换文本中的某个参数与 ## 相邻,则该参数被实际参数替换,##和其前后的空白符将被删除,且替换结果会被重新扫描。例如:定义的宏paste连接它的两个参数:
#define paste(front back) front ## back
因此, paste(name, l) 将建立记号 namel。
##的嵌套使用的规则较难;详见附录A。
练习4-14 定义宏 swap(t, x, y) 以交换两个t类型的参数。(程序块结构会有所帮助。)