摘要
本文全面介绍 C++ 宏(Macro)的使用方法,从基础概念到高级技巧。主要内容包括:
- 基础用法:简单替换、多行宏(使用
\续行)、嵌套宏(宏调用宏) - 运算符技巧:
#运算符将参数转为字符串,##运算符拼接标识符 - 高级特性:可变参数宏(Variadic Macros)处理不定数量参数
- 实际应用:通过 BusTub 项目案例展示宏在代码生成、错误处理、日志记录中的实用技巧
- 原理与局限:宏本质是编译前的文本替换,因此不遵守作用域、无法调试、缺乏类型检查
- 最佳实践:优先使用函数、参数加括号、注意分号使用
宏作为"代码生成器",能有效减少样板代码,但需谨慎使用以避免常见陷阱。
1. 最基础 — 简单替换
#define PI 3.14
#define BUSYUB_ASSERT(expr, msg) assert((expr) && (msg))
2. 多行宏 — 用 \ 续行
宏定义可以跨多行,每行末尾加 \:
#define BUSTUB_ENSURE(expr, message) \
if (!(expr)) { \
std::cerr << "ERROR: " << (message) << std::endl; \
std::terminate(); \
}
3. 嵌套宏 — 宏调用宏
DISALLOW_COPY_AND_MOVE 就是嵌套宏:
#define DISALLOW_COPY_AND_MOVE(cname) \
DISALLOW_COPY(cname); \
DISALLOW_MOVE(cname);
外层宏先展开参数 cname,再调用内层宏。这能减少重复代码。
4. # 运算符 — 把参数变成字符串
#define ASSERT_MSG(cond) #cond
ASSERT_MSG(x > 0); // 展开成 "x > 0"
应用:自动生成带变量名的报错。
5. ## 运算符 — 拼接标识符
#define MAKE_GETTER(name) int get_##name() { return name##_; }
struct Foo {
int age_;
MAKE_GETTER(age) // 自动生成 get_age() 函数
};
应用:自动生成重复样板代码。
6. 可变参数宏(Variadic Macros)
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
LOG("name=%s, age=%d\n", "tom", 18);
... 接收可变参数,__VA_ARGS__ 代表所有参数。
7. 为什么宏能做到这些?
宏本质是==编译前的纯文本替换==(发生在预处理阶段,比编译还早)。所以它可以:
- 出现在类、函数能出现的任何地方(包括类内部的
public:段) - 生成任何合法 C++ 代码
- 重复使用同一个名字多次,每次展开不同
8. 宏的缺点(也很重要)
- 不遵守作用域 — 全局生效
- 不调试 — 断点打不到宏里
- 容易出错 — 优先级问题:
#define SQUARE(x) x*x调SQUARE(1+2)得 5,不是 9 - 不类型检查 — 编译器看到的是展开后的代码
9. 最佳实践
- 函数能做的事别用宏
- 宏的每个参数用括号包起来:
#define SQR(x) ((x)*(x)) - 多行宏结尾别漏分号(或者在调用处加分号)
简单说,宏就是“代码生成器”,可以用它来减少样板代码。理解了 DISALLOW_COPY_AND_MOVE 的展开过程,其他宏套路都差不多。