c++中const和constexpr的区别
核心概念总结
const:主要是一个语义/承诺概念,意思是“只读”。它告诉编译器和程序员:“这个对象初始化后,我不应该通过这个标识符去修改它”。它不关心这个值是在编译期还是运行期确定的。constexpr:主要是一个时间点概念,意思是“常量表达式”。它告诉编译器:“这个值(或函数)必须在编译期就能计算出来”。因为它必须在编译期确定,所以它天然就是const的(即只读的)。
可以把它们的关系理解为:所有的constexpr都是const,但并非所有的const都是constexpr。
详细区别与示例
我们从变量和函数两个角度来分析。
1. 用于变量(Objects)
| 特性 | const | constexpr |
|---|---|---|
| 核心含义 | 只读。运行时或编译期初始化均可。 | 编译期常量。必须在编译期初始化。 |
| 初始化时间 | 运行时或编译期 | 必须是编译期 |
| 要求的严格性 | 较宽松。可以用运行时计算的结果初始化。 | 非常严格。初始化表达式必须是编译期可计算的。 |
| 用途 | 保证程序逻辑上不修改变量。 | 用于需要编译期常量的场景(如数组大小、模板参数等)。 |
示例代码:
#include <iostream>
int get_value() {
return 10;
}
constexpr int get_value_constexpr() {
return 10;
}
int main() {
int runtime_var = 10; // 显然是运行时变量
// 1. const 变量
const int const_var_1 = 10; // 编译期初始化,是 const,也是编译期常量
const int const_var_2 = get_value(); // **运行时**初始化,是 const,但不是编译期常量
// 2. constexpr 变量
constexpr int constexpr_var_1 = 10; // 正确:字面量,编译期可知
constexpr int constexpr_var_2 = get_value_constexpr(); // 正确:constexpr函数在编译期计算
// constexpr int constexpr_var_3 = get_value(); // **错误!** get_value() 不是 constexpr 函数,无法在编译期求值
// constexpr int constexpr_var_4 = const_var_2; // **错误!** const_var_2 本身是运行时初始化的,不是编译期常量
// 关键区别体现在需要编译期常量的场景
int array_1[const_var_1]; // OK (在大多数编译器下): 因为 const_var_1 是编译期常量
// int array_2[const_var_2]; // **错误!** const_var_2 不是编译期常量,不能用于定义数组大小
int array_3[constexpr_var_1]; // OK: constexpr_var_1 肯定是编译期常量
// 对于模板元编程(如 std::array)
std::array<int, const_var_1> arr1; // 通常OK
// std::array<int, const_var_2> arr2; // 错误!
std::array<int, constexpr_var_1> arr3; // 总是OK
return 0;
}2. 用于函数(Functions)
| 特性 | const成员函数 | constexpr函数 |
|---|---|---|
| 核心含义 | 承诺不修改类的成员变量(mutable成员除外)。 | 表示该函数有可能在编译期被调用和执行。 |
| 调用时机 | 只能在运行时调用。 | 既可以在编译期调用,也可以在运行时调用。 |
| 函数体要求 | 函数体内不能修改非mutable成员。 | 函数体在C++11/14/17/20标准下有严格限制,但标准在逐步放宽。通常要求函数体非常简单(C++11),后来可以包含循环、局部变量甚至if等(C++14+)。 |
核心思想:constexpr函数就像一个“承诺”,承诺如果我给你的参数是编译期常量,那么我就能在编译期计算出结果。如果给的是运行时变量,那么它就在运行时计算,像一个普通函数一样。
示例代码:
#include <array>
// 一个 constexpr 函数
constexpr int square(int n) {
return n * n;
}
class MyClass {
public:
// const 成员函数:承诺不修改成员状态
int get_value() const {
return member_var;
}
// constexpr 构造函数:允许在编译期创建该类的常量表达式
constexpr MyClass(int x) : member_var(x) {}
// constexpr 成员函数
constexpr int get_value_constexpr() const {
return member_var;
}
private:
int member_var;
};
int main() {
int x = 5;
// 使用 constexpr 函数
constexpr int compiled_time_result = square(10); // 编译期计算
int run_time_result = square(x); // 运行时计算
// 使用 constexpr 构造函数
constexpr MyClass obj(15); // 必须在编译期初始化
constexpr int val = obj.get_value_constexpr(); // 编译期调用 constexpr 成员函数
// 用于需要编译期常量的场景
std::array<int, square(5)> arr1; // 数组大小在编译期计算为 25
// std::array<int, square(x)> arr2; // 错误!x 不是编译期常量
return 0;
}总结与使用建议
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 需要一个绝对不会改变的值 | const或constexpr | 语义上是“只读”的。 |
| 需要一个值,并且它必须在编译期就知道(如数组大小、模板参数、case标签) | 必须使用constexpr | const不一定能在编译期知道值。 |
| 设计一个函数,希望它既能用于编译期计算也能用于运行时计算 | constexpr | 提高了函数的可用性和性能(编译期计算可优化为字面量)。 |
| 一个类的成员函数,承诺不修改对象状态 | const | 这是const成员函数的专属用途。 |
| 一个类的构造函数,希望该类能用于编译期常量 | constexpr | 允许在编译期创建该类的常量表达式对象。 |
现代C++开发的最佳实践是:
优先使用
constexpr。如果你需要一个常量,并且它的值可以在编译期确定,那么毫不犹豫地使用constexpr。这为你的代码提供了最大的优化可能性和灵活性。只有在确实需要在运行时初始化一个只读变量时,才使用普通的const。
转载请注明出处。