constexpr是C++11标准引入的关键字,核心作用是标识可以在编译阶段完成求值的表达式、变量或者函数,让原本运行期执行的计算提前到编译期完成,既减少运行时的计算开销,也能让编译器做更多优化。它和传统的const关键字有本质区别,const只是保证变量运行期不可修改,而constexpr要求表达式在编译期就必须能确定结果。

constexpr的核心作用
1. 定义编译期常量
使用constexpr修饰的变量,必须在编译期就能确定初始值,编译器会直接把这个值嵌入到生成的二进制代码中,不会为它分配运行时的内存空间。比如下面这个例子:
#include <iostream>
// 编译期常量,编译时就能确定值为100
constexpr int MAX_SIZE = 100;
int main() {
// 可以用constexpr常量定义数组大小,这是普通const变量做不到的
int arr[MAX_SIZE] = {0};
std::cout << "数组大小: " << MAX_SIZE << std::endl;
return 0;
}
2. 定义编译期可求值函数
constexpr修饰的函数,当传入的参数是编译期常量时,函数会在编译期执行并返回结果,否则会退化为普通运行期函数。这种特性让我们可以把简单的计算逻辑封装成函数,同时保证编译期可用的场景能正常求值。
#include <iostream>
// constexpr函数,计算两个整数的和
constexpr int add(int a, int b) {
return a + b;
}
int main() {
// 传入编译期常量,编译期就能算出结果,相当于直接写30
constexpr int sum = add(10, 20);
std::cout << "求和结果: " << sum << std::endl;
int x = 10, y = 20;
// 传入运行期变量,函数会在运行期执行
int run_sum = add(x, y);
std::cout << "运行期求和结果: " << run_sum << std::endl;
return 0;
}
3. 支持编译期构造对象
constexpr还可以修饰类的构造函数,只要构造函数的所有参数都是编译期常量,并且构造函数内部没有运行期才能执行的操作,就可以在编译期创建该类的对象。比如下面这个简单的坐标类:
#include <iostream>
class Point {
public:
// constexpr构造函数
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int get_x() const { return x_; }
constexpr int get_y() const { return y_; }
private:
int x_;
int y_;
};
int main() {
// 编译期构造Point对象
constexpr Point p(10, 20);
constexpr int x = p.get_x();
constexpr int y = p.get_y();
std::cout << "坐标x: " << x << ", 坐标y: " << y << std::endl;
return 0;
}
constexpr与元编程的结合
元编程指的是在编译期生成或者操作代码的编程方式,传统的C++元编程主要依赖模板特化,语法复杂且可读性差。constexpr出现后,我们可以用更直观的函数式写法实现编译期元编程,大幅降低元编程的门槛。
比如要计算斐波那契数列的第n项,用传统模板元编程需要写特化版本,而用constexpr函数只需要几行代码:
#include <iostream>
// constexpr递归计算斐波那契数列
constexpr int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
int main() {
// 编译期计算斐波那契第10项,结果直接嵌入二进制
constexpr int fib10 = fib(10);
std::cout << "斐波那契第10项: " << fib10 << std::endl;
return 0;
}
上面的fib函数在传入编译期常量n时,递归过程会在编译期完成,不会占用运行时的栈空间和计算资源。如果是运行期传入n,函数会正常在运行期执行递归逻辑。
constexpr的使用注意事项
- constexpr变量必须有初始值,且初始值必须是编译期可求值的表达式,不能是运行期才能确定的值,比如用户输入、函数运行期返回值等。
- constexpr函数内部在C++11标准下只能有一条return语句,不能有循环、局部变量等复杂逻辑,C++14及之后标准放宽了限制,允许函数内有循环、变量定义等操作,只要整体能在编译期求值即可。
- constexpr构造函数初始化的所有成员变量,都必须是内置类型或者也有constexpr构造函数的自定义类型,否则无法在编译期创建对象。
- 如果不确定一个表达式是否能在编译期求值,可以用
static_assert配合constexpr表达式做编译期检查,比如static_assert(MAX_SIZE == 100, "MAX_SIZE值错误");,如果表达式不成立会直接编译报错。
constexpr和const的区别
很多开发者容易混淆const和constexpr,两者的核心差异如下:
| 对比项 | const | constexpr |
|---|---|---|
| 求值阶段 | 运行期保证值不可修改,初始值可以是运行期确定的 | 编译期必须能确定值,否则编译报错 |
| 数组大小定义 | 不能直接作为数组大小(C++不支持变长数组的情况下) | 可以直接作为数组大小,因为编译期已知值 |
| 函数修饰 | 修饰成员函数表示该函数不会修改对象成员 | 修饰函数表示该函数可以在编译期求值 |
| 对象构造 | 可以修饰运行期构造的对象 | 只能修饰编译期可构造的对象 |
实际开发中,如果确定某个变量、函数或者对象需要在编译期确定值,优先使用constexpr,这样既能获得编译期优化的好处,也能让代码的语义更清晰,明确告诉其他开发者这个表达式是编译期常量。