第3.14章: 字符串化

English

宏与词法单元操作

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

编译器会在展开之后报错。

安全的使用模式

宏很强大,但应当少用。

要点