第3.14章: 字符串化
宏与词法单元操作
C++ 预处理器提供了两个用于操作词法单元的特殊运算符:
#—— 字符串化##—— 词法单元拼接
这些运算符只在 #define 宏内部有效。它们很
强大,但必须谨慎使用。
字符串化:#
# 运算符把宏参数转换为字符串
字面量。
#define STR(x) #x
println(STR(hello world));
输出:
hello world
参数不会被求值——它按原样变成文本。
示例:打印表达式
#define SHOW(expr) println(#expr, " = ", (expr))
SHOW(2 + 3);
SHOW(10 * 20);
输出:
2 + 3 = 5
10 * 20 = 200
词法单元拼接:##
## 运算符把两个词法单元拼接成一个。
#define MAKE_NAME(prefix, id) prefix##id
int MAKE_NAME(var, 1) = 42; // becomes: int var1 = 42;
词法单元拼接常用于生成变量名或函数名。
示例:生成枚举到字符串的辅助函数
#define CASE_TO_STRING(x) case x: return #x;
switch (c) {
CASE_TO_STRING(red)
CASE_TO_STRING(green)
CASE_TO_STRING(blue)
}
组合使用 # 与 ##
你可以把两个运算符结合起来,构建更复杂的宏。
#define MAKE_GETTER(name) \
int get_##name() { \
println("getting " #name); \
return name; \
}
int value = 123;
MAKE_GETTER(value)
展开为:
int get_value() {
println("getting " "value");
return value;
}
宏的陷阱
1. 没有类型检查
宏操作的是文本,而不是类型。错误可能只在展开之后才出现。
2. 副作用
宏可能对实参求值多次。
#define SQR(x) ((x) * (x))
SQR(++i); // expands to ((++i) * (++i))
这会让 i 自增两次——几乎从来不是本意。
3. 难以调试
调试器显示的是展开后的代码,而不是宏定义。
4. 词法单元拼接可能产生非法代码
#define BAD(x) x##123abc // invalid token
编译器会在展开之后报错。
安全的使用模式
- 给宏参数加上圆括号。
- 用
#编写调试辅助工具。 - 只在必要时使用
##。 - 现代 C++ 中优先使用
constexpr与模板。
宏很强大,但应当少用。
要点
- # 把宏参数变成字符串字面量。
- ## 把词法单元拼接在一起。
- 宏操作的是文本,不是类型。
- 副作用与多次求值是常见陷阱。
- 尽可能优先使用现代 C++ 特性。