更新时间: 2026-08-27
04 篇讲过 const:告诉编译器"这个值不许改"。这篇讲它的进阶版本 constexpr——不止"不许改",还要"在编译期就算出来"。这是 C++11 起让"常量表达式"进入日常编程的关键特性,也是 C++ 性能优化里"把开销从运行期挪到编译期"的代表手法。
本文要回答:constexpr 和 const 到底差在哪?constexpr 函数怎么保证编译期求值?以及什么时候该用、什么时候用了也白用?
一、constexpr 变量
先看最简单的形式——constexpr 变量,要求初始化表达式必须在编译期可求值:
constexpr int kMaxSize = 1024; // 字面量,OK
constexpr double kPi = 3.14159; // OK
constexpr int kDouble = kMaxSize * 2; // 由其它 constexpr 算出来,OK
int runtime_value() { return 42; }
// constexpr int x = runtime_value(); // 编译错误!运行期才有的值constexpr 变量必须在编译期确定值,所以:
- 初始化必须是编译期常量(字面量、其它 constexpr、constexpr 函数调用)
- 它自动是
const(不能改),还能用作数组大小、模板参数、switch的 case 标签
int arr[kMaxSize]; // 数组大小(编译期必须知道)
std::array<int, kMaxSize> a; // 模板参数(必须编译期常量)二、constexpr 函数
C++11 的 constexpr 函数限制很多(函数体只能有一条 return),C++14 放宽到"函数体里能用局部变量、循环、if",C++17/20 继续扩展。现在写起来已经比较自然:
// C++14 起:函数体可以是普通语句,但参数和返回必须是字面类型
constexpr int square(int x) {
return x * x;
}
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) result *= i;
return result;
}
constexpr int kFiveFact = factorial(5); // 编译期就算好 = 120
int n = 10;
int runtime = factorial(n); // 运行期算(也能调用!)关键特性:constexpr 函数既能在编译期用,也能在运行期用。传入编译期常量,编译期求值;传入运行期变量,退化为普通函数调用。同一个函数,两头通吃。
@startuml
left to right direction
skinparam nodeFontSize 13
skinparam backgroundColor #FFFFFF
rectangle "constexpr int factorial(int)" as fn #E8F1FF {
node "参数是编译期常量" as c #D9FFE2
node "参数是运行期变量" as r #FFF3D6
}
c --> node "编译期求值 → 常量" as c2 #D9FFE2
r --> node "运行期求值 → 普通调用" as r2 #FFF3D6
@enduml三、const 与 constexpr 的区别
很多教程把两者混着讲,其实分工明确:
| 维度 | const | constexpr |
|---|---|---|
| 核心语义 | 不可修改 | 编译期求值 |
| 修饰对象 | 变量、成员函数、参数 | 变量、函数、构造 |
| 值何时确定 | 可能运行期 | 必须编译期(或作为优化) |
| 能否作数组大小 | 不一定(运行期 const 不行) | 一定可以 |
| 能否作模板参数 | 不一定 | 一定可以 |
判断口诀:const 管"不能改",constexpr 管"早点算"。二者不冲突——constexpr 变量隐含 const,const 变量不一定是 constexpr。
一个典型对比:
int get_value() { return 42; }
const int c1 = get_value(); // OK:运行期初始化,之后不能改
// constexpr int c2 = get_value(); // 编译错误:get_value 不是 constexpr
constexpr int c3 = 42; // 编译期常量
const int c4 = 42; // 也能用于常量表达式(优化场景)四、C 对照:宏 vs constexpr
C 里"编译期常量"的传统做法是宏或 enum:
#define MAX_SIZE 1024
enum { BUF_LEN = 256 };| 需求 | C(宏/enum) | C++(constexpr) |
|---|---|---|
| 编译期常量 | #define / enum | constexpr int |
| 编译期计算 | 宏表达式(有坑) | constexpr 函数 |
| 类型检查 | 无(纯文本替换) | 有(真正的类型) |
| 调试信息 | 无(已替换) | 有(编译期计算但保留符号) |
| 作用域 | 全局污染 | 变量作用域 |
宏的最大问题:没有类型、没有作用域、纯文本替换。#define MAX 1024 里的 MAX 只是个记号,可能和别的标识符撞车。constexpr 是真正的变量/函数,有类型、有作用域、可重载、可放在命名空间里——它是"有类型安全的宏"。
五、什么时候用 constexpr
该用的场景:
- 常量定义:
kMaxSize、kPi这类语义上"永远不变"的值 - 编译期计算:数组大小、模板参数、查表初始化(比如编译期算好一张三角函数表)
- 性能敏感:热点路径里能提前算的都提前算
用了也白用的场景:
- 简单字面量:
constexpr int x = 1;和const int x = 1;编译产物没差别(都常量折叠) - 运行期才知道的值:别硬标 constexpr,编译器会拒绝
- 过度设计:函数很复杂、又只在运行期用,标 constexpr 增加编译负担却没收益
六、constexpr 在现代 C++ 的位置
constexpr 是"编译期编程"家族的一员,往深了走还有:
if constexpr(C++17):编译期分支,让模板代码按类型分叉consteval(C++20):强制编译期求值(constexpr 允许运行期,consteval 只准编译期)- constexpr 容器/算法(C++20 起
std::vector也能在 constexpr 里用)
本站 concepts/ 里性能相关章节提到"把计算从运行期挪到编译期"就是 constexpr 的用武之地。另外 constexpr 与 -O0 教学设置的关系:本站演示程序默认 -O0 -g,但 constexpr 的编译期求值是编译器前端行为,不受 -O0 影响——该在编译期算的照样算,这也是它和普通运行期优化(依赖 -O2)的区别。
七、与本站主线衔接
- 04 篇(const):const 的完整语义,本篇是它的"编译期"延伸
- 39 篇(算法库):
std::array大小等模板参数需要 constexpr demos/:部分实验用 constexpr 定义编译期常量表- 高手层:
constexpr与模板元编程的配合,在templates.md展开
八、一句话总结
constexpr 让变量和函数在编译期求值:constexpr 变量必须是编译期常量、可作数组大小和模板参数;constexpr 函数传常量时编译期算、传变量时退化为运行期调用;它比 C 的宏更安全(有类型、有作用域),是现代 C++ "把开销挪到编译期"的主力特性。