C

C 知识量:16 - 74 - 317

16.2 #define中的参数><

类函数宏- 16.2.1 -

在#define中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,这些参数也需要出现在替换体中。例如:

#define ADD(X) X+X

在程序中可以这样使用:

R = ADD(2);

其中,ADD(2)会被替换为2+2,R的值为4。

需要特别注意的是,函数调用和宏调用的重要区别:函数调用在程序运行时把参数的值传递给函数;宏调用在编译之前把参数记号传递给程序。这两个不同的过程发生在不同时期。

在使用带参数的类函数宏时,需要注意以下2点:

1、应当避免在宏中使用递增或递减运算符,例如对于以下定义:

#define MAKE(x) x*x

如果使用MAKE(++x),那么宏展开为++x*++x,由于C标准没有规定此种情况下的计算规则,在计算乘法之前,x可能递增了一次,也可能递增了两次,结果将是不确定的。

2、为了在宏展开和计算中得到预期的效果,应当在替换体中使用小括号来确保表达的准确性。例如对于上面的宏定义,如果使用MAKE(3+2),那么宏展开为3+2*3+2,计算的结果会是11,而不是预期的25。为了避免这种情况,可以这样修改:

#define MAKE(x) (x)*(x)

这样就可以避免出现以上情况,如果还存在问题,可以考虑继续添加小括号解决。

#运算符- 16.2.2 -

如果有以下宏定义:

#define PS(X) printf("The square of X is %d.\n",((X)*(X)))

如果X为9,那么运行结果为:

The square of X is 81.

printf()函数双引号中的X作为一个普通文本没有被替换为9,如果希望这个X也作为一个可以替换的记号,可以在X前面加上#运算符。以下是一个新的示例:

#include <stdio.h>
#define PS1(X) printf("The square of " #X " is %d.\n",((X)*(X))) //正确的用法
#define PS2(X) printf("The square of #X is %d.\n",((X)*(X)))     //错误的用法

int main(void) {
    int y = 4;
    PS1(y);
    PS1(5 + 5);
    PS2(y);
    PS2(5 + 5);
    system("pause");
    return 0;
}

运行的结果为:

The square of y is 16.
The square of 5 + 5 is 100.
The square of #X is 16.
The square of #X is 100.

在使用#X时注意不能将它置于printf()函数的双引号内,PS2(X)宏定义是一个错误的演示。在类函数宏的替换体中,#号作为一个预处理运算符,可以把记号(宏形参)转换成字符串,这个过程称为字符串化。在PS1(X)的宏展开中,利用ANSI C字符串的串联特性,#X与printf()语句的其他字符串组合,生成最终的字符串。

##运算符- 16.2.3 -

##运算符被称为预处理器黏合剂,可用于把两个记号合并为一个记号,可用于在预编译时编辑变量名。例如:

#include <stdio.h>
#define PS1(X) printf("The number %s","is "#X)        //#运算符
#define PS2(X) printf(".\nThe strring is %s.\n",s##X) //##运算符

int main(void) {
    char s3[] = "hello";
    PS1(3);
    PS2(3);
    system("pause");
    return 0;
}

运行结果为:

The number is 3.
The strring is hello.

main函数中,首先定义了字符串s3,然后PS2(3)将替换为一个printf()函数,其中,s##X将替换为s3,正好是定义的字符串s3。而PS1(3)的宏展开中只是将#X替换为字符串"3"。

变参宏- 16.2.4 -

C的一些函数(例如printf())接受数量可变的参数,宏也可以实现这种功能。这需要同时使用...(省略号)和__VA_ARGS__(注意前后都是两个“_”)来实现。示例:

#include <stdio.h>
#define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)

int main(void) {
    int x = 6;
    int y = x*x;
    PR(1, "x = %d\n", x);
    PR(2, "x = %d, y = %d\n", x, y);
    system("pause");
    return 0;
}

运行结果为:

Message 1: x = 6
Message 2: x = 6, y = 36

注意:只能把宏参数列表中最后的参数写成省略号;__VA_ARGS__用在替换部分中,表明省略号代表什么。

  • 第一次宏展开,__VA_ARGS__展开为2个参数:"x = %d\n"、x。

  • 第二次宏展开,__VA_ARGS__展开为3个参数:"x = %d, y = %d\n"、x、y。

宏和函数的选择- 16.2.5 -

有时带参数的宏与函数可以实现相同的功能,选择宏还是函数需要考虑以下几点:

  • 宏比函数要复杂一些,更容易出错。

  • 宏和函数的选择实际上是时间和空间的权衡。宏的用时更少,而函数占用的空间更少,各有优势。

  • 使用宏不用担心变量类型,宏处理的是字符串,而不是实际的值。

  • 最重要的是:宏在预编译时起作用,而函数在执行时起作用。