第 18.7 節
constexpr
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
有些計算在編譯時就已經知道結果,但如果用普通函數,卻要等到運行時才計算——浪費了 CPU 時間。此外,某些場景(數組大小、模板參數、static_assert)必須使用編譯期常量,普通變量做不到。
constexpr 讓函數和變量在編譯期就能求值,把運行時開銷轉移到編譯期,還能用在"必須編譯期常量"的地方。
這個特性是什麼
constexpr 聲明一個變量或函數可以在編譯期求值。從 C++11 開始引入,後續標準不斷增強。
| C++ 版本 | 支持 |
|---|---|
| C++11 | 簡單 constexpr 函數(一條 return 語句)、constexpr 變量 |
| C++14 | constexpr 函數支持多條語句、循環 |
| C++17 | constexpr lambda |
| C++20 | consteval(必須在編譯期執行)、更強的 constexpr |
| C++23 | constexpr 支持更多標準庫 |
C++ 標準版本
C++11(隨後的每個版本都有增強)
需要的頭文件
不需要額外頭文件。constexpr 是語言關鍵字。
基本語法
// constexpr 变量:值在编译期确定
constexpr int size = 100;
// constexpr 函数:可以在编译期执行
constexpr int square(int n)
{
return n * n;
}
// constexpr 变量可以用编译期函数初始化
constexpr int val = square(5); // 编译期计算
// C++20 consteval:必须在编译期执行(不能运行时调用)
consteval int cube(int n)
{
return n * n * n;
}
const vs constexpr
| 方面 | const | constexpr |
|---|---|---|
| 含義 | 變量不可修改 | 變量或函數可在編譯期求值 |
| 初始化 | 可在運行時初始化 | 必須在編譯期初始化 |
| 函數 | 只能修飾成員函數 | 函數可在編譯期執行 |
| 用途 | 防止變量被修改 | 編譯期計算,提高效率 |
| 示例 | const int x = get_value(); | constexpr int x = 5 * 3; |
constexpr、consteval、constinit 的區別
這三個詞看起來都和"常量"有關,但解決的問題不同。初學時先掌握 constexpr,再理解 C++20 的 consteval 和 constinit。
| 關鍵字 | 含義 | 典型場景 |
|---|---|---|
constexpr | 可以在編譯期求值,也可以在運行期調用 | 通用編譯期函數、常量表達式 |
consteval | 必須在編譯期求值 | 生成編譯期 ID、編譯期校驗 |
constinit | 保證靜態對象在編譯期初始化,但對象不一定是 const | 避免全局變量動態初始化順序問題 |
一句話區分:constexpr 偏"能不能算",consteval 偏"必須現在算",constinit 偏"初始化時機必須早"。
示例代碼
示例 1:constexpr 變量——必須編譯期確定
#include <iostream>
int main()
{
constexpr int size = 10; // 编译期常量
int arr[size]; // ✅ 可以用作数组大小
const int runtime_size = size; // const 可以用编译期常量初始化
int arr2[runtime_size]; // ✅ 也可以用作数组大小(因为初始化值是 constexpr)
int n = 5;
// constexpr int s2 = n; // ❌ n 是运行时变量,不能初始化 constexpr
const int s3 = n; // ✅ const 可以在运行时初始化
// int arr3[s3]; // ❌ s3 不是编译期常量,不能用作数组大小
std::cout << "size = " << size << "\n";
return 0;
}
運行結果:
size = 10
示例 2:在示例 1 基礎上,constexpr 函數
#include <iostream>
// constexpr 函数:编译期可执行
constexpr int factorial(int n)
{
int result = 1;
for (int i = 1; i <= n; ++i) // C++14 起支持循环
{
result *= i;
}
return result;
}
int main()
{
// 编译期计算:factorial(5) 在编译时就得到 120
constexpr int f5 = factorial(5);
std::cout << "factorial(5) = " << f5 << "\n";
// 也可运行时调用
int n;
std::cout << "Enter a number: ";
std::cin >> n;
std::cout << "factorial(" << n << ") = " << factorial(n) << "\n";
return 0;
}
運行結果:
factorial(5) = 120
Enter a number: 4
factorial(4) = 24
示例 3:在示例 2 基礎上,constexpr 用於模板和編譯期檢查
#include <iostream>
// constexpr 函数:编译期判断是否为质数
constexpr bool is_prime(int n)
{
if (n <= 1) return false;
for (int i = 2; i * i <= n; ++i)
{
if (n % i == 0) return false;
}
return true;
}
// 编译期静态断言
static_assert(is_prime(2), "2 is prime");
static_assert(is_prime(7), "7 is prime");
static_assert(!is_prime(9), "9 is NOT prime");
// 编译期生成质数数组
constexpr int get_nth_prime(int n)
{
int count = 0;
int num = 1;
while (count < n)
{
++num;
if (is_prime(num))
{
++count;
}
}
return num;
}
int main()
{
constexpr int primes[] = {
get_nth_prime(1), // 2
get_nth_prime(2), // 3
get_nth_prime(3), // 5
get_nth_prime(4), // 7
get_nth_prime(5) // 11
};
std::cout << "First 5 primes: ";
for (int p : primes)
{
std::cout << p << " ";
}
std::cout << "\n";
return 0;
}
運行結果:
First 5 primes: 2 3 5 7 11
示例 4:C++17 constexpr lambda
#include <iostream>
int main()
{
// constexpr lambda:编译期可调用
constexpr auto square = [](int x) constexpr {
return x * x;
};
constexpr int result = square(5); // 编译期计算
std::cout << "square(5) = " << result << "\n";
// constexpr lambda 也可运行时调用
int n = 7;
std::cout << "square(" << n << ") = " << square(n) << "\n";
return 0;
}
運行結果:
square(5) = 25
square(7) = 49
示例 5:在示例 4 基礎上,constexpr 可以運行期調用,consteval 不行
#include <iostream>
constexpr int square(int x)
{
return x * x;
}
consteval int compile_time_square(int x)
{
return x * x;
}
int main()
{
constexpr int a = square(5); // 编译期计算
int n = 7;
int b = square(n); // 运行期调用,也允许
constexpr int c = compile_time_square(6); // 必须编译期计算
// int d = compile_time_square(n); // ❌ 编译错误:n 不是编译期常量
std::cout << "a = " << a << "\n";
std::cout << "b = " << b << "\n";
std::cout << "c = " << c << "\n";
return 0;
}
運行結果:
a = 25
b = 49
c = 36
運行結果
見上方每個示例的"運行結果"。
示例中的關鍵語法解釋
| 示例 | 講了什麼 | 新出現的語法 | 為什麼這樣寫 | 注意事項 |
|---|---|---|---|---|
| 示例 1 | constexpr vs const | constexpr int size = 10;、數組大小 | constexpr 必須編譯期初始化,可以用作數組大小 | const 可以在運行時初始化,不保證編譯期 |
| 示例 2 | constexpr 函數 | 循環在 constexpr 函數中 | 同一個函數可編譯期調用也可運行時調用 | 編譯期調用時所有參數也必須是編譯期常量 |
| 示例 3 | constexpr 用於 static_assert | static_assert() | 編譯期檢查,不通過直接編譯失敗 | static_assert 的參數必須是編譯期常量 |
| 示例 4 | constexpr lambda | constexpr auto f = [](int x) constexpr {...}; | lambda 也能編譯期求值 | C++17 起支持,兩個 constexpr 都要寫 |
| 示例 5 | constexpr vs consteval | consteval | consteval 函數只能編譯期調用 | C++20 才有 consteval |
常見錯誤
錯誤 1:把運行時變量賦給 constexpr
int n = 5;
constexpr int x = n; // ❌ n 不是编译期常量
正確做法:const int x = n;(用 const 而不是 constexpr)。
錯誤 2:constexpr 函數假設所有情況都能編譯期求值
constexpr int divide(int a, int b)
{
return a / b; // 编译期 b=0 会导致编译错误
}
constexpr int x = divide(10, 0); // ❌ 编译错误!
正確做法:編譯期調用時注意參數合法性。
錯誤 3:constexpr 函數內部有未定義行為
constexpr int get(int* p)
{
return *p; // 编译期不能解引用空指针
}
constexpr int x = get(nullptr); // ❌ 编译错误!
正確做法:constexpr 函數中避免未定義行為。
錯誤 4:以為 constexpr 函數一定在編譯期執行
constexpr int square(int x) { return x * x; }
int n = 5;
int y = square(n); // ✅ 这是运行期调用,不是编译期调用
正確做法:需要強制編譯期時,把結果放進 constexpr 變量、static_assert、模板參數中;C++20 起也可以使用 consteval 函數。
使用建議
- 能用 constexpr 表達的就用 constexpr:把運行時計算移到編譯期,程序性能更好。
- constexpr 函數既編譯期又運行期:不必寫兩份代碼。
- 用
static_assert做編譯期檢查:配合 constexpr 函數非常強大。 - constexpr 不是"更快"的魔法:對於小函數,編譯器本來就會優化。但對於常量表、預計算等場景很有用。
- 需要"必須編譯期"才考慮 consteval:普通工具函數優先寫 constexpr,限制更少。
小結
constexpr變量必須在編譯期確定,可以用作數組大小、模板參數等。constexpr函數可以在編譯期執行(參數也都是 constexpr 時)。constexpr函數也可以運行期調用;consteval才是必須編譯期調用。const強調"不可修改",constexpr強調"編譯期求值"。- C++17 起 lambda 可以用
constexpr。 static_assert+ constexpr 函數 = 編譯期安全網。