常量关键词
前两章分别讲了 const 和 constexpr。C++ 还提供 consteval 与 constinit,历史代码里也会遇到用宏和枚举定义的常量。本章把同族关键词放进一张表,解决“什么时候用哪个”的选型问题。
关键词对照
| 关键词 | 可变性 | 求值时机 | 链接与共享 | 典型场景 |
|---|---|---|---|---|
const |
不可通过该名字修改 | 允许运行时确定 | 命名空间作用域默认内部链接 | 运行时配置、读入后不再修改的值 |
constexpr |
同 const,并要求编译期确定 |
编译期 | 头文件共享用 inline constexpr |
尺寸、阈值等编译期规则 |
consteval |
修饰函数:调用必须发生在编译期 | 强制编译期 | 修饰函数而非变量 | 只在编译期使用的计算 |
constinit |
初始化在编译期完成,对象本身仍可修改 | 编译期初始化 | 常与全局变量配合 | 保证静态变量启动时已初始化 |
#define |
无类型、文本替换 | 预处理期 | 无作用域、无类型 | 历史代码,新代码不用于常量 |
C++11 之前,枚举值承担过整型常量的角色。如今这个角色首选仍是 constexpr。consteval 与 constinit 各自解决更专门的问题,遇到时按上表定位。头文件里常量与函数对 inline 要求不同的完整对照,见 constexpr 的「常量与函数」。
场景选型
选型只看值从哪里来、放在哪里:
- 运行时才知道的配置用
const。 - 编译期规则或尺寸用
constexpr,写在供多个.cpp共享的头文件里时加inline。 - 只在编译期完成的计算用
consteval。 - 需要保证静态存储期变量在启动时完成初始化的场景用
constinit。 - 有类型的常量不用
#define。
常见错误
const 与 constexpr 混同
二者都阻止通过该名字修改,但 constexpr 还要求初始化结果在编译期确定,运行时才知道的配置仍然使用 const。
用 #define 定义有类型常量
#define kMaxCount 5 没有类型和作用域,替换发生在预处理期,报错也难读;写成 constexpr int kMaxCount{5}; 后,类型检查和作用域规则全部生效。
自测问题
运行时读入的配置值选
const还是constexpr?查看答案
选
const。值在编译期未知,不满足constexpr的编译期要求,读入后不再修改正是const的场景。供多个
.cpp共享的头文件里的棋盘尺寸常量应该怎么写?查看答案
放在具名命名空间里写成
inline constexpr,例如board::kRows,所有包含该头文件的.cpp共享同一个实体。为什么新代码不用
#define定义有类型常量?查看答案
宏没有类型和作用域,只是预处理期的文本替换。
constexpr变量保留类型检查、作用域规则和更好的错误信息。
本章回顾
你已经能按“值从哪里来、放在哪里”做常量关键词选型。用 const-variables.cpp 和 constexpr.cpp 练习判断每个常量的选法,下一章进入类型与边界。