Menu

C++ const 常量:const、constexpr 与 consteval

在 C++ 中如何用 const 声明只读的值,const 和 constexpr 有什么区别,const 指针与指向 const 的指针,以及 const 成员函数。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

为什么需要常量

常量是一个你承诺设定之后就不再改变的值。把某个东西标记为 const 同时做了两件事:它向读代码的人说明了你的意图,也让编译器来强制执行这个意图。任何试图修改这个值的代码都会变成编译错误,而不是一个悄无声息的运行时 bug。

auto 关键字让编译器推断变量的类型,而 const 限制的是你能对这个变量做什么。两者可以自由组合:const auto limit = 100; 就是一个只读的 int。

声明 const 值

把 const 写在类型前面。const 变量必须在同一行初始化,因为之后再也没有允许你给它赋值的时机。

取消那行赋值的注释,程序就无法编译,编译器会报告“assignment of read-only variable”。这正是目的所在:错误在程序运行之前就被发现了。

从 C 带过来的一个常见新手习惯是写 #define MAX_USERS 100。不要这样做。宏只是盲目的文本替换,没有类型,也不遵守作用域,所以没法在调试器里查看,出错时的错误信息也让人摸不着头脑。const(或 constexpr)变量会像其他变量一样做类型检查,也有自己的作用域。

const 与 constexpr

两个关键字都能得到一个不能改变的值,但它们回答的是不同的问题。const 说的是“设定之后就不再改变”。constexpr 说的更进一步:“这个值能在编译期算出来”。而且任何 constexpr 的东西自动也是 const。

经验法则:只要值是一个固定的字面量,或者是编译器能算出的计算(数组大小、缓冲区长度、switch 标签、模板实参),就用 constexpr。如果值要到运行时才确定,但之后不应再改变,就用普通的 const,比如函数参数的一个 const 副本。

从 C++20 开始还有 consteval,用于那些必须在编译期运行的函数:

consteval int square(int x) { return x * x; }
constexpr int area = square(8); // computed during compilation

constexpr 函数可以在编译期运行;consteval 函数则总是必须在编译期运行,否则就是错误。

指针与 const:从右往左读

这是 const 最容易让人犯迷糊的地方,因为这个关键字可以写在 * 的任意一边,而两种写法的含义正好相反。诀窍是从右往左读声明。

从右往左读 int* const p2:“p2 是一个指向 int 的 const 指针”。把 const int* p1 读作“p1 是一个指向 const int 的指针”。如果搞错了,你会花不少时间对着一条错误发愣:它说你不能修改一个你以为可以修改的东西。

一个实际的坑:永远不要取一个 const 的地址,再把 const 转换掉去修改底层对象。如果原对象本来就是 const,这样做就是未定义行为,编译器可以假定这个值永远不会改变,你的“写入”可能直接被忽略。

用 const 引用作函数参数

const 在日常中最常见的用法,是按引用传递大对象而不复制它们。const& 参数既避免了复制,又保证函数不会修改调用者传进来的实参。

对于任何超过几个字节的参数(字符串、vector、你自己写的类),按 const& 传递都是默认的选择。注意它还能让函数接受像 "Grace" 这样的临时值:普通的非 const 引用不能绑定到临时值,所以如果这里去掉 const,第二次调用就会被拒绝。

const 成员函数

写类的时候,凡是不修改对象的方法,都在末尾加上 const。正是这一点让该方法可以在 const 实例和 const& 参数上调用。没有它,你就没法通过 const 句柄读取自己的对象。

把只读方法标记为 const 的这种做法叫作 const 正确性(const correctness)。尽早养成这个习惯:给一个本该是 const 的方法补上 const 很容易,但等代码库变大之后再到处补 const 就很痛苦了,因为每个通过 const 引用调用它的地方都依赖于它。

下一步:运算符

现在你的值已经可以用 const 锁定了,下一步就是用它们做事情。运算符这一页讲解算术、比较、逻辑和赋值运算符,包括整数除法和运算符优先级中的坑,以及 const 与那些你不能使用的赋值运算符之间的关系。

常见问题

C++ 中 const 和 constexpr 有什么区别?

const 表示值在初始化之后不能再改变,但这个值可以在运行时才算出来。constexpr 的要求更强:它保证值能在编译期算出,所以可以用在必须是编译期常量的地方(数组大小、模板实参、switch 标签)。每个 constexpr 对象同时也是 const,但并不是每个 const 对象都是 constexpr。

C++ 中怎么声明一个常量?

在类型前面加上 const 并给它一个值:const int maxUsers = 100;。const 变量必须在声明时初始化,因为之后你再也不能给它赋值。如果是编译期常量,优先写成 constexpr int maxUsers = 100;。不要用旧的 C 风格 #define 宏,它没有类型,也不管作用域。

C++ 中的 const 指针是什么意思?

要看 const 写在哪里。const int* p 是指向 const 的指针:你可以让 p 指向别处,但不能修改 *p。int* const p 是 const 指针:你可以修改 *p,但不能让 p 指向别处。从右往左读声明:int* const 就是“指向 int 的 const 指针”。

Coddy 编程语言插图

和 Coddy 一起学编程

开始