将宏参数原样转为字符串字面量,不展开、不求值;## 拼接两个标记为新标识符,拼接前各自先展开。二者常组合用于调试宏,需注意展开顺序与空参数处理。
# 用来把宏参数转成字符串字面量它只在宏定义内部有效,作用是把传入的**预处理标记(token)原样包裹成双引号字符串**,不是运行时转换,也不做类型检查或求值。
常见错误是以为 # 能处理变量名以外的东西,比如表达式或带空格的片段——它会直接报错或拼出非法字符串。
# 后必须紧跟宏参数名,不能加空格,如 #x 合法,# x 非法#define STR(x) #x #define X 123 STR(X) // 展开为 "X",不是 "123" STR(abc) // 展开为 "abc" STR(int a) // 展开为 "int a" —— 注意空格也被保留
## 用于拼接两个预处理标记它把左右两边的 token 合并成一个新的合法标识符(identifier)

## 两端不能是纯数字或含运算符)。
容易忽略的是:## 两侧 token 在拼接前**各自已完成宏展开**(除非被 # 或另一个 ## 阻止),这点和 # 行为不同。
##x 或 x## 非法## 实现条件拼接(例如可选后缀)#define CONCAT(a, b) a##b #define PREFIX ver CONCAT(PREFIX, 2) // 展开为 ver2 CONCAT(ver, 2) // 同样是 ver2 #define EMPTY CONCAT(foo, EMPTY) // 展开为 foo(EMPTY 展开为空,## 消除空隙)
# 和 ## 实现调试宏真实项目里它们常一起出现,比如打印变量名和值:# 把参数名变字符串,## 拼出临时变量避免命名冲突,或区分不同作用域的同名变量。
最典型的坑是“展开顺序”:想让参数先展开再字符串化,得套一层宏;否则直接 #x 拿到的是原始形参名。
STR(x) → STR_IMPL(x) → #x
## 在宏参数为空时可能产生孤立的 ##,GCC/Clang 支持 ##__VA_ARGS__ 消除逗号,但标准 C++ 中需谨慎printf 的格式串——那是运行时行为,预处理器不参与#define STR_IMPL(x) #x #define STR(x) STR_IMPL(x) #define VAR_NAME(x) x##_t STR(STR) // "STR",不是 "STR_IMPL" VAR_NAME(int) // int_t
# 和 ## 尤其受限它们只在翻译阶段第 4 步(宏替换)起作用,无法访问类型信息、作用域或模板实例化结果。现代 C++ 更推荐 constexpr、模板别名或属性(如 [[maybe_unused]])替代部分宏用途。
真正难调试的不是语法错误,而是展开后语义突变:比如拼出的函数名在链接时找不到,或字符串化后的名字和实际变量名大小写不一致,导致断点失效或日志误导。