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
。
转载请注明出处。