一、核心概念与用法
1. const(常量)
const 是 C++ 中历史悠久的关键字,其核心作用是修饰变量、对象或函数,声明其具有“只读”属性。关键在于,它保证的是“不可修改”,但并不强制该值必须在编译期就确定下来。
核心特点:
- 修饰变量:变量一旦初始化后,其值便不能被修改。但这个初始化操作可以发生在运行时。
- 修饰函数:在类成员函数中使用时,表明该函数不会修改类的任何成员变量(mutable 修饰的变量除外)。
- 修饰指针/引用:这是一个易错点,需要仔细区分“指向常量的指针”和“常量指针”。
代码示例:
#include <iostream>
using namespace std;
int get_num() { return 10; }
int main() {
// 1. 运行时初始化的 const 变量(只读,但不是编译期常量)
const int a = get_num(); // 合法,a是只读的,但其值在运行时才确定
// a = 20; // 错误:const 变量不可修改
// 2. 编译期初始化的 const 变量(此时可作为编译期常量使用)
const int b = 20;
int arr[b]; // 合法(C++11 之后),b 是一个编译期已知的常量
// 3. 类成员函数中的 const
class Test {
public:
int x = 5;
int get_x() const { // const 成员函数:承诺不修改成员变量
// x = 10; // 错误:不能在 const 成员函数中修改成员
return x;
}
};
return 0;
}
从上面的例子可以看出,const 更多地是一种访问权限的限制,是 C/C++ 编程中保证数据不被意外修改的基础工具。
2. constexpr(常量表达式)
C++11 引入了 constexpr,其核心作用是强制表达式必须在编译期完成求值。你可以将它理解为 const 的“强化版”,专门用于定义编译期常量。
核心特点:
- 修饰变量:变量的值必须在编译期就能确定,因此可以直接用作数组大小、模板参数等需要编译期常量的地方。
- 修饰函数:函数可以在编译期被调用并计算返回值(需满足一定条件:参数是常量表达式、函数体足够简单等)。
- C++14 放宽限制:
constexpr 函数内部可以包含局部变量、循环和条件判断等更复杂的逻辑。
代码示例:
#include <iostream>
using namespace std;
// constexpr 函数:用于编译期计算
constexpr int add(int a, int b) {
return a + b;
}
// C++14 风格:constexpr 函数支持更复杂的逻辑
constexpr int factorial(int n) {
int res = 1;
for (int i = 1; i <= n; ++i) {
res *= i;
}
return res;
}
int main() {
// 1. constexpr 变量:值在编译期确定
constexpr int c = add(10, 20); // c = 30,在编译时计算完成
int arr[c]; // 合法,c是确凿的编译期常量
// 2. 错误示例:constexpr 变量必须用编译期已知的值初始化
// int d = 10;
// constexpr int e = d; // 错误:d 是运行时变量,值不确定
// 3. 使用 constexpr 函数进行编译期计算
constexpr int f = factorial(5); // f = 120,编译期计算
return 0;
}
constexpr 将“常量”的概念从“只读”提升到了“编译期可知”,是进行编译期计算、优化性能的利器。
3. consteval(立即函数)
C++20 带来了 consteval,它是 constexpr 的“严格版”,核心作用是强制函数必须在编译期执行,绝对不允许在运行时被调用。
核心特点:
- 仅限编译期:
consteval 函数的调用结果必须是一个编译期常量。任何试图用运行时参数调用它的行为都会导致编译错误。
- 比 constexpr 更严格:
constexpr 函数是“可以在编译期执行”,而 consteval 函数是“必须在编译期执行”。
代码示例:
#include <iostream>
using namespace std;
// consteval 函数:强制编译期执行
consteval int square(int n) {
return n * n;
}
int main() {
// 合法:使用字面量调用,在编译期计算
constexpr int g = square(5); // g = 25
// 错误示例:不能用运行时变量调用 consteval 函数
int h = 5;
// int i = square(h); // 错误:h 是运行时变量,无法满足“立即执行”的要求
return 0;
}
当你需要确保某个函数逻辑(如数学运算、配置生成)百分百在编译期完成,以避免任何运行时开销时,consteval 是你的不二之选。
4. constinit(常量初始化)
同样是 C++20 的新关键字,constinit 的核心作用是强制静态或线程局部变量在编译期进行初始化,但它并不限制变量初始化后的可修改性。
核心特点:
- 解决初始化顺序问题:专门用于修饰
static 或 thread_local 变量,确保其初始化发生在编译期或链接期,从而避免令人头疼的“静态初始化顺序错乱”问题。
- 变量可修改:与
const 和 constexpr 不同,constinit 只关心初始化时机,不施加“只读”约束,变量之后仍然可以被修改。
代码示例:
#include <iostream>
using namespace std;
// constinit 修饰静态变量:保证编译期初始化,但变量可修改
constinit static int j = 100;
int main() {
j = 200; // 合法:constinit 只保证初始化时机,不限制修改
cout << j << endl; // 输出 200
// 错误示例:constinit 只能用于静态或线程局部存储期的变量
// constinit int k = 5; // 错误:k 是自动存储期(非static)
return 0;
}
constinit 是管理程序启动阶段和线程启动阶段变量初始化的强大工具,它保证了初始化的确定性,同时保留了后续修改的灵活性。
二、关键区别对比
| 关键字 |
核心作用 |
是否只读 |
是否编译期求值 |
主要适用范围 |
| const |
声明只读(值可在运行时确定) |
是 |
不一定 |
变量、函数、指针、引用 |
| constexpr |
强制编译期求值(常量表达式) |
是 |
是 |
变量、函数、构造函数 |
| consteval |
强制函数仅编译期执行(立即函数) |
是 |
必须 |
函数 |
| constinit |
强制静态变量编译期初始化(但可修改) |
否 |
是 |
static / thread_local 变量 |
总结
- const:基础中的基础,核心是“只读保护”。它不关心值何时确定,只关心值一旦确定就不能改。
- constexpr:核心是“编译期常量”。它要求值必须在编译期就计算好,是进行编译期计算和优化的关键。
- consteval:C++20 的强化武器,核心是“函数必须编译期执行”。它比
constexpr 函数更严格,彻底杜绝运行时调用。
- constinit:C++20 的秩序维护者,核心是“静态变量编译期初始化”。它解决了初始化顺序的顽疾,但不限制变量后续的可变性。
简单记忆口诀:
- 想要只读用
const;
- 想要编译期常量用
constexpr;
- 想要函数必须编译期跑用
consteval;
- 想要静态变量确定初始化(还能改)用
constinit。
希望这篇对比能帮助你清晰地理解这四个容易混淆的关键字。在实际的 C/C++ 项目开发中,根据不同的需求(保护数据、编译期计算、优化性能、确定初始化)选择合适的工具,是写出高效、健壮代码的重要一步。如果你在实践中遇到了具体问题,欢迎来 云栈社区 与其他开发者一起交流探讨。