(一)符号常量
宏定义是C语言中的一种替换策略,即使用预处理命令 #define 将一串(冗长的)文本与某个名字(称为宏)等同起来,然后就可以在源代码中批量使用宏。在预处理阶段再将源代码中的宏替换为原来的文本。例如,在源代码中:
#define PI 3.14
那么在接下来的代码中,需要写3.14的地方可以直接用PI代替。预处理的时候,PI又全部变回3.14。
这样换来换去的有啥好处呢?万一代码的中的3.14需要全部改为3.1415926,那么如果没有刚才的宏定义,就只能挨个去修改;但是有了宏定义的话,只需修改宏定义就好了:
#define PI 3.1415926
可以看出,每个#define命令由三部分组成,分别是:#define命令本身;缩略语(宏);替换文本或主体。各个部分之间用空格隔开,所以宏名称中不能存在空格,且必须遵循C变量命名规则。宏名称一般使用大写字母。
预处理中,从宏变回主体的过程成为宏展开。有些宏定义的主体比较长,可以在行尾使用反斜线“\”将剩余的部分延伸到下一行(但是注意第二行如果没有左对齐,那么开头的那些空白也会被当成主体的一部分)。
主体部分可以是常量,也可以是C表达式,甚至是一条完整的语句(即带有分号),总之可以是任意字符串。这里需要注意的是,如果主体中仍含有宏名称,则该宏也会被替换;但是如果这个宏名在主体中被双引号括起来,那么就不会发生宏替换了,只会被按照字面意思理解。
(二)类函数宏
宏定义分为两种,上面提到的都是不带参数的,称为类对象宏,这种宏也被称为符号常量;另外还有一种带参数的,称为类函数宏。例如:
#define FUN(X) X * X
类函数宏的外形与函数十分相似,也是在名称后面紧跟一对圆括号,然后参数列表置于括号中。然后其主体就是这些参数的某种运算规则。在使用这个宏时,X可以被其他字符替换,就是函数的参数一样,X起到的也是参数的作用。
代码中这样的代码:
x = FUN(4);
会被替换成:
x= 4 * 4;
可能有点绕,但是我个人把这个宏展开的过程分为两步理解:首先,把FUN(4)直接替换成X * X;然后再用“实参”4代替“形参”X(不知道机器是不是这样执行的,会不会是偶的创新?)。
但是对类函数宏要有足够的警惕:预处理器在宏展开时,仅仅进行文字替换操作,而不是真的像函数传参那样。比如刚才那个宏这样使用:
x = FUN(3 + 4);
我们期望会替换成:
x = 7 * 7;
可实际上是:
x = 3 + 4 * 3 + 4;
由于结合顺序的原因,我们不会得到想要的结果。避免出现这种结果的办法就是:定义类函数宏时,最好给每个出现在主体中的参数加上括号,同时给每个运算规则也用括号保护起来。如,FUN应该这样定义:
#define FUN(X) ((X) * (X))
那么替换之后就是这样:
x = ((3 + 4) * (3 + 4))
这样看似麻烦啰嗦,但却是避免意外的好办法。
下面是搬运时间(人家写的太精彩了,我就可耻的偷把懒~,摘自《Linux C一站式编程》)
函数式宏定义经常写成这样的形式(取自内核代码 include/linux/pm.h ): #define device_init_wakeup(dev,val) do { device_can_wakeup(dev) = !!(val); device_set_wakeup_enable(dev,val); } while(0) 为什么要用 do { ... } while(0) 括起来呢?不括起来会有什么问题呢? #define device_init_wakeup(dev,val) device_can_wakeup(dev) = !!(val); device_set_wakeup_enable(dev,val); if (n > 0) device_init_wakeup(d, v); 这样宏展开之后,函数体的第二条语句不在 if 条件中。那么简单地用 { ... } 括起来组成一个语 句块不行吗? #define device_init_wakeup(dev,val) { device_can_wakeup(dev) = !!(val); device_set_wakeup_enable(dev,val); } if (n > 0) device_init_wakeup(d, v); else
continue; 问题出在 device_init_wakeup(d, v); 末尾的 ; 号,如果不允许写这个 ; 号,看起来不像个函数调用,可如果写了这个 ; 号,宏展开之后就有语法错误, if 语句被这个 ; 号结束掉了,没法 跟 else 配对。因此, do { ... } while(0) 是一种比较好的解决办法。
然后我做点补充,do { ... } while(0) 这种形式到底怎么运行?我们知道,这种do...while()循环称为退出条件循环,判断条件在执行循环之后进行检查,这样就可以保证循环体中的语句至少被执行一次,而且这里的while条件被置为0,也就是说循环体恰好只执行一次,真是聪明的办法!!
(三)#与##运算符
写到这里真的有点撑不住,妹的C语言也真是博大精深,一个宏定义都要搞得这么复杂。。。
上面提到过,主体中双引号内的宏名称不会发生替换而是被当做普通文本,那如果非得要它发生替换呢?(话说怎么会有这么二的需求?)办法就是在这个宏名称前面加一个#符号,术语叫做:字符串化 ( stringizing )。
注意:这个符号仅仅用于类函数宏中的参数身上,即类对象宏中这种用法不起作用(我试过了,是真的)。
##运算符把两个语言符号组合成单个语言符号,但是它还可用于类对象宏的替换部分,被称作预处理器的粘合剂。在我看来,它的作用就是给对象(变量或函数)起名时用的,比如我们经常会给变量起x1 x2 x3之类的名字,这类名字的特征就是一部分不变而另一部分变。栗子:
#define XNAME(n) x ## n
然后
<span style="font-size:18px;">int XNAME (1) = 14;</span>
将被替换成:
int x1 = 14:
就酱,全篇完。