宏定义仅在预处理阶段机械替换,无类型、作用域和调试信息;const/constexpr 提供类型安全、链接性和编译期常量保证,应优先使用 constexpr 替代宏。

宏定义 #define 根本不参与编译,它只在预处理阶段被机械替换
预处理器根本不知道类型、作用域、语句结构这些概念。它看到 #define PI 3.14159,就不管上下文,把所有裸露的 PI 字符串原样替换成 3.14159,哪怕它出现在字符串字面量里、注释里,甚至变量名的一部分(比如 PI_value)——只要匹配,就可能被误换。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 永远用括号包裹宏的整个值:
#define MAX(a,b) ((a) > (b) ? (a) : (b)),否则MAX(x + 1, y) * 2会展开成x + 1 > y ? x + 1 : y * 2,优先级全乱 - 避免宏名和变量名/函数名重叠,尤其别用常见缩写如
ERR、OK,容易污染全局命名空间 - 宏没有调试信息:GDB 里看不到
PI,只能看到展开后的数字,断点和单步都跳过宏本身
const 变量是真正的编译期常量,但行为取决于初始化方式
如果 const int x = 42;,且 x 在全局或命名空间作用域,它通常会进入符号表,有地址,能取地址,也能用于需要常量表达式的场景(比如数组维度);但如果写成 const int y = some_runtime_func();,那它就只是“只读变量”,不保证编译期可知,也不能当模板非类型参数。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 想确保编译期常量,用
constexpr替代const:constexpr int arr_size = 100;,这样int a[arr_size];才合法 -
const全局变量默认内部链接(static),而#define是无链接的纯文本,所以多个文件里同名const不冲突,同名#define却可能因头文件包含顺序引发意外覆盖 - 类内
static const成员若未定义(只声明),不能取地址;但加constexpr就可以,且无需单独定义
类型安全差异直接决定能否过编译器检查
#define 是纯文本替换,完全绕过类型系统。比如 #define ZERO 0,它可被当 int、double、甚至指针(char* p = ZERO; 在旧标准里居然能过)用,毫无约束。而 const double ZERO = 0.0; 传给 void f(int) 就会触发隐式转换警告(若开启 -Wconversion),传给 void f(std::string&) 则直接报错。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 代替数值宏,优先用
constexpr变量,比如constexpr std::size_t BUFFER_SIZE = 4096; - 代替字符串宏,用
inline constexpr char[] NAME = "mylib";(C++20 起支持 inline 变量,避免 ODR 违规) - 宏适合做条件编译(
#ifdef DEBUG)或 token 拼接(#define CONCAT(a,b) a##b),这些是const做不到的
调试和链接时的行为差异常被忽略
宏没有符号,不会出现在 nm 或 objdump 输出里;const 变量如果有外部链接(比如加了 extern 声明并定义在某处),就会生成符号,能被其他目标文件引用,也能被调试器识别。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 调试时想查某个“常量”的值?如果是宏,你得去头文件翻定义;如果是
const或constexpr,GDB 里直接print MY_CONST就行 - 动态库导出常量:宏无法导出,
const变量需显式加__declspec(dllexport)(MSVC)或__attribute__((visibility("default")))(GCC/Clang)才能被外部看到 - 模板实例化时,
const值可能被内联优化掉,导致符号不可见;而宏因为不生成符号,反而没这问题——但这不是优点,是失控
真正难处理的是宏和 const 混用的边界情况:比如头文件里用 #define 控制是否启用某功能,但实现里又依赖一个 const 数组大小——这时预处理和编译阶段的视角错位,很容易出现“定义了宏却报数组越界”之类的问题。得盯住预处理后的代码(g++ -E)才能看清真相。









