C/C++预处理指令#define 之##连接符的用法

本文详细解析了C风格预处理宏中的##连接符用法,并通过实际项目案例展示了如何有效利用宏定义提高代码灵活性。尤其针对带参数的宏定义中##的特殊作用及其带来的挑战给出了具体的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文结合项目实例进行了##连接符用法剖析。


关于C风格的预处理符号定义#define 的用法,很多人已经非常熟悉,我也很乐于使用一个容易记忆的宏定义,代替记忆一串数字。

#define PIE (3.14159265358979323846264338327950288419716939937510582097494459230781640628620899)

在定义这一宏后,程序中仅需引入PIE就不必输入那一串难以记忆的数字了。

对于一般的宏定义的用法很简,没必要多浪费口舌。这里主要介绍##的用法。


宏定义中##的作用是将##两侧的字符连接起来,对于普通的无参数宏定义,##的意义不大,而对于带参数的宏定义,我们则可以巧妙的应用##组合出我们想要的符号。

在介绍##之前,先说一下带参数宏定义的参数:

#define _MYDEFINE(val) int val

_MYDEFINE(x)
这里,val 是宏定义的形参,x是宏定义的实参,上面的_MYDEFINE(x)会被编译成 int x



英文原版宏替换规则
A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token, is replaced by the corresponding argument after all macros contained therein have been expanded.

所以,在使用带参数宏定义中如果出现了##连接符号,那么宏实参将会被视作一个普通符号而不是一个宏,然后开始当前宏的替换操作。
#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端口。

如需更换数据端口,只需简单的更改前两个宏定义就可以实现,可谓方便简单

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值