宏定义在预处理阶段纯文本替换,无类型、无作用域、不参与编译检查;const变量具类型和地址,可调试;constexpr是现代C++推荐的编译期常量方案,类型安全且支持编译期计算。

宏定义在预处理阶段就被替换了,根本没机会参与类型检查
预处理器只做纯文本替换,#define PI 3.14159 后,所有 PI 都被无差别替换成 3.14159,连括号都不加。它不占内存、没有作用域、不经过编译器语义分析——所以 sizeof(PI) 是非法的(预处理后变成 sizeof(3.14159),但这是字面量,不是对象),而 auto x = PI; 中的 x 类型由字面量推导为 double,但这个过程发生在替换之后,和宏本身无关。
常见错误现象:
-
#define MAX(a,b) a > b ? a : b导致MAX(i++, j++)展开成i++ > j++ ? i++ : j++,副作用执行两次 -
#define FLAG 1和int FLAG = 2;在同一作用域不报错(宏先被删了),但逻辑混乱 - 调试时无法对宏名设断点,IDE 里看不到“变量”定义位置
const 变量是真正的编译期常量(满足条件时),有类型、有地址、能进符号表
const double PI = 3.14159; 声明了一个具有 double 类型的左值,有内存地址(除非被优化掉),支持取址 &PI,可参与模板推导、用作数组维度(若为字面量常量表达式)、能出现在 switch 的 case 中(前提是是 constexpr 或整型字面量常量表达式)。
但注意:不是所有 const 都是编译期常量:
立即学习“C++免费学习笔记(深入)”;
-
const int x = rand();—— 运行期初始化,只是不可修改,不能用于模板非类型参数 -
const int y = 42;—— 满足常量表达式要求,等价于constexpr int y = 42;(C++17 起隐式 constexpr) -
extern const int z;—— 外部链接,定义在别处,编译器通常无法在本翻译单元确认其值,不能用于需要常量表达式的地方
用 constexpr 替代宏才是现代 C++ 的正确姿势
宏该退场了。真正需要编译期常量的地方,优先用 constexpr:
-
constexpr double PI = 3.1415926535;—— 类型安全、可调试、支持sizeof、能用于所有常量表达式上下文 -
constexpr int square(int x) { return x * x; }—— 函数也能编译期求值,宏函数做不到泛型和重载 -
template—— 模板参数必须是常量表达式,struct Array { int data[N]; }; Array错(PI是double),但Array对(返回int)
宏只剩少数场景还必要:条件编译(#ifdef)、头文件卫士(#pragma once 或 #ifndef XXX_H)、字符串化(#__FILE__)、连接符(# 和 ##)——这些是预处理器专属能力,constexpr 无法替代。
调试和符号信息差异直接影响开发体验
你用 gdb 调试时,print PI 会提示 “no symbol”,因为宏早已消失;而 print PI_const(对应 const double PI_const = ...)能正常显示值、类型、地址。IDE 的跳转、重命名、查找引用等功能对宏完全失效,但对 const 和 constexpr 变量完全支持。
容易被忽略的一点:const 变量默认内部链接(static),而宏没有链接属性概念——这意味着多个源文件里定义同名 const int x = 1; 不会链接冲突,但宏定义重复可能导致意外覆盖或警告。反过来,如果真需要外部链接的编译期常量,得显式写 extern constexpr int x = 1; 并在某处定义。










