
一、基本概念#define是预处理指令在编译前由预处理器处理负责文本替换、条件编译等不经过编译器语法检查不分配内存也不属于 C 的类型系统。简单来说它就像一个“高级查找替换”工具。编译器正式编译前预处理器会扫描代码将宏名原封不动地替换为对应的替换文本。编译流程源代码 → 预处理 → 编译 → 汇编 → 链接 → 可执行文件预处理器的工作特点特性说明纯文本替换不理解C语法只做字符串替换顺序扫描从上到下遇到宏就展开递归展开展开后的代码可能包含其他宏继续展开不做计算不执行任何运算只做文本处理1.1引出案例//自定义异常处理 #define DSSJ_RE1_CALL(RE_CODE)\ do{\ switch (RE_CODE)\ {\ case DhSysSetJson::DhSysSetIO::UnErr: break;\ case DhSysSetJson::DhSysSetIO::ReadErr: {hWarning 读取配置文件错误,文件不存在或打不开ln; return ;}\ case DhSysSetJson::DhSysSetIO::WriteErr: {hWarning 写入错误,文件路径错误或被占用或文件只读 ln;return;}\ case DhSysSetJson::DhSysSetIO::PaseJsonErr: {hWarning 解析错误,非JSON格式文本 ln;return;}\ case DhSysSetJson::DhSysSetIO::ParseKeyErr: {hWarning 解析错误,JSON字符串和对应的类变量格式对不上,找不到对应的键 ln;return;}\ case DhSysSetJson::DhSysSetIO::ParseKeyTypeErr: {hWarning 解析错误JSON字符串和对应的类变量格式对不上,键类型对不上 ln;return;}\ case DhSysSetJson::DhSysSetIO::AcceptErr:{hWarning 序列化错误,类变量格式和指定的JSON格式对不上 ln; return;}\ default: {hWarning 未知错误 ln; return;}\ }\ }while(false)1.2案例分析\是行续接符用于将多行代码连接成一行逻辑语句。1.2.1作用C/C 预处理器会将\结尾的多行视为一行完整的宏定义。1.2.2原因这是由C/C预处理器的设计特性决定的1. 预处理器的工作方式预处理器按换行符识别每条指令的结束遇到换行就认为当前指令结束\的作用是取消换行符的结束含义告诉预处理器下一行是本行的继续2. 历史原因C语言诞生于1970年代当时的编程环境如穿孔卡片、终端按行处理文本为了兼容这种行模式需要一种机制表示逻辑行跨越多个物理行3. 实际效果对比// 预处理器视角 #define A 1 // 第1行结束宏A定义为1 #define B 2 // 第2行开始宏B定义为2 #define C 1 \ // 第1行未结束继续读第2行 2 // 预处理器合并为 1 2这不是有效语句4. 关键约束续行符只能在预处理器指令内使用不能跨越多个预处理器指令不能续接注释或字符串外的部分本质上这是一个兼容性设计让旧的单行文本处理方式能支持多行宏定义。二、对象宏定义常量或文本片段#define PI 3.14159 #define VERSION 1.0.0 #define MAX_SIZE 1024展开方式直接文本替换int r 5; double area PI * r * r; // 展开为: 3.14159 * 5 * 5三、函数式宏Function-like Macro3.1 基本形式#define SQUARE(x) ((x) * (x))#define MAX(a, b) ((a) (b) ? (a) : (b)) //多参数#define WARNING() do { printf(Warning!\n); } while(0) //无参数3.2宏函数与其他函数比较特性宏函数内联函数普通函数类型检查❌ 无✅ 有✅ 有调试友好❌ 难✅ 好✅ 好性能✅ 最快✅ 快需调用开销参数类型任意固定固定作用域全局受限受限递归不支持可内联递归支持泛型✅❌✅适用范围简单表达式简单逻辑复杂泛型展开时机预处理编译编译3.3宏函数优势优势1零调用开销// 宏函数 #define MAX(a, b) ((a) (b) ? (a) : (b)) int x MAX(a, b); // 展开后直接计算无函数调用// 内联函数 inline int max(int a, int b) { return a b ? a : b; } // 问题类型固定只能处理int // 如果传入其他类型需要重载或模板优势2类型无关泛型能力// 宏函数任何类型都适用 #define MAX(a, b) ((a) (b) ? (a) : (b)) int i MAX(1, 2); double d MAX(1.5, 2.5); char c MAX(a, b);// 函数需要为每种类型写一个重载或模板 inline int max(int a, int b) { return a b ? a : b; } inline double max(double a, double b) { return a b ? a : b; } // ... 需要写很多重载优势3不会有参数求值副作用问题正确写法时// 宏函数 #define MAX(a, b) ({ \ __typeof__(a) _a (a); \ __typeof__(b) _b (b); \ _a _b ? _a : _b; \ }) int x 5, y 10; int m MAX(x, y); // x, y 只求值一次优势4在编译前就可以决定行为#define DEBUG_MODE 1 #if DEBUG_MODE #define LOG(x) cout x endl #else #define LOG(x) // 空操作 #endif // 编译前LOG完全不存在编译器看不到它3.4宏函数劣势劣势1无类型检查风险高#define SQUARE(x) ((x) * (x)) int a SQUARE(hello); // ❌ 编译器不报错预处理却可能出问题劣势2调试困难#define SQUARE(x) ((x) * (x)) int a SQUARE(5 3); // 调试时看到的是展开后的代码不是宏 // 断点打在宏上实际断在展开后的位置劣势3可能产生重复副作用#define SQUARE(x) ((x) * (x)) int a 1; int b SQUARE(a); // ❌ a被自增两次 // 展开为: ((a) * (a))3.5实际选择建议// ✅ 用宏函数的情况 // 1. 简单的类型无关操作 #define MAX(a, b) ((a) (b) ? (a) : (b)) #define SWAP(a, b) do { typeof(a) _t (a); (a) (b); (b) _t; } while(0) // 2. 编译时条件代码 #define UNUSED(x) ((void)(x)) // 3. 必须在预处理阶段决策的逻辑 #ifdef DEBUG #define ASSERT(x) if(!(x)) abort() #else #define ASSERT(x) #endif // ✅ 用内联函数的情况 // 1. 需要类型安全 inline int max_int(int a, int b) { return a b ? a : b; } // 2. 复杂逻辑需要调试 inline double calculate(double x) { double result x * x 2 * x 1; return result; // 可以打断点调试 } // ✅ 用模板函数的情况 // 1. 需要类型安全 泛型 templatetypename T T max_t(T a, T b) { return a b ? a : b; } // ✅ 用成员函数的情况 // 1. 与对象状态相关 class Buffer { public: void clear() { memset(data_, 0, size_); } };四、字符串化操作符#将宏参数转换为字符串#define STRINGIFY(x) #x int a 10; printf(STRINGIFY(a)); // 输出: a printf(STRINGIFY(123)); // 输出: 123原理STRINGIFY(a) → #a → a STRINGIFY(11) → 11 // 注意不会计算结果五、连接操作符##连接两个标记Token#define CONCAT(a, b) a ## b int xy 100; printf(%d\n, CONCAT(x, y)); // 输出: 100 // x ## y → xy → 变量xy应用场景生成变量名、函数名#define GENERATE_VAR(prefix, num) prefix ## num GENERATE_VAR(data, 1); // 生成 data1 GENERATE_VAR(data, 2); // 生成 data2六、可变参数宏...支持任意数量的参数#define LOG(fmt, ...) printf([LOG] fmt \n, __VA_ARGS__) LOG(Value is %d, 42); // [LOG] Value is 42 LOG(Hello); // [LOG] Hello命名可变参数#define LOG(fmt, args...) printf([LOG] fmt \n, ##args)七、条件编译7.1 基本结构#define DEBUG 1 #if DEBUG 1 printf(Debug mode\n); #else printf(Release mode\n); #endif7.2 防止重复包含Include Guard// file.h #ifndef _FILE_H_ #define _FILE_H_ // 头文件内容 #endif // _FILE_H_7.3defined操作符#if defined(DEBUG) DEBUG 0 // 调试代码 #endif八、宏运算符defined#define FEATURE_X 1 #if defined(FEATURE_X) // 等价于 #ifdef FEATURE_X // 启用特性X #endif九、#error 和 #pragma// 编译时错误提示 #if __STDC_VERSION__ 201112L #error C11 or later required #endif // 编译器特定指令 #pragma once // 只包含一次部分编译器支持 #pragma pack(1) // 字节对齐十、高级用法10.1 do-while(0) 技巧// 确保宏像单条语句一样使用 #define SAFE_FREE(p) do { free(p); p NULL; } while(0) // 使用 if (ptr ! NULL) SAFE_FREE(ptr); // 正确只执行一条语句 else printf(NULL pointer);do-while(false)的核心作用解决分号;的歧义问题。问题演示如下1.假设没有do-while(false)#define SAFE_FREE(p) free(p); p NULL;场景1单独使用 ✅SAFE_FREE(ptr); // 展开为: free(ptr); p NULL;;看起来没问题但多了一个分号。场景2用在 if 语句中 ❌ 严重Bugif (ptr ! NULL) SAFE_FREE(ptr); // 用户加了分号 else printf(Already NULL);展开后变成if (ptr ! NULL) free(ptr); p NULL;; // 分号断开了if语句! else // else没有对应的if报错 printf(Already NULL);2.加上 do-while(false) 后#define SAFE_FREE(p) do { free(p); p NULL; } while(0)场景1单独使用 ✅SAFE_FREE(ptr); // 展开为: do { free(ptr); p NULL; } while(0);完美执行分号加在整个语句后面。场景2用在 if 语句中 ✅if (ptr ! NULL) SAFE_FREE(ptr); // 用户加了分号 else printf(Already NULL);展开后变成if (ptr ! NULL) do { free(ptr); p NULL; } while(0); // 整体是一个语句 else printf(Already NULL);else正确绑定到if代码正常工作。图解对比不用 do-while: if (cond) macro; ← ; 让if结束 else ← else孤立语法错误 ... 用 do-while: if (cond) do { ... } while(0); ← ; 加在整个循环后面 else ← else正确绑定 ...为什么不用{}直接包起来#define SAFE_FREE(p) { free(p); p NULL; }if (ptr ! NULL) SAFE_FREE(ptr); // 用户加分号 else printf(...);展开后if (ptr ! NULL) { free(ptr); p NULL; }; // ; 导致if结束 else // else孤立同样有问3.总结方案单独使用if中使用if-else中使用不用包裹✅❌❌用{}✅❌❌do-while(0)✅✅✅do-while(false) 是唯一能完美解决分号问题的结构整体是一个语句可以加分号保证代码块只执行一次兼容所有 C 语法场景10.2 宏代替小函数内联优化。#define IS_EVEN(x) (((x) % 2) 0) if (IS_EVEN(10)) // 比函数调用更快 printf(Even);10.3 类型无关宏#define MAX(type) type max_##type(type a, type b) { \ return a b ? a : b; \ } MAX(int) // 生成 max_int(int a, int b) MAX(float) // 生成 max_float(float a, float b)十一、常见陷阱与最佳实践11.1 括号的重要性// 错误写法 #define SQUARE(x) x * x SQUARE(31); // 展开为 31*31 7 (错误!) // 正确写法 #define SQUARE(x) ((x) * (x)) SQUARE(31); // 展开为 ((31) * (31)) 1611.2 参数重复求值#define MAX(a, b) ((a) (b) ? (a) : (b)) int x 5, y 10; MAX(x, y); // 展开后 x和y可能被多次自增11.3 副作用#define ABS(x) ((x) 0 ? -(x) : (x)) int a -5; ABS(a); // 危险a可能被自增多次十二、完整示例// debug.h #ifndef _DEBUG_H_ #define _DEBUG_H_ #define VERSION 2.0 // 日志宏 #define LOG_INFO(fmt, ...) printf([INFO] fmt \n, ##__VA_ARGS__) #define LOG_ERROR(fmt, ...) printf([ERROR] fmt \n, ##__VA_ARGS__) // 调试开关 #ifdef DEBUG_MODE #define DEBUG_LOG(fmt, ...) printf([DEBUG] fmt \n, ##__VA_ARGS__) #else #define DEBUG_LOG(fmt, ...) #endif // 通用工具宏 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define MIN(a, b) ((a) (b) ? (a) : (b)) #define MAX(a, b) ((a) (b) ? (a) : (b)) // 禁用拷贝构造和赋值 #define DISABLE_COPY(Class) \ Class(const Class) delete; \ Class operator(const Class) delete; #endif十三、总结对比类型语法用途常量宏#define X value定义常量函数宏#define F(x) expr内联代码模板字符串化#define S(x) #x转参数为字符串连接符#define C(a,b) a##b连接标记可变参#define V(...) expr可变参数支持条件编译#if/#ifdef编译时选择#define的本质是纯文本替换理解这一点就能掌握其所有用法。