本文结合项目实例进行了##连接符用法剖析。
关于C风格的预处理符号定义#define 的用法,很多人已经非常熟悉,我也很乐于使用一个容易记忆的宏定义,代替记忆一串数字。
#define PIE (3.14159265358979323846264338327950288419716939937510582097494459230781640628620899)
在定义这一宏后,程序中仅需引入PIE就不必输入那一串难以记忆的数字了。
对于一般的宏定义的用法很简,没必要多浪费口舌。这里主要介绍##的用法。
宏定义中##的作用是将##两侧的字符连接起来,对于普通的无参数宏定义,##的意义不大,而对于带参数的宏定义,我们则可以巧妙的应用##组合出我们想要的符号。
在介绍##之前,先说一下带参数宏定义的参数:
#define _MYDEFINE(val) int val
_MYDEFINE(x)
这里,val 是宏定义的形参,x是宏定义的实参,上面的_MYDEFINE(x)会被编译成 int x
#define _MY_CONNECT (wparam,lparam) wparam##lparam
如面的一行代码,编译时会将两个参数连接到一起,如
_MY_CONNECT(VISUAL,STUDIO)
将会被编译器视为VISUALSTUDIO
当实参为宏时
<pre name="code" class="html">#define _MY_CONNECT(parama,paramb) parama##paramb
#define _MY_SYMBLE 0xcc
#define _MY_CHANGE(a) _change##a
例如已定义上面的3个宏
此时如果使用_MY_CONNECT(_MY_SYMBLE,_MY_CHANGE(_value)),
那么编译器最终会将其处理成_MY_SYMBLE_MY_CHANGE(_value)
而不是预期的0xcc_change_value
这是因为_MY_CONNECT中存在了##连接符,这导致该宏的实参全部被视为了普通的符号,导致这些参数宏全部失效。
解决方案:在向带##连接号的宏传参数之前,就将要传递的实参替换完毕,可以有效的解决这一问题。
具体方法:定义中间宏,通过此宏先让宏实参完成替换,然后在将替换后的宏传入带##连接符的宏当中。
<pre name="code" class="html">#define __MY_CONNECT(parama,paramb) _MY_CONNECT(parama,paramb)
#define _MY_CONNECT(parama,paramb) parama##paramb
#define _MY_SYMBLE 0xcc
#define _MY_CHANGE(a) _change##a
使用__MY_CONNECT(_MY_SUMBLE,_MY_CHANGE(_value)),此时编译器将会替换成为0xcc_change_value
编译器替换步骤如下:
1)因为__MY_CONNECT(parama,paramb)中不存在##连接符,所以展开所有宏参数
即__MY_CONNECT(_MY_SUMBLE,_MY_CHANGE(_value))被转换成_MY_CONNECT(0xcc,_change_value)
2)此时直接按照_MY_CONNECT(parama,paramb)展开,
即_MY_CONNECT(0xcc,_change_value)被替换为0xcc_change_value
总结:
使用带参数宏定义时,如果该定义中存在##连接符,那么最好需要同时为此宏定义一个中间转换宏,通过调用中间转换宏,可以支持宏实参,从而实现宏定义的最大兼容。
最后加入应用项目实例:
笔者编写了一个AVR的SDI12数据串行通讯协议的IO端口驱动。为了实现方便的端口设置,笔者选择了宏定义的方式,代码如下:
#include <avr/io.h>
#define SDI12_PORTX D
#define SDI12_PINXN 4
#define __SDI12_PIN(val) PORT##val
#define __SDI12_PORT(val) PORT##val
#define __SDI12_DDR(val) PORT##val
#define _SDI12_PIN(val) __SDI12_PIN(val)
#define _SDI12_PORT(val) __SDI12_PORT(val)
#define _SDI12_DDR(val) __SDI12_DDR(val)
#define SDI12_PIN _SDI12_PIN(SDI12_PORTX)
#define SDI12_PORT _SDI12_PORT(SDI12_PORTX)
#define SDI12_DDR _SDI12_DDR(SDI12_PORTX)
#define SETPORT SDI12_PORT |= 0x01<<SDI12_PINXN
#define CLRPORT SDI12_PORT &= ~(0x01<<SDI12_PINXN)
#define GETPORT (SDI12_PIN&(0x01<<SDI12_PINXN))
#define ENPORT SDI12_DDR |= 0x01<<SDI12_PINXN
#define UNPORT SDI12_DDR &= ~(0x01<<SDI12_PINXN)
前两行的SDI12_PORTX与SDI12_PINXN分别对应了SDI12 数据总线的端口号及管脚号,笔者的项目中采用的是PORTD.4端口。
如需更换数据端口,只需简单的更改前两个宏定义就可以实现,可谓方便简单