预计阅读本页时间:-
通过使用参数,可以创建外形和作用都与函数相似的类函数宏(function-like macro)。宏的参数也用圆括号括起来,因此,带参数的宏外形与函数非常相似。类函数宏的定义中,用圆括号括起一个或多个参数,随后这些参数出现在替换部分,如图16.2所示。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
图16.2 类函数宏定义的组成
下面是一个类函数宏定义的示例:
在程序中可以这样使用:
这看上去就像函数调用,但它们的行为不完全相同。程序清单16.2举例说明这个类函数宏和另一个宏的使用方法。有些示例还指出了可能存在的缺陷,因此,应仔细阅读该程序清单。
程序清单16.2 mac_arg.c程序
宏SQUARE的定义为:
其中,SQUARE为宏标识符,SQUARE(×)中的×为宏的参数,×*×为替换列表。程序清单16.2中出现SQUARE(×)的地方都用×*×代替。这与前面的示例有些不同,使用这个宏时既可以使用×,也可以自由地使用其他符号。宏定义中的×由程序调用的宏中的符号代替。因此,SQUARE(2)替换为2*2, ×实际上起到了参数的作用。
但是,您即将看到,宏的参数与函数的参数不完全一样,下面是程序运行的结果。注意,有些答案与我们所期待的答案不同。实际上,您的编译器甚至会给出与下面所示不同的答案:
头两行是我们期待的结果,但接下来是些奇特的结果。回忆一下,×的值为4。可能您会认为SQUARE(×+2)应该是6*6(即36),但输出的结果是14,这不像是个平方值。产生这种令人费解的输出结果的简单原因我们曾经声明过:预处理器不进行计算,而只进行字符串替换。在出现×的地方,预处理器都用字符串×+2进行替换。因此:
变成:
惟一的乘法运算为2*×。如果×为4,表达式值为:
本例指出了函数调用和宏调用之间的重要差异。程序运行时,函数调用把参数的值传递给函数。而编译前,宏调用把参数的语言符号传递给程序。这是不同时间发生的不同过程。可以修改定义使SQUARE(×+2)输出36吗?可以。只须多加几个圆括号。
现在,SQUARE(×+2)变成了(×+2)*(×+2),在替换字符串中使用圆括号得到了期待的乘法运算。
但是,这还不能解决所有问题。考虑下面输出行:
它将变成:
根据优先级规则,从左到右对表达式求值:
把SQUARE(×)定义为下面的形式可以解决这种混乱:
这样做会产生100/ (2*2),最后求出值为100/4即25。
要处理前面两个示例中的情况,需要这样定义:
从中得到的经验是使用必需的足够多的圆括号来保证以正确的顺序进行运算和结合。
不过,这些措施还是无法避免最后一个示例中的问题。
变成:
×进行了两次增量运算,其中一次在乘法操作前,另一次在乘法操作后。
因为对这些运算的顺序没有做出规定,所以有些编译器产生乘积6*5。而其他编译器可能在乘法运算前同时对×进行自加操作,从而产生6*6。但在两种情况下,×的开始值均为4,终止值均为6。然而,从代码来看,×只进行一次增量操作。
解决这个问题的最简单的方法是避免在宏的参数中使用++×。一般来说,在宏中不要使用增量或减量运算符。注意,++×可作为函数参数,因为会对++×进行计算得到值5,再把5传递给函数。
16.3.1 利用宏参数创建字符串:#运算符
下面是一个类函数宏:
如果这样使用宏:
则输出为:
注意,引号中的字符串中的×被看作普通文本,而不是被看作一个可被替换的语言符号。
假设您确实希望在字符串中包含宏参数,ANSIC允许您这样做。在类函数宏的替换部分中,#符号用作一个预处理运算符,它可以把语言符号转化为字符串。例如,如果×是一个宏参量,那么#×可以把参数名转化为相应的字符串。该过程称为字符串化(stringizing)。程序清单16.3说明了该过程。
程序清单16.3 subst.c程序
输出如下:
第一次调用宏时,用“y”代替#×;第二次调用宏时,用“2+4”代替#×。ANSIC的字符串连接功能将这些字符串与printf()语句中的其他字符串组合以产生最终使用的字符串。例如,第一次调用变成:
接着,字符串连接功能将这三个相邻的字符串转换为一个字符串:
16.3.2 预处理器的粘合剂:##运算符
和#运算符一样,##运算符可以用于类函数宏的替换部分。另外,##还可用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如,可以定义如下的宏:
这样,下面的宏调用:
会展开成下列形式:
程序清单16.4用这个宏和另外一个使用##的宏进行了一些语言符号的粘合操作。
程序清单16.4 glue.c程序
输出如下:
注意宏PRINT_XN()如何使用#运算符组合字符串、如何使用##运算符把两个语言符号组合为一个新的标识符。
16.3.3 可变宏:…和__VA_ARGS__
有些函数(如printf ())接受可变数量的参数。本章稍后讨论的头文件stdvar.h提供了创建用户自定义的带可变数量参数的函数的工具。C99对宏作同样的工作。虽然“可变”(variadic)不是标准词,但它已经成为标志这种工具的词(虽然“字符串化(stringizing)”和“可变”已经添加到C词汇表中,但是,固定参数的函数或宏并没有被称为固定(fixadic)函数和不变(normadic)宏)。
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个句号)。这样,预定义宏__VA_ARGS__就可以被用在替换部分中,以表明省略号代表什么。例如,考虑下面的定义:
假设稍后用下面的方式调用该宏:
第一次调用中,__VA_ARGS__展开为1个参数:
第二次调用中,它展开为3个参数:
因此,展开后的代码为:
程序清单16.5显示了一个较为复杂的示例,其中使用了字符串连接功能和#运算符:
程序清单16.5 variadic.c程序
在第一个宏调用中,×的值为1,因此,#×变成“1”。展开后成为:
接着连接4个字符串,把调用简化为:
输出如下:
记住,省略号只能代替最后的宏参数。下面的定义是错误的: