C 语言的宏系统远不止简单的文本替换。通过高级宏技术,可以实现类型无关的编程、可变参数支持、代码生成等高级功能。本文将详细介绍这些高级技巧。
字符串化运算符 #
# 运算符可以将宏参数转换为字符串字面量:
/* 字符串化运算符示例 */
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
/* 使用 */
printf("%s\n", STRINGIFY(123)); /* 输出: 123 */
printf("%s\n", STRINGIFY(1+2)); /* 输出: 1+2(不展开)*/
printf("%s\n", TOSTRING(1+2)); /* 输出: 3(会展开)*/
/* 日志宏中使用 */
#define LOG(msg) printf("[%s:%d] %s\n", __FILE__, __LINE__, #msg)
连接运算符 ##
## 运算符可以连接两个标记(token):
/* 连接运算符示例 */
#define CONCAT(a, b) a ## b
/* 连接数字和前缀 */
#define VAR(name) CONCAT(var_, name)
int VAR(123); /* 展开为 int var_123; */
/* 创建通用的函数/变量名 */
#define MAKE_GETTER(type, name) \
type CONCAT(get_, name)() { \return name; }
int value = 42;
MAKE_GETTER(int, value); /* 创建 get_value() 函数 */
可变参数宏 ... 和 __VA_ARGS__
C99 引入了可变参数宏的支持:
/* 可变参数宏基础 */
#define LOG(fmt, ...) printf("[LOG] " fmt "\n", ##__VA_ARGS__)
LOG("Hello"); /* 只有一个参数 */
LOG("Value: %d", 42); /* 两个参数 */
LOG("%s = %d", "x", 100); /* 三个参数 */
/* ##__VA_ARGS__ 的作用:处理空参数情况 */
/* 没有 ##,当没有额外参数时会编译错误 */
类型无关的编程
使用宏可以实现真正的类型无关编程:
/* 通用最大值宏 */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/* 通用 swap 宏 - 不限于特定类型 */
#define SWAP(a, b) \
do { \
typeof(a) _a = (a); \
(a) = (b); \
(b) = _a; \
} while(0)
/* 使用示例 */
int x = 10, y = 20;
SWAP(x, y); /* 交换 int */
double p = 3.14, q = 2.71;
SWAP(p, q); /* 交换 double */
do-while(0) 技巧
这个技巧用于确保宏作为单条语句使用:
/* 问题:如果宏包含多条语句,在 if-else 中会出错 */
#define BAD_MACRO(x) \
init(x); \
cleanup(x);
/* 使用时可能出错 */
if (condition)
BAD_MACRO(x); /* 只有 init(x) 在 if 作用域内! */
else
do_something();
/* 解决方案:do-while(0) */
#define GOOD_MACRO(x) \
do { \
init(x); \
cleanup(x); \
} while(0)
/* 现在无论在哪里使用都是安全的 */
if (condition)
GOOD_MACRO(x);
else
do_something();
条件宏
可以根据条件生成不同的代码:
/* 根据操作系统选择不同的代码 */
#if defined(_WIN32)
#define PATH_SEP "\\"
#define sleep(ms) Sleep(ms)
#elif defined(__linux__)
#define PATH_SEP "/"
#include
#endif
/* 编译时计算数组大小 */
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/* 静态断言确保是数组而非指针 */
_Static_assert(ARRAY_SIZE(arr) > 0, "arr must be an array");
注意事项
在使用宏时,应当始终用括号包裹参数和整个表达式,以避免由于运算符优先级导致的问题。使用 do-while(0) 包装多条语句的宏,确保它们在任何上下文中都能安全使用。
总结
C 语言的宏系统是非常强大的工具,掌握这些高级技巧可以让你写出更加简洁和高效的代码。关键要点包括:
- 使用
#进行字符串化,##进行标记连接 - 使用可变参数宏
...__VA_ARGS__处理可变数量的参数 - 使用
do-while(0)确保宏作为单条语句安全执行 - 利用
typeof实现类型无关的通用宏