宏定义无类型、仅文本替换,const有类型检查并进入符号表;constexpr是编译期常量首选,宏仅适用于条件编译等特殊场景。
#define 没有类型,const 有类型检查这是最根本的区别。#define PI 3.14159 只是文本替换,编译器在预处理阶段就把它替换成字面量,后续完全不感知“PI”是个什么类型;而 const double PI = 3.14159; 会进入符号表,类型是 double,参与类型推导、重载决议、模板实参匹配等。
常见错误现象:
#define MAX(a,b) ((a)>(b)?(a):(b)) 时,MAX(i++, j++) 会导致 i 或 j 自增两次 —— 因为宏展开后变成 ((i++)>(j++)?(i++):(j++))
const 变量不会出现这类副作用,且支持取地址(&PI 合法),#define 宏不能取地址const 可以在类内声明并初始化,#define 不能替代类内常量成员比如想定义一个类的整型常量用于数组维度或模板参数:
class A {
static const int N = 10; // ✅ 合法(C++11 起允许内联初始化)
int arr[N]; // ✅ 编译期可知
};
而 #define N 10 是全局的,污染命名空间,且无法限定作用域。若用 static const int N; 不初始化,则必须在类外定义(const int A::N;),但 C++11 后基本不需要这么写。
注意:只有 integral type(如 int、enum)的 static const 才能在类内初始化并用于常量表达式;double 或自定义类型不行(除非是 constexpr)。
constexpr,不是 #define 也不是普通 const
比如模板非类型参数、case 标签、数组大小等场景,要求必须是编译期常量:
const int x = 42; 在 C++11 前不一定被当作编译期常
constexpr int y = 42; 明确承诺可求值于编译期,且类型安全、可调试、可取地址#define Y 42 虽然也能用,但绕过所有类型和作用域检查,调试器看不到 Y,IDE 无法跳转,重构工具无法识别所以现代 C++ 中,该用编译期常量的地方,constexpr 是事实标准;宏只保留在极少数场景:条件编译(#ifdef)、拼接 token(##)、字符串化(#)、跨翻译单元的配置开关(如 DEBUG)。
const/constexpr 更友好调试器通常无法显示宏定义的值(GDB/LLDB 看不到 #define 符号),断点打在宏调用处可能跳转错行;而 const 和 constexpr 变量在调试信息中完整保留名称、类型、值。
IDE(如 CLion、VS2025)对宏的支持有限:无法重命名、无法查找引用、无法显示类型提示。但对 constexpr int MAX_SIZE = 1024; 就能做全链路语义分析。
容易被忽略的一点:#define 的作用域是“从定义点到文件末尾或 #undef”,没有命名空间隔离;而 const 和 constexpr 遵守 C++ 作用域规则,可以放在 namespace、函数内、甚至 if constexpr 分支里。