C++ 宏定义(#define)详解
宏(macro)是 C/C++ 预处理器提供的文本替换机制,在正式编译之前由预处理器完成替换。本文介绍对象式宏、条件编译、宏函数、# 与 ## 运算符,以及预定义宏,并说明现代 C++ 中宏的替代方案。
一、宏的本质
宏不是变量也不是函数,而是编译前的文本替换。比如:
1 |
预处理阶段,代码里所有的 NUM 都会被原样替换成 123,然后才进入编译。理解这一点是看懂宏所有「坑」的前提。
二、对象式宏(不带参数)
1 |
- 宏名通常全大写,和普通变量区分(约定俗成,非语法要求);
- 定义行末尾不要加分号——分号会被一起替换进去,多数场景下多余甚至出错;
- 宏没有类型,不做类型检查,只是纯文本。
现代 C++ 中,常量优先用 const / constexpr:
1 | constexpr int NUM = 123; // 有类型、有作用域、编译器会检查,更安全 |
三、条件编译
条件编译指令根据「宏是否被定义」决定某段代码是否参与编译。
3.1 判断宏是否定义:#ifdef / #ifndef
1 |
配套指令:
#ifdef X:X 已定义时编译;#ifndef X:X 未定义时编译(常用于头文件保护);#else/#elif:分支;#endif:结束,每个#if都必须有对应结尾。
3.2 按宏的值做比较:#if(注意不是 #ifdef)
⚠️
#ifdef只能判断「是否定义」,不能写#ifdef NUM > 10这种表达式,预处理器不支持这种语法。
要按值比较必须用 #if:
1 |
|
判断「已定义且满足条件」可以组合 defined():
1 |
|
3.3 头文件保护
防止头文件被重复包含:
1 | // 老式写法 |
1 | // 现代编译器通用写法,等价且更简洁 |
四、宏函数(带参数的宏)
语法:
1 | #define 标识符(参数列表) 替换表达式 |
4.1 括号陷阱
1 |
宏是文本替换,调用 MUL(3, 1+1) 会变成:
1 | 3 * 1 + 1 // 结果是 4,不是预期的 6! |
给每个参数和整个表达式都加上括号即可:
1 |
现在 MUL(3, 1+1) 展开为 ((3) * (1+1)) = 6。
4.2 参数被重复求值的副作用
宏没有函数调用机制,参数可能在展开后出现多次:
1 |
|
这是宏函数最危险的坑之一。同样的问题用内联函数则完全不会发生。
4.3 用 inline 函数替代
C++ 中,类型相关的「宏函数」应优先用 inline 函数:
1 | inline int mul(int a, int b) { |
内联函数有类型检查、参数只求值一次、遵循作用域规则,还能被调试器单步,远比宏安全。现代 C++ 的经验法则是:能用 constexpr / inline / 模板解决的,就不要用宏。宏主要保留给条件编译、头文件保护、日志中需要 #/## 的场景。
五、# 运算符:字符串化
#参数 会把传入的实参转成字符串字面量:
1 |
|
PRINT(a * 2 + 3) 展开后相当于:
1 | cout << "a * 2 + 3" << " = " << (a * 2 + 3) << endl; |
输出:
1 | a * 2 + 3 = 9 |
常用于调试日志——同时打印「表达式文本」和「计算结果」。
六、## 运算符:标记连接
## 把左右两个标记(token)拼接成一个新标记:
1 |
|
注意连接的是标识符片段,不是字符串;拼接结果必须是合法的标记。常用于批量生成变量名、函数名。
七、预定义宏
编译器内置了一组宏(注意两边都是双下划线):
| 宏 | 示例 | 说明 |
|---|---|---|
__FILE__ |
"main.cpp" |
当前源文件名(字符串) |
__LINE__ |
42 |
当前行号(整数) |
__DATE__ |
"Sep 23 2026" |
编译日期 |
__TIME__ |
"10:30:00" |
编译时间 |
__cplusplus |
201703L |
C++ 标准版本号,C 编译器中不定义 |
一个简单的调试打印宏:
1 |
利用 __cplusplus 还可以区分当前按 C 还是 C++、按哪个标准编译:
1 |
|
八、小结
- 宏是预处理阶段的文本替换,没有类型、不做检查;
- 对象式宏行尾不要加分号,常量优先用
const/constexpr; #ifdef只判断「是否定义」,比较值要用#if+defined();头文件用#ifndef或#pragma once防重复包含;- 宏函数要给参数和整体都加括号,但仍有副作用参数重复求值的致命坑,能用
inline就不用宏; #把参数字符串化,##拼接标记;- 预定义宏
__FILE__、__LINE__、__DATE__、__TIME__、__cplusplus两边都是双下划线。