C语言笔记之宏定义

(一)符号常量

宏定义是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
然后

int XNAME (1) = 14;
将被替换成:

int  x1 = 14:

就酱,全篇完。


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值