C语言宏定义小结

来源:互联网 发布:网络上个人信息 编辑:程序博客网 时间:2024/06/13 06:46

  在C语言中,宏定义的形式如下:

    #define name replacement-text

  这是最简单的一种宏替换——后续所有出现name的地方都会被替换成replacement-text#define指令中的name与普通变量名的命名方式相同,而replacement-text可以是任意的字符串。如果一个宏定义比较长,可以在待续行的末尾加上一个反斜杠\。就像下面这样:

     #define DO_LOOP do{\                statements;\            }while(1)

  #define指令定义的name作用域从定义处开始,直到被编译的源文件末尾处结束。宏定义的替换只针对对token,对括在引号中的字符串不起作用。例如以下代码中的printf函数仍然会打印出YES而不是123。

    #define YES 123    printf("YES");

  宏定义也可以带参数,这样可以根据不同的宏调用使用不同的replacement-text。例如,下列宏定义定义了一个宏MAX,用来返回A和B之间的较大者:

    #define MAX(A,B) ((A) > (B) ? (A) : (B))

  宏调用直接将replacement-text插入到代码中,形式参数A和B的每一次出现都会被替换成对应的实际参数。因此,语句:

     x = MAX(a + b, c + d);

  将会被替换成下列形式:

    x = ((a + b) > (c + d) ? (a + b) : (c + d));

  我们可以看到,宏MAX的展开式存在一些缺陷,作为参数的表达式要被重复计算两次,在上例中a + b和c + d均被计算了两次。如果表达式存在某些副作用,比如含有自增运算符或输入输出,那么会出现不正确的情况:

    x = MAX(i++, j--);

  它将对第一个参数做两次自增运算而对第二个参数做两次自减运算,这将会导致结果错误。同时还要注意,适当的使用圆括号来保证运算次序的正确性。由于宏调用是文本替换,替换后代码的运行结果和期望的运行结果可能会有所差异。例如以下代码:

    #define MAXN 1000 + 5    int arr[MAXN * 4];

  代码的期望运行结果是申请到一个能够容纳约4000个有符号整数的数组,但是实际运行结果是只申请到一个能够容纳1020个有符号整数的数组,访问下标大于等于1020的元素将会出现数组越界的错误。这是因为宏调用直接将MAXN替换为1000 + 5,又由于乘法的优先级大于加法,所以导致只申请到了能容纳1020个元素的整型数组。

  对于带有参数的宏,在其replacement-text中,以#为前缀的参数将会扩展为由实参替换该参数的带引号的字符串。例如:

    #define dprintf(expr) printf(#expr " = %g\n", expr)

  使用语句

    dprintf(x / y);

  调用该宏时,该宏将会被扩展为:

    printf("x / y" " = %g\n", x / y);

  其中的字符串被连接,等价于:

    printf("x / y = %g\n", x / y);

  在实际参数中,每个双引号"被替换成\",反斜杠\将被替换成\\,因此替换后的字符串是合法的字符串常量。

  预处理器运算符##将为宏扩展提供一种连接实参的手段。如果replacement-text中的参数与##相邻,那么该参数将会被实参替换,##与前后的空白符将被删除。例如,下面的宏PASTE用于连接两个参数:

    #define PASTE(FRONT, BACK) FRONT ## BACK

  因此,宏调用PASTE(hello,world)将建立记号helloworld。

  对于有参数的宏,如果它的某个实参也是一个宏,且该实参紧临###,那么该实参不会进行宏扩展。例如:

    #define STR(X) #X    #define XSTR(X) STR(X)    #define OP plus    char* opname1 = STR(OP);    char* opname2 = XSTR(OP);

  这段代码把opname1置为”OP”而把opname2置为”plus”。我们知道,符号#会立即字符串化紧随其后的参数。对于opname1来说,参数OP直接被字符串化,不再进行扩展;而对于opname2来说,由宏XSTR扩展它的参数,然后宏STR再对plus进行字符串化。

  另一个关于符号##的例子如下所示:

    #define CAT(X, Y) X ## Y    #define XCAT(X, Y) CAT(X, Y)    int var1 = CAT(1, 2);    int var2 = CAT(CAT(1, 2), 3);    int var3 = XCAT(XCAT(1, 2), 3);

  上面的代码将12赋值给var1,将123赋值给var3,var2所在的那行代码并不能正确执行。这是因为符号##阻止了外层宏调用对其参数的扩展。而由于宏XCAT本身不含有符号##,所以var3可以被正确赋值。

参考资料:
[1]Brain W. Kernighan and Dennis M. Ritchie, The C Programming Language, Second Edition, Prentice Hall, 1988
[2]Steve Summit, 《你必须知道的495个C语言问题》, 第二版, 北京 : 人民邮电出版社, 2016

原创粉丝点击