C语言宏定义中#与##运算符
2012-11-25 21:04 muzinian 阅读(3541) 评论(0) 编辑 收藏 举报#运算符
宏定义可以包含两个专用的运算符:#和##。编译器不会识别这两个运算符,他们会预处理时被执行。
#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替代列表中。(#运算符所执行的操作可以理解为“字符串化(stringization)”).
#运算符有许多用途,这里只来讨论其中的一种。假设我们决定在调试过程中使用PRINT_INT宏作为一个便捷的方法来输出整型变量或表达式的值。#运算符可以使PRINT_INT为每个输出的值添加标签。下面是改进后的PRINT_INT:
#define PRINT_INT(n) printf(#n " = %d\n",n)
n之前的#运算符通知预处理器根据PRINT_INT的参数创建一个字符串字面量。因此,调用
PRINT_INT(i/j);
会变为
printf("i/j" " = %d\n",i/j);
等价为printf("i/j = %d\n",i/j);
##运算符
##运算符可以将两个记号(如标示符)"粘合"在一起,成为一个记号。如果其中一个操作数是宏参数,“粘合”会在形式参数被相应的实际参数替换后发生。考虑下面的宏:
#define MK_ID(n) i##n
当MK_ID被调用时(MK_ID(1)),预处理器首先使用实际参数替换形式参数n。接着,预处理器将i和1合并成为一个记号(i1)。下面的声明使用MK_ID创建了3个标识符:
int MK_ID(1),MK_ID(2),MK_ID(3);
预处理后这一声明变为:int i1,i2,i3;
如果要被“字符串化”的参数包含“或\字符,#运算符会将"转换为\",\转换为\\。考虑下面的宏:
#define STRINGIZE(x) #x
预处理器会将STRINGIZE("foo")替换为”\“foo\""。
替换列表中依赖##的宏通常不能嵌套调用。如:
#define CONCAT(x,y) x##y 尽管CONCAT(a,b)会如期望那样得到ab,但CONCAT(a,CONCAT(b,c))会给出一个怪异的结果。这里的问题在于CONCAT(a,CONCAT(b,c))不会按照"正常"的方式扩展——CONCAT(b,c)得出bc。然后CONCAT(a,bc)给出abc。在替换列表中,位于##运算符之前和之后的宏参数在替换时不被扩展,因此,CONCAT(a,CONCAT(b,c))扩展aCONCAT(b,c),而不会进一步扩展,因为没有名aCONCAT的宏。
有一种办法可以解决这个问题,但不好看。技巧是再定义一个宏来调用第一个宏:
#define CONCAT2(x,y) CONCAT(x,y)
用CONCAT2(a,CONCAT2(b,c))就会得到abc。
以上来源于C语言程序设计_现代方法(第2版)
但是,如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。例如,可以将它与字符串连接运算结合起来编写一个调试打印宏:#define dprint(expr) printf(#expr " = %g\n", expr)使用语句dprint(x/y)调用该宏时,该宏将被扩展为:printf("x/y" " = &g\n", x/y);其中的字符串被连接起来了,这样,该宏调用的效果等价于printf("x/y = &g\n", x/y);在实际参数中,每个双引号"将被替换为\",反斜杠\将被替换为\\,因此替换后的字符串是合法的字符串常量。预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。例如,下面定义的宏paste用于连接两个参数#define paste(front, back) front ## back因此,宏调用paste(name, 1)的结果将建立记号name1。
两个特殊的运算符会影响替换过程。首先,如果替换记号序列中的某个形式参数前面直接是一个#符号(它们之间没有空白符),相应形式参数的两边将被加上双引号("),随后,#和形式参数标识符将被用引号引起来的实际参数替换。实际参数中的字符串字面值、字符常量两边或内部的每个双引号(")或反斜杠(\)前面都要插入一个反斜杠(\)。其次,无论哪种宏的定义记号序列中包含一个##运算符,在形式参数替换后都要把##及其前后的空白符都删除掉,以便将相邻记号连接起来形成一个新记号。如果这样产生的记号无效,或者结果依赖于##运算符的处理顺序,则结果没有定义。同时,##也可以不出现在替换记号序列的开头或结尾。对这两种类型的宏,都要重复扫描替换记号序列以查找更多的已定义标识符。但是。当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符时不再对其执行替换,而是保持不变。即使执行宏扩展后得到的最终结果以#打头,也不认为它是预处理指令。说明:有关宏扩展处理的细节信息,ANSI标准比第1版描述得更详细。最重要的变化是加入了#和##运算符,这就使得引用和连接成为可能。某些新规则(特别是与连接有关的规则)比较独特(参见下面的例子)。
例如,这种功能可用来定义“表示常量”,如下例所示:
#define TABSIZE 100
int table[TABSIZE];
定义
#define ABSDIFF(a, b) ((a)>(b) ? (a)-(b) : (b)-(a))
定义了一个宏,它返回两个参数之差的绝对值。与执行同样功能的函数所不同的是,参数与返回值可以是任意算术类型,甚至可以是指针。同时,参数可能有副作用,而且需要计算两次,一次进行测试,另一次则生成值。假定有下列定义:
#define tempfile(dir) #dir "%s"
宏调用tempfile(/usr/tmp)将生成"/usr/tmp" "%s"随后,该结果将被连接为一个单个的字符串。给定下列定义:#define cat(x, y) x ## y那么,宏调用cat(var, 123)将生成var123。但是,宏调用cat(cat(1,2),3)没有定义:##阻止了外层调用的参数的扩展。因此,它将生成下列记号串:cat ( 1 , 2 )3并且,)3 (不是一个合法的记号,它由第一个参数的最后一个记号与第二个参数的第一个记号连接而成。如果再引入第二层的宏定义,如下所示:#define xcat(x, y) cat(x,y)我们就可以得到正确的结果。xcat(xcat(1, 2), 3)将生成123,因为xcat 自身的扩展不包含##运算符。类似地,ABSDIFF(ABSDIFF(a,b),c)将生成所期望的经完全扩展后的结果。
以上来源C程序设计语言(第2版)