C 知识量:16 - 74 - 317
在#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)
这样就可以避免出现以上情况,如果还存在问题,可以考虑继续添加小括号解决。
如果有以下宏定义:
#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()语句的其他字符串组合,生成最终的字符串。
##运算符被称为预处理器黏合剂,可用于把两个记号合并为一个记号,可用于在预编译时编辑变量名。例如:
#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"。
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。
有时带参数的宏与函数可以实现相同的功能,选择宏还是函数需要考虑以下几点:
宏比函数要复杂一些,更容易出错。
宏和函数的选择实际上是时间和空间的权衡。宏的用时更少,而函数占用的空间更少,各有优势。
使用宏不用担心变量类型,宏处理的是字符串,而不是实际的值。
最重要的是:宏在预编译时起作用,而函数在执行时起作用。
Copyright © 2017-Now pnotes.cn. All Rights Reserved.
编程学习笔记 保留所有权利
MARK:3.0.0.20240214.P35
From 2017.2.6