C++ constexpr 修饰符与函数
文章目录
- 一、constexpr 修饰符
- 1.1 修饰变量:定义编译期常量
- 1.2 核心作用
- 1.3 区别对比
- 二、constexpr 函数
- 三、字面值
- 3.1 字面值类型
- 3.2 指针和引用
- 3.3 自定义字面值类型
- 四、允许递归
- 五、泛型
- 5.1 模板函数中的 constexpr
- 5.2 if constexpr(C++17)
一、constexpr 修饰符
在 C++ 中,constexpr 是一个关键字修饰符,其核心作用是指定对象或函数可以在编译期计算,从而将计算逻辑从运行时提前到编译期,带来性能优化、代码安全性提升等好处,同时在编译时就能发现错误。
换句话说:
- 如果上下文要求编译期常量(如数组大小、模板参数),
constexpr
能保证表达式能在编译期计算; - 如果上下文允许运行时计算,
constexpr
也能在运行时执行(并非强制仅限编译期)。
1.1 修饰变量:定义编译期常量
constexpr
修饰变量时,要求变量必须在定义时初始化,并且初始化值必须是常量表达式,该值不可修改(类似 const,但更严格)。
#include <iostream>
#include <string>
using namespace std;int some_function() {return 999;
}int main() {constexpr int max_size = 100; // 编译期确定值,可用于数组大小等编译期上下文int arr[max_size]; // 合法:编译期已知大小// 对比 const: const 变量可能在运行时初始化const int runtime_val = some_function(); // 运行时确定值// constexpr int error = some_function(); // 错误:无法在编译期确定值return 0;
}
特点:
constexpr
变量必须立即初始化;- 初始值必须是编译期可计算的表达式;
constexpr
变量一定是编译期常量,而const
变量可能是运行时常量;constexpr
在模板元编程、常量数组、编译期分支(if constexpr
)中尤为重要。
1.2 核心作用
- 性能优化:结果在编译期直接计算并嵌入,减少运行时开销。
- 安全性提升:在编译期检查逻辑,例如数组大小是否合法。
- 支持元编程:结合模板,可以写出编译期执行的算法,如编译期排序、编译期字符串哈希。
1.3 区别对比
-
constexpr
不是“只能在编译期执行”,而是“允许在编译期执行”; -
不同标准的演进:
- C++11:
constexpr
限制非常严格(函数只能有一个 return)。 - C++14:放宽限制,允许循环、局部变量、多条语句。
- C++20:支持
constexpr
动态内存分配、虚函数、try-catch 等更复杂场景。
- C++11:
constexpr
、const
、#define
区别
特性 | constexpr | const | #define |
---|---|---|---|
类型检查 | 有 | 有 | 无(宏替换) |
编译期计算 | 必须 | 可能 | 可能(纯替换) |
作用域 | 块作用域 | 块作用域 | 全局宏替换,无作用域 |
调试支持 | 好 | 好 | 差 |
内存分配 | 无额外开销 | 可能有 | 无 |
constexpr
是 最严格的常量语义(编译期 + 类型安全);const
只保证不可修改,不一定编译期常量;#define
只是预处理器替换,缺乏类型检查和作用域控制。
二、constexpr 函数
constexpr
函数是指可以用于常量表达式的函数。
constexpr 函数指的是在编译的时候就能得到其返回值的函数,也就是说编译器将 constexpr 函数直接转换成其返回值。
简单理解就是在编译阶段函数已经执行完成并把运行结果填到了 constexpr 函数所在位置。因此,constexpr 函数都是被隐式地定义为内联函数。
编译器会尽量在编译期执行 constexpr
函数,如果参数是常量表达式,则直接计算出结果并替换;如果参数不是常量表达式,函数会退化为普通函数,运行时求值。
所有 constexpr
函数都会被隐式地视为 inline
。
要点
- C++11 限制:函数中只能包含一个
return
语句; - C++14 起:支持多条语句、局部变量、分支语句、循环等;
- C++20 起:支持
try-catch
、new/delete
等复杂逻辑; - 返回值必须是字面值类型(算术类型、引用、指针属于字面值类型)
- 参数必须是字面值类型(自定义类、IO 库、string 类型不属于字面值类型)
- constexpr 函数被隐式地指定为内联函数
- 允许递归调用。
测试合法性:
#include <iostream>
using namespace std;constexpr int add(int a, int b) {return a + b;
}int main() {// 合法:编译期计算,结果为8constexpr int sum1 = add(3, 5);cout << sum1 << endl;// 合法:运行时计算,结果为8(仍合法),// 印证了constexpr不是"仅编译期执行"的标志,而是"允许编译期执行"的标志int x = 3, y = 5;int sum2 = add(x, y);cout << sum2 << endl;// 不合法:编译期计算,不能传入变量;// constexpr int sum3 = add(x, y); // 错误return 0;
}
constexpr
不是“强制编译期执行”,而是“能编译期执行时就执行”。
三、字面值
字面值类型(Literal Types):编译器在编译期就能完全确定的类型。
在编程中,字面值类型指的是编译期就能确定其值的类型,它们的取值是固定的字面值(如数值、字符串等),且在编译阶段即可被编译器完全知晓。这类类型通常用于模板元编程、常量表达式等场景,能在编译期进行计算或校验,提升程序效率或安全性。
3.1 字面值类型
- 算术类型:
int
,long
,float
,double
,bool
- 字符类型:
char
,wchar_t
,char16_t
,char32_t
- 布尔类型:bool
- 指针、引用(指向编译期已知地址的对象,如全局变量、静态变量)
- 从 C++11 起:自定义字面值类型(满足 constexpr 构造函数、析构函数的类)。
3.2 指针和引用
指向全局变量(编译期可确定地址)的指针和引用也属于字面值类型。
#include <iostream>
#include <string>
using namespace std;// 全局变量,地址编译期可确定
int global_var = 42;constexpr int* change(int* p) {*p = 12;return p;
}int main() {// 合法:指向全局变量的指针是字面值类型,可用于constexprconstexpr int* ptr_to_global = &global_var;// 合法int* p = change(&global_var);cout << p << endl; // 输出地址cout << ptr_to_global << endl; // 输出相同地址cout << global_var << endl; // 输出12cout << *p << endl; // 输出12return 0;
}
输出:
0x56265707e010
0x56265707e010
12
12
如果指针指向的是局部变量(运行时分配地址),就不是字面值类型。
3.3 自定义字面值类型
#include <iostream>
using namespace std;struct Point {int x, y;constexpr Point(int a, int b) : x(a), y(b) {}// 添加一个constexpr成员函数计算距离平方constexpr int distanceSquared() const {return x * x + y * y;}
};constexpr Point p1(3, 4); // 编译期常量对象int main() {// 编译期计算距离平方constexpr int distSq = p1.distanceSquared();cout << "Point p1: (" << p1.x << ", " << p1.y << ")" << endl;cout << "Distance squared from origin: " << distSq << endl;// 可以在编译期上下文使用constexpr Point p2(5, 12);constexpr int distSq2 = p2.distanceSquared();cout << "Point p2: (" << p2.x << ", " << p2.y << ")" << endl;cout << "Distance squared from origin: " << distSq2 << endl;// 使用constexpr对象作为数组大小int arr[distSq > 10 ? 10 : 5] = {0}; // 条件编译期表达式return 0;
}
输出:
Point p1: (3, 4)
Distance squared from origin: 25
Point p2: (5, 12)
Distance squared from origin: 169
四、允许递归
constexpr
函数允许递归调用,这使得一些经典递归算法可以在编译期计算。
#include <iostream>
#include <string>
using namespace std;// 常量表达式函数
constexpr int factorial(int n) {return n == 1 ? 1 : n * factorial(n - 1);
}// 常量表达式
constexpr int num = 5;int main() {// 编译期间进行计算并且返回结果cout << factorial(num) << endl; // 输出120cout << factorial(3) << endl; // 输出6// 实参为变量时,程序运行期间计算并返回结果。int i = 8;// constexpr int result = factorial(i); // 报错;表达式必须含有常量值int result = factorial(i); // 运行时计算cout << result << endl; // 输出40320return 0;
}
输出:
120
6
40320
注意:
- 如果传入的是常量表达式(如
5
),在编译期计算; - 如果传入的是变量(如
x
),在运行时计算。
五、泛型
constexpr
在泛型编程中扮演着重要角色,特别是结合模板、if constexpr
(C++17 引入)时,可以让模板逻辑在编译期分支,从而实现更高效、更安全的代码。
5.1 模板函数中的 constexpr
#include <iostream>
using namespace std;// constexpr 模板函数
template <typename T>
constexpr T add(T a, T b) {return a + b;
}int main() {// 编译期计算constexpr int sum = add(10, 20);cout << "Compile-time sum: " << sum << endl;// 运行时计算double d = add(1.1, 2.2);cout << "Run-time sum: " << d << endl;// 演示编译期计算的能力 - 用于数组大小constexpr int array_size = add(5, 5);int arr[array_size] = {0}; // 数组大小在编译期确定cout << "Array size: " << sizeof(arr)/sizeof(arr[0]) << endl;return 0;
}
输出:
Compile-time sum: 30
Run-time sum: 3.3
Array size: 10
5.2 if constexpr(C++17)
if constexpr
是一种编译期分支,只有满足条件的分支才会被实例化。
#include <iostream>
#include <type_traits>
using namespace std;// 重命名函数以避免与 std::type_info 冲突
template <typename T>
constexpr auto get_type_info() {if constexpr (is_integral<T>::value) {return "Integral";} else {return "Non-integral";}
}int main() {cout << get_type_info<int>() << endl; // 输出 "Integral"cout << get_type_info<double>() << endl; // 输出 "Non-integral"return 0;
}
作用:
- 结合模板,
constexpr
能实现零运行时开销的泛型编程; if constexpr
让模板元编程逻辑更直观,不必依赖 SFINAE;- 编译期递归、分支、计算,极大地增强了 C++ 的元编程能力。