20 宏的字符串化与记号拼接
一、核心结论
| 预处理运算符 | 作用 | 例子 | 结果 |
|---|---|---|---|
# | 将宏参数字符串化 | RAW(BLE) | "BLE" |
## | 拼接预处理记号 | JOIN(key, 2) | key2(标识符) |
两者都发生在预处理阶段,不是运行时字符串操作。
##拼接结果必须构成合法的预处理记号。
二、#:将参数变成字符串
#include <stdio.h>
#define RAW(x) #x
int main(void)
{
printf("%s\n", RAW(BLE)); // 输出 BLE
return 0;
}
RAW(BLE) ── # 字符串化 ──> "BLE" ── printf("%s") ──> BLE
字符串字面量 打印内容不带引号
三、##:将两个记号拼成一个
#include <stdio.h>
#define JOIN(a, b) a##b
int main(void)
{
int key2 = 20;
printf("%d\n", JOIN(key, 2)); // 输出 20
return 0;
}
JOIN(key, 2) ── ## 拼接 ──> key2 ── 读取变量值 ──> 20
变量名 整数值
注意: key2 是变量名,不是字符串 "key2"。
四、为什么单层与两层宏结果不同?
#include <stdio.h>
#define VERSION 456
#define RAW(x) #x
#define TEXT(x) RAW(x)
int main(void)
{
printf("%s %s\n", RAW(VERSION), TEXT(VERSION));
return 0;
}
输出:
VERSION 456
展开过程图
单层:RAW(VERSION)
│ 参数直接用于 #,不先展开 VERSION
▼
"VERSION"
两层:TEXT(VERSION)
│ 外层没有直接用 #,参数先展开
▼
RAW(456)
│ 内层再用 # 字符串化
▼
"456"
| 写法 | 预处理结果 | 用途 |
|---|---|---|
RAW(VERSION) | "VERSION" | 需要参数原本的写法 |
TEXT(VERSION) | "456" | 需要宏展开后的内容 |
注意: 这里的“两层宏先展开”适用于上述常见字符串化写法;实际复杂宏还需要遵守预处理器的参数展开与重新扫描规则。
五、蓝牙/TWS 固件中的简单用途
#define FW_VERSION 123
#define RAW(x) #x
#define TEXT(x) RAW(x)
// 将编译期固件版本宏变成日志可打印的字符串:
const char *version_text = TEXT(FW_VERSION); // "123"
与位掩码、结构体布局等不同,这里的结果是在预处理阶段生成的,不需要运行时转换。
六、快速记忆
#= 变字符串:RAW(BLE)→"BLE"。##= 拼记号:JOIN(key, 2)→ 变量名key2。- 直接
#不先展开宏参数:RAW(VERSION)→"VERSION"。 - 外层宏先展开、内层再字符串化:
TEXT(VERSION)→"456"。 printf("%s", "VERSION")输出VERSION,不带引号。
口诀:井号变字符串,双井号拼名字;一层保留写法,两层先展开。
本篇不重复第 12 篇的重复求值、do...while(0)、inline 等内容。