在 C/C++ 宏中,##
和 #
是两个特殊的预处理操作符,用于在宏展开时对参数进行字符串化和符号连接。以下是它们的详细用法和区别:
1. #
运算符(字符串化)
作用:将宏参数转换为字符串常量(即添加双引号)。
语法:#参数
示例:
#include <stdio.h>
// 将参数转换为字符串
#define STRINGIFY(x) #x
int main() {
int num = 42;
printf("变量名: %s\n", STRINGIFY(num)); // 输出: 变量名: num
printf("值: %d\n", num); // 输出: 值: 42
return 0;
}
输出:
变量名: num
值: 42
应用场景:
- 在调试时输出变量名和值。
- 动态生成字符串化的参数名称。
2. ##
运算符(符号连接)
作用:将两个符号(Token)拼接为一个新的符号。
语法:符号1 ## 符号2
示例:
#include <stdio.h>
// 拼接变量名
#define CONCAT(a, b) a##b
int main() {
int num1 = 10, num2 = 20;
int num12 = num1 + num2;
// 通过拼接生成变量名 num12
printf("num1 + num2 = %d\n", CONCAT(num, 12)); // 输出: num1 + num2 = 30
return 0;
}
输出:
num1 + num2 = 30
应用场景:
- 动态生成变量名或函数名(例如批量定义结构体字段)。
- 模板化代码,减少重复代码量。
3. #
和 ##
的联合使用
示例:生成带前缀的变量名并字符串化:
#include <stdio.h>
#define VAR_NAME(pre, num) pre##num
#define PRINT_VAR(pre, num) printf("%s = %d\n", #pre #num, VAR_NAME(pre, num))
int main() {
int var_x1 = 100, var_x2 = 200;
PRINT_VAR(var_x, 1); // 展开为: printf("var_x1 = %d\n", var_x1)
PRINT_VAR(var_x, 2); // 展开为: printf("var_x2 = %d\n", var_x2)
return 0;
}
输出:
var_x1 = 100
var_x2 = 200
4. 关键区别
特性 | # 运算符 |
## 运算符 |
---|---|---|
作用 | 将参数转换为字符串常量 | 拼接两个符号为一个新符号 |
输入类型 | 单个宏参数 | 两个符号(可以是参数或字面量) |
输出类型 | 字符串(带双引号) | 符号(无引号) |
常见用途 | 调试输出、字符串生成 | 动态生成变量名、模板化代码 |
5. 注意事项
##
的限制:- 拼接后的符号必须是有效的标识符(如
var_1
),否则会导致编译错误。 - 不能直接拼接字符串字面量(如
"Hello" ## "World"
是非法的)。
- 拼接后的符号必须是有效的标识符(如
宏展开顺序:
- 宏参数在替换前会先展开,但
#
和##
会阻止参数展开。若需要强制展开参数,需使用中间宏。
- 宏参数在替换前会先展开,但
避免歧义:
- 使用括号避免优先级问题,例如
#define CONCAT(a, b) (a##b)
。
- 使用括号避免优先级问题,例如
6. 进阶示例
动态生成枚举和字符串映射
#include <stdio.h>
#define DEFINE_ENUM(name) \
enum name { \
name##_A, \
name##_B, \
name##_C \
}; \
const char* name##_strings[] = { #name "_A", #name "_B", #name "_C" }
DEFINE_ENUM(Color); // 生成 enum Color 和 Color_strings
int main() {
printf("枚举值: %d -> %s\n", Color_A, Color_strings[Color_A]); // 输出: 0 -> Color_A
return 0;
}
总结
#
:将宏参数转换为字符串,用于调试输出或动态字符串生成。##
:拼接符号生成新标识符,适用于模板化代码和减少重复。- 联合使用时,可以实现高度灵活的代码生成(如反射、枚举映射等)。
- 注意宏展开的优先级和符号有效性,避免编译错误。