【c++八股文】Day5:const和constexpr,define
非常棒的问题,这几个关键字(const
、constexpr
、#define
)都可以用来表示“常量”,但它们意义、作用时机和参与编译的方式完全不同,理解这些对掌握 C++ 编译模型和写出高质量代码非常重要。
我们从用途、语义、生命周期、编译阶段、类型检查、作用域、性能优化等维度系统性整理:
✅ 一、先看三者的简要对比
特性/关键词 | #define | const | constexpr |
---|---|---|---|
类型安全 | ❌(纯文本替换) | ✅(有类型) | ✅(有类型,且编译期常量) |
编译阶段 | 预处理阶段 | 编译阶段 | 编译阶段(要求常量表达式) |
编译器可见性 | ❌(对编译器不可见) | ✅(符号存在) | ✅(符号存在且常量可折叠) |
优化能力 | 差(不利于调试优化) | 较好 | 最佳 |
可以取地址 | ❌ | ✅(在符号表中) | ✅(但可能折叠为立即数) |
支持复杂类型 | ❌(只能是文本) | ✅ | ✅ |
可用于数组长度 | ❌ | ✅(C++11起) | ✅ |
常量表达式限制 | 无 | 不要求是编译期常量 | 必须是编译期常量表达式 |
🧩 二、编译器行为层面解释
1. #define
:预处理阶段文本替换
#define PI 3.14
🔍 行为:
- 在预处理阶段,编译器会把 所有出现
PI
的地方直接替换为3.14
- 编译器看不到 PI 这个标识符,调试器、类型系统也看不到
- 不参与类型检查、作用域控制
📌 不推荐:
#define SQUARE(x) x * x
int y = SQUARE(1 + 2); // → 展开为 1 + 2 * 1 + 2 → 错误
2. const
:在编译阶段处理的只读变量
const double pi = 3.14;
🔍 行为:
pi
是一个具有类型和作用域的符号- 编译器会参与类型检查
- 可以取地址(存在符号表中)
- 如果优化器看到它不变,可能优化为立即数
📌 示例:
const int N = 10;
int arr[N]; // C++11 起允许
3. constexpr
:编译阶段就能求值为常量的表达式
constexpr double pi = 3.14;
constexpr int square(int x) { return x * x; }
🔍 行为:
-
要求变量或函数在编译期间就能确定值
-
编译器会强制执行常量折叠(constant folding)
-
可用于:
- 数组大小
- 模板参数
- 枚举值
if constexpr
语句分支
-
可定义常量表达式函数
📌 示例:
constexpr int N = square(3); // N = 9,在编译期完成计算
int arr[N]; // 数组大小合法
⚠️ 三者在调试器中的差异
内容类型 | 在调试器中是否可见 | 是否能打断点 | 是否能取地址 |
---|---|---|---|
#define | ❌ 不可见(被替换掉) | ❌ 无法断点 | ❌ |
const | ✅ 可见(有符号名) | ✅ 可以断点 | ✅ |
constexpr | ⚠️ 可能折叠优化掉 | ❌ 不一定能断点 | ❌ 常常变成立即数 |
✅ 四、代码层级对比示例
#define LEN 100 // 无类型,只是 100 的文本替换const int len1 = 100; // 有类型,支持类型检查,可能折叠
constexpr int len2 = 100; // 编译期常量,支持最强优化int arr1[LEN]; // ✅
int arr2[len1]; // ✅(C++11 起)
int arr3[len2]; // ✅(优先推荐)
🎓 五、适合用于什么场景?
使用场景 | 推荐选项 | 原因 |
---|---|---|
简单常量替代 | constexpr | 类型安全 + 编译期求值 + 优化好 |
宏函数 | inline constexpr function | 避免宏展开错误,可调试 |
魔法数字、配置参数 | constexpr or const | 类型安全、可调试 |
条件编译(平台相关) | #ifdef/#define | 预处理器语法只在这类场景合法 |
定义模板参数或数组大小 | constexpr | 编译期要求常量 |
✅ 六、语义分层总结图
┌────────────────────────────┐│ #define PI 3.14 │ ← 预处理,替换文本,无类型、无调试信息└────────────────────────────┘↓┌────────────────────────────┐│ const double pi = 3.14; │ ← 有类型,有符号,有作用域,可调试└────────────────────────────┘↓┌────────────────────────────┐│ constexpr double pi = 3.14;│ ← 编译期常量表达式,最优性能和安全性└────────────────────────────┘
✅ 七、一句话总结
#define
是预处理器的文本替换,完全不参与类型系统;const
是只读变量,具有类型和作用域;constexpr
是编译期可求值的常量表达式,是现代 C++ 推荐的写法,支持最强类型安全与编译优化。