在C语言中,我们使用#define来定义宏。在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本。
不带参数的宏
关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形式:
1#define 宏名称 替换文本
例如:
1#define ARRAY_SIZE 10
2int data[ARRAY_SIZE];
当程序需要修改数组长度时,只需要修改宏定义即可,无需对程序中每一处用到数组长度的地方进行修改。
带参数的宏
你可以定义具有形参的宏,然后预处理器展开这类宏时,会将宏指定的实参替换文本中对应的形参。这有点像函数,故也叫做函数式宏定义、类函数宏,形式如下:
1#define 宏名称([行参列表]) 替换文本
2#define 宏名称([行参列表,]...) 替换文本
当宏被调用时,替换文本中的每个值都与形参列表相对应。另外C99标准允许定义有参略号的宏,省略号必须放在参数列表的后面,以表示可选参数。
当调用有可选参数的宏时,预处理器会将所有可选参数连同分隔它们的逗号打包在一起作为一个参数。在替换文本中,标识符 __VA_ARGS__对应前面打包的可选参数。
1//假设有个已经打开的日志文件,准备采用文件指针fp_log对其进行写入
2#define printLog(...) fprintf(fp_log, __VA_ARGS__)
3//使用printLog
4printLog("%s: intVar=%d\r\n", __func__, intVar);
预处理器把最后一行的宏调用替换成下面一行代码:
1fprintf(fp_log, "%s: intVar=%d\r\n", __func__, intVar);
带参宏的一些问题
1#include
2#define SQUARE(x) x * x
3int main(int argc, char const *argv[])
4{
5 int a = 5;
6 printf("%d\r\n", SQUARE(5));
7 printf("%d\r\n", SQUARE(a+1));
8 return 0;
9}
程序运行,第一条打印显而易见为25,第二条打印为多少呢?不是36而是11。我们通过"gcc -E a.c >a.txt",将预处理后的文件重定向到a.txt,来观察被替换的宏文本。
1printf("%d\r\n", 5 * 5);
2printf("%d\r\n", a+1 * a+1);
可见替换产生的表达式并没有按照预想的次序进行求值。可能大家会说,加上两个括号就解决了嘛:
1#define SQUARE(x) (x) * (x)
那么,为每个出现在替换文本中的参数加上括号就一定没问题了吗?看下面例子:
1#include
2#define ADD(x) (x) + (x)
3int main(int argc, char const *argv[])
4{
5 int a = 5;
6 printf("%d\r\n", 10 * ADD(5));
7 return 0;
8}
看似输出100,实际输出55。我们看下替换后的文本:
1printf("%d\r\n", 10 * (5) + (5));
乘法运算在加法运算之前执行,所以结果为55。这个错误很容易修正:整个表达式加上括号:
1#define ADD(x) ((x) + (x))
所有对数值表达式进行求值的宏定义,为避免参数中操作符或邻近的操作符之间不可预料的互相作用,应对每个参数加括号,整个表达式也要加括号。
宏与函数
尽可能多的加括号就绝对不会有问题了吗?看下面例子:
1#include 2#define MAX(a, b) ((a) > (b) ? (a) : (b))3int main(int argc, char const *argv[])4{