C++ 宏定义使用手册
在 C++ 中,可以通过宏定义命令 #define
简化代码编写过程,为开发提供极大的便利,但初学时我们常常不能理解该命令的本质,导致代码出现逻辑错误或者无法通过编译。本文总结了一些宏定义的常见用法,打算日后偶尔拿来当个字典用用(
宏替换发生的时机
当我们对一些源文件进行编译时,编译器内部实际进行了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输出,它实现了以下的功能:
- 文件包含
将源文件中的 #include
扩展为文件正文,即把包含的 .h 文件找到并展开到 #include
所在位置
- 条件编译
预处理器根据 #if
和 #ifdef
等编译命令及其后的条件,将源文件中的某部分包含进来或排除在外(通常把排除在外的语句转换成空行)
- 宏展开
预处理器将源文件中出现的对宏的引用(即 #define
相关定义)展开成相应的宏定义,由预处理器来完成。在这个阶段只是纯粹的字符串替换与展开,没有进行任何计算,理解了这一点,就能有效避免对 #define
命令的误用
常用宏定义参考
无参宏定义
无参宏的宏名后不带参数,其定义的一般形式为:
1 |
其中的 #
表示这是一条预处理命令。凡是以 #
开头的均为预处理命令。 #define
为宏定义命令。<标识符>
为所定义的宏名。字符串
可以是常数、表达式、格式串等。例如:
1 |
这样 MAXN 就被定义为 23333,在宏展开过程中,会将 MAXN 简单的替换为 23333 进行处理。
有参宏定义
C++ 语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要进行宏展开,而且要用实参去代换形参。带参宏定义的一般形式为:
1 |
在字符串中含有各个形参。在使用时调用带参宏调用的一般形式为:宏名(实参表),例如:
1 |
|
这个「函数」定义了加法,但是没有类型检查,有点类似无模板安全的模板,所以不建议使用这样的方式去定义一个「函数」。
宏定义中的特殊操作符
#
假如希望在字符串中包含宏参数,在类函数宏的替换部分,可以将 #
符号用作一个预处理运算符,它将把语言符号转化成字符串。例如,如果 x
是一个宏参量,那么 #x
可以把参数名转化成相应的字符串。该过程称为字符串化
讲人话,就是相当于在宏参数左右加双引号:
1 |
|
##
不同于 #
的操作方式,##
会把宏参数直接作为代码拼接到字符串中,例如,定义一个 #define conn(a, b) a##b
,在代码中使用 conn(123, 456)
时,最终将被替换为 123456
,或者可以像下面那样去使用:
1 |
|
...
和 __VA_ARGS__
__VA_ARGS__
是一个可变参数的宏,这个宏是新的 C99 规范中新增的,实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏 __VA_ARGS__
就可以被用在替换部分中,替换省略号所代表的字符串。
1 |
|
需要注意的问题
在编写代码的过程中,要时刻记住宏定义只是进行简单的字符串替换,否则就可能出现如下的问题:
1 |
|
编译运行,程序并没有像预料之中的那样输出 16
,而是输出了 8
,因为在宏展开过程中只是简单的进行了字符串替换,这段代码在解析后会变成 2 + 2 * 2 + 2
,导致结果为 8
。解决方法也很简单,在做宏定义时加上括号就可以了
1 |
|
只要做宏定义的时候时刻记住,宏定义只是简单的替换,此时真正的编译过程尚未开始,就不会在这样的问题上出错