C++中關(guān)鍵字constexpr的實現(xiàn)示例
constexpr 是 C++11 引入并在后續(xù)標準(C++14/C++17/C++20)中持續(xù)增強的關(guān)鍵字,其核心作用是在編譯期計算常量或表達式,既保證了編譯期的安全性檢查,又能消除運行期計算的開銷,是實現(xiàn)“零成本抽象”和編譯期元編程的關(guān)鍵技術(shù)。
一、constexpr 的本質(zhì)與核心目標
constexpr 的字面意思是“常量表達式(constant expression)”,它的設(shè)計目標有兩個核心:
- 編譯期確定值:讓變量、函數(shù)或?qū)ο蟮娜≈翟诰幾g階段就能計算完成,避免運行時的冗余計算(如重復(fù)的數(shù)學(xué)運算、常量初始化)。
- 強類型安全檢查:強制編譯器驗證“是否滿足編譯期計算條件”,若存在運行時依賴(如動態(tài)輸入、未初始化變量),則直接報錯,避免潛在的邏輯錯誤。
從本質(zhì)上看,constexpr 是對“常量”概念的強化——它不僅要求值“不可修改”,更要求值“可在編譯期確定”,這是它與傳統(tǒng) const 最核心的區(qū)別。
二、constexpr 變量:編譯期初始化的常量
constexpr 最基礎(chǔ)的用法是修飾變量,要求變量在編譯期完成初始化,且后續(xù)不可修改。
1. 變量聲明的核心要求
- 必須在聲明時初始化,且初始化表達式必須是常量表達式(即無運行時依賴的表達式)。
- 變量類型必須是“字面類型(Literal Type)”——即能通過 constexpr 構(gòu)造函數(shù)初始化、無虛函數(shù)/虛基類、成員均為字面類型的類型(如
int、double、數(shù)組、滿足條件的自定義類)。
正確示例
// 1. 基本類型:編譯期計算 3+2 的結(jié)果(值為5) constexpr int a = 3 + 2; // 2. 數(shù)組:大小由 constexpr 變量確定(編譯期已知) constexpr size_t arr_size = 10; int arr[arr_size]; // 合法,數(shù)組大小編譯期確定 // 3. 常量表達式嵌套:依賴其他 constexpr 變量 constexpr int b = a * 4; // 編譯期計算 5*4=20
錯誤示例
int x = 5; // 錯誤:初始化表達式依賴運行時變量 x(非常量表達式) constexpr int c = x + 3; // 錯誤:未初始化(constexpr 變量必須聲明時初始化) constexpr double d;
2. 修飾指針與引用的特殊規(guī)則
constexpr 修飾指針/引用時,需明確“修飾的是指針本身”還是“指向的對象”,規(guī)則與 const 類似,但要求更嚴格(必須編譯期確定地址):
- constexpr 指針:指針本身是編譯期常量(地址固定),指向的對象是否可修改需額外用
const聲明。int num = 10; // 錯誤:num 是棧上變量(地址運行時確定),無法作為 constexpr 指針的目標 constexpr int* p1 = # // 正確:全局變量地址編譯期確定,p2 是 constexpr 指針(地址固定) int global_num = 20; constexpr int* p2 = &global_num; // 正確:constexpr 指針 + const 對象(指針地址固定,指向的內(nèi)容不可改) constexpr const int* p3 = &global_num;
- constexpr 引用:引用的目標必須是編譯期常量(地址固定),且引用本身不可重新綁定(與
const引用一致,但要求更高)。// 正確:引用目標是 constexpr 變量(編譯期常量) constexpr int e = 5; constexpr const int& ref = e;
三、constexpr 函數(shù):編譯期可執(zhí)行的函數(shù)
constexpr 函數(shù)是支持“編譯期調(diào)用”的函數(shù)——當(dāng)函數(shù)的實參是常量表達式時,函數(shù)會在編譯期計算結(jié)果;若實參是運行時變量,則退化為普通函數(shù)在運行期執(zhí)行(即“兩態(tài)性”)。
const修飾的函數(shù)(修飾函數(shù)返回值或者函數(shù)類型)不可以因為constexpr的存才而刪除,起到的作用不同,constexpr修飾作用可退回普通函數(shù),但是const修飾的函數(shù)必定是常函數(shù)。
1. 函數(shù)聲明的核心要求(隨標準演進放寬)
constexpr 函數(shù)的限制在 C++11 到 C++20 中逐步放寬,核心要求如下:
| 標準版本 | 核心限制 | 允許的操作 |
|---|---|---|
| C++11 | 1. 函數(shù)體僅允許 return 語句 2. 不能有局部變量/循環(huán) 3. 返回值和參數(shù)必須是字面類型 | 僅常量表達式計算、return |
| C++14 | 1. 允許定義局部變量(需是字面類型,可初始化) 2. 允許循環(huán)(for/while) 3. 允許條件判斷(if/else) | 局部變量、循環(huán)、條件判斷、常量計算 |
| C++17 | 1. 允許 constexpr lambda(嵌套使用) 2. 允許使用部分標準庫函數(shù)(如 std::string_view) | lambda、部分標準庫調(diào)用 |
| C++20 | 1. 允許動態(tài)內(nèi)存分配(new/delete,但需在編譯期釋放) 2. 允許使用 std::vector/std::string(部分實現(xiàn)) 3. 允許 try-catch(僅編譯期異常) | 動態(tài)內(nèi)存(編譯期釋放)、復(fù)雜容器、異常處理 |
2. 典型示例:編譯期計算階乘
// C++14 及以上:constexpr 函數(shù)支持循環(huán)
constexpr int factorial(int n) {
if (n < 0) throw "n must be non-negative"; // C++20 允許 try-catch
int result = 1;
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
// 1. 編譯期調(diào)用:實參是常量表達式(5),結(jié)果在編譯期計算為 120
constexpr int f5 = factorial(5);
// 2. 運行期調(diào)用:實參是運行時變量(x),結(jié)果在運行期計算
int x = 6;
int f6 = factorial(x);
3. 關(guān)鍵特性:兩態(tài)性與編譯期驗證
- 兩態(tài)性:constexpr 函數(shù)并非“必須編譯期執(zhí)行”,而是“可編譯期執(zhí)行”。實參是常量表達式時編譯期計算,否則運行期計算,兼顧靈活性與性能。
- 編譯期驗證:若強制要求函數(shù)在編譯期執(zhí)行(如賦值給 constexpr 變量),編譯器會嚴格檢查——若函數(shù)存在無法編譯期執(zhí)行的操作(如依賴運行時輸入),則直接報錯。
四、constexpr 類與對象:編譯期構(gòu)造的自定義類型
C++11 允許類通過 constexpr 構(gòu)造函數(shù) 創(chuàng)建“編譯期對象”,即對象的所有成員在編譯期初始化,且對象的成員函數(shù)可通過 constexpr 修飾實現(xiàn)編譯期調(diào)用。
1. constexpr 構(gòu)造函數(shù)的要求
- 必須初始化類的所有非靜態(tài)成員(確保無未初始化成員)。
- C++11 中函數(shù)體必須為空(僅通過初始化列表初始化);C++14 及以上允許簡單函數(shù)體(如條件判斷)。
- 構(gòu)造函數(shù)的參數(shù)必須是字面類型。
示例:編譯期可構(gòu)造的 Point 類
class Point {
private:
int x_, y_;
public:
// C++11 風(fēng)格:空函數(shù)體,通過初始化列表初始化成員
constexpr Point(int x, int y) : x_(x), y_(y) {}
// constexpr 成員函數(shù):編譯期計算兩點距離的平方
constexpr int distance_sq(const Point& other) const {
int dx = x_ - other.x_;
int dy = y_ - other.y_;
return dx*dx + dy*dy;
}
// 普通成員函數(shù):僅運行期調(diào)用
void print() const {
std::cout << "(" << x_ << "," << y_ << ")\n";
}
};
// 1. 編譯期創(chuàng)建對象:構(gòu)造函數(shù)實參是常量表達式
constexpr Point p1(1, 2);
constexpr Point p2(4, 6);
// 2. 編譯期調(diào)用成員函數(shù):計算距離平方(結(jié)果 25)
constexpr int dist_sq = p1.distance_sq(p2);
// 3. 運行期調(diào)用普通成員函數(shù)
p1.print();
2. constexpr 對象的特性
- constexpr 對象的所有非靜態(tài)成員均為編譯期常量,可直接用于常量表達式(如作為 constexpr 函數(shù)的實參)。
- constexpr 對象的
this指針在編譯期可見,因此其 constexpr 成員函數(shù)可直接訪問成員并完成編譯期計算。
五、C++17/C++20 關(guān)鍵增強:constexpr lambda、consteval 與 constinit
隨著標準演進,constexpr 的能力大幅擴展
1. C++17:constexpr lambda
C++17 允許 lambda 表達式通過 constexpr 修飾(或隱式滿足 constexpr 條件),使其可在編譯期執(zhí)行。
示例:編譯期使用 lambda 計算數(shù)組總和
#include <array>
constexpr auto sum_array = [](const auto& arr) {
int sum = 0;
for (auto val : arr) {
sum += val;
}
return sum;
};
// 編譯期計算數(shù)組總和:sum = 1+2+3+4+5 = 15
constexpr std::array<int, 5> arr = {1,2,3,4,5};
constexpr int total = sum_array(arr);
2. C++20:consteval 與 constinit
C++20 新增兩個關(guān)鍵字,進一步細化“編譯期計算”的語義,與 constexpr 形成互補:
- consteval:強制函數(shù)“必須在編譯期執(zhí)行”(即“立即函數(shù)”),若無法編譯期計算(如實參是運行時變量),直接報錯。
// consteval 函數(shù):必須編譯期執(zhí)行 consteval int square(int n) { return n * n; } constexpr int s4 = square(4); // 正確:編譯期計算(16) int y = 5; // 錯誤:實參 y 是運行時變量,無法編譯期執(zhí)行 int s5 = square(y); - constinit:確保變量“在編譯期初始化”(僅適用于靜態(tài)/線程局部變量),但不要求變量是“常量”(即后續(xù)可修改,只要初始化在編譯期完成)。
// 錯誤:靜態(tài)變量默認運行時初始化(全局變量除外) static int static_num = 10; // 正確:constinit 強制靜態(tài)變量編譯期初始化(值 20) constinit static int constinit_num = 20; constinit_num = 30; // 合法:constinit 僅限制初始化,不限制后續(xù)修改
constexpr、consteval、constinit 對比
| 關(guān)鍵字 | 核心語義 | 是否允許運行期執(zhí)行 | 適用場景 |
|---|---|---|---|
| constexpr | 可編譯期執(zhí)行(兩態(tài)性) | 是(實參為運行時變量時) | 兼顧編譯期計算與運行期靈活調(diào)用 |
| consteval | 必須編譯期執(zhí)行(強制) | 否(否則報錯) | 確保零運行時開銷(如編譯期哈希) |
| constinit | 強制編譯期初始化 | 是(變量可后續(xù)修改) | 靜態(tài)變量的編譯期初始化(如全局配置) |
六、constexpr 的核心應(yīng)用場景
constexpr 并非“語法糖”,而是解決實際問題的工具,核心應(yīng)用場景包括:
1. 編譯期計算:消除運行時開銷
對于固定邏輯(如數(shù)學(xué)公式、常量配置),通過 constexpr 在編譯期計算結(jié)果,避免運行時重復(fù)計算。例如:
- 編譯期計算數(shù)組大小(替代
sizeof(arr)/sizeof(arr[0]))。 - 編譯期生成哈希值(如字符串字面量的哈希,避免運行時哈希計算)。
2. 靜態(tài)斷言(static_assert):編譯期合法性檢查
static_assert 的條件必須是常量表達式,constexpr 可提供復(fù)雜的編譯期條件判斷,實現(xiàn)更靈活的斷言。例如:
// 編譯期檢查模板參數(shù)是否為偶數(shù)
template <int N>
void process() {
static_assert(N % 2 == 0, "N must be even"); // 條件依賴 constexpr 計算
}
process<4>(); // 正確:4 是偶數(shù)
// process<5>(); // 錯誤:編譯期斷言失敗,提示 "N must be even"
3. 編譯期元編程:生成代碼邏輯
結(jié)合模板與 constexpr,可在編譯期生成代碼(如循環(huán)展開、類型判斷),實現(xiàn)“元編程”。例如:
- 編譯期遍歷數(shù)組并計算結(jié)果(無需運行時循環(huán))。
- 編譯期根據(jù)類型生成不同的處理邏輯(替代運行時
if-else)。
4. 常量表達式接口:增強類型安全
通過 constexpr 定義接口(如 constexpr 函數(shù)、constexpr 類),強制調(diào)用者使用編譯期常量,避免運行時錯誤。例如:
- 自定義數(shù)值類型(如角度、長度),通過 constexpr 構(gòu)造函數(shù)確保輸入合法(如角度范圍 0-360)。
七、常見誤區(qū)與注意事項
- “constexpr 變量一定在編譯期存儲”:錯誤。constexpr 僅要求“值在編譯期確定”,存儲位置仍由編譯器決定(如可能存儲在數(shù)據(jù)段,也可能直接內(nèi)聯(lián)到代碼中)。
- “constexpr 函數(shù)只能返回常量”:錯誤。constexpr 函數(shù)的返回值是否為常量,取決于實參——實參是常量表達式時返回常量,實參是運行時變量時返回普通值。
- “constexpr 與 const 完全等價”:錯誤。
const僅表示“值不可修改”,不要求“值在編譯期確定”(如const int a = rand();是合法的,但constexpr int a = rand();是錯誤的);而 constexpr 同時要求“不可修改”和“編譯期確定”。 - “C++20 后 constexpr 可隨意使用動態(tài)內(nèi)存”:錯誤。C++20 允許 constexpr 函數(shù)使用
new,但必須在編譯期通過delete釋放(否則編譯器報錯),無法將動態(tài)內(nèi)存泄露到運行時。
補充const與constexpr
const 和 constexpr 修飾函數(shù)時的作用和場景完全不同,核心區(qū)別在于:const 關(guān)注函數(shù)是否修改對象狀態(tài)(僅用于成員函數(shù)),而 constexpr 關(guān)注函數(shù)是否能在編譯時求值(可用于任意函數(shù))。
1.const修飾函數(shù)(僅適用于類的非靜態(tài)成員函數(shù))
作用:聲明該成員函數(shù)是“常量成員函數(shù)”,承諾不會修改對象的非靜態(tài)數(shù)據(jù)成員,也不能調(diào)用非
const的成員函數(shù)(防止間接修改對象)。
本質(zhì)是對函數(shù)的“行為約束”,確保調(diào)用該函數(shù)時對象狀態(tài)不變,屬于運行時的類型安全保障。語法:
const放在函數(shù)參數(shù)列表后(const成員函數(shù)的標志)。class A { private: int x; public: // const 成員函數(shù):承諾不修改 x,也不能調(diào)用非 const 函數(shù) int getX() const { // x = 10; // 編譯錯誤:const 函數(shù)不能修改成員 return x; } void setX(int val) { // 非 const 函數(shù):可以修改成員 x = val; } };適用場景:
當(dāng)需要獲取對象狀態(tài)(如訪問器getter)但不修改它時,用const修飾,確保函數(shù)的“只讀”行為。
例如:const A a; a.getX();合法(const對象只能調(diào)用const成員函數(shù)),但a.setX(5);會報錯。
2.constexpr修飾函數(shù)(適用于任意函數(shù):普通函數(shù)、成員函數(shù)、構(gòu)造函數(shù)等)
作用:聲明該函數(shù)是“常量表達式函數(shù)”,可以在編譯時被求值(前提是輸入?yún)?shù)為常量表達式)。
本質(zhì)是對函數(shù)的“計算時機約束”,允許函數(shù)結(jié)果作為編譯期常量使用(如模板參數(shù)、數(shù)組大小等),屬于編譯時的計算能力保障。語法:
constexpr放在函數(shù)返回類型前。// 普通 constexpr 函數(shù):編譯時可求值 constexpr int add(int a, int b) { return a + b; } class B { private: int y; public: // constexpr 構(gòu)造函數(shù):允許創(chuàng)建編譯期對象 constexpr B(int val) : y(val) {} // constexpr 成員函數(shù):編譯時可求值(需滿足條件) constexpr int getY() const { // 可同時是 const(雙重約束) return y; } };適用場景:
當(dāng)需要在編譯時計算結(jié)果(如常量、模板參數(shù))時,用constexpr修飾。例如:constexpr int sum = add(3, 5); // 編譯時計算,sum 是編譯期常量(8) constexpr B b(10); // 編譯期創(chuàng)建對象 int arr[b.getY()]; // 合法:數(shù)組大小是編譯期常量(10)
注意:
constexpr函數(shù)并非只能在編譯時調(diào)用,當(dāng)輸入?yún)?shù)是運行時變量時,它也能像普通函數(shù)一樣在運行時調(diào)用。
3. 核心對比表
| 維度 | const 修飾函數(shù) | constexpr 修飾函數(shù) |
|---|---|---|
| 適用范圍 | 僅類的非靜態(tài)成員函數(shù) | 任意函數(shù)(普通函數(shù)、成員函數(shù)、構(gòu)造函數(shù)等) |
| 核心作用 | 保證函數(shù)不修改對象狀態(tài)(只讀約束) | 保證函數(shù)可在編譯時求值(編譯期計算) |
| 本質(zhì) | 運行時的對象狀態(tài)保護 | 編譯時的計算能力支持 |
| 與常量表達式的關(guān)系 | 無關(guān)(結(jié)果不能直接作為編譯期常量) | 直接相關(guān)(結(jié)果可作為編譯期常量) |
| 能否同時使用 | 可以(constexpr const 成員函數(shù),雙重約束) | 無沖突,constexpr 不影響 const 的作用 |
- 用
const修飾成員函數(shù):告訴編譯器和使用者“這個函數(shù)不會改對象,放心在const對象上調(diào)用”。 - 用
constexpr修飾函數(shù):告訴編譯器“這個函數(shù)可以在編譯時算,給我編譯期優(yōu)化的能力”。 - 兩者可以共存(如
constexpr const成員函數(shù)),此時函數(shù)既保證不修改對象,又能在編譯時求值。
到此這篇關(guān)于C++中關(guān)鍵字constexpr的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)C++ 關(guān)鍵字constexpr內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++/Php/Python 語言執(zhí)行shell命令的方法(推薦)
下面小編就為大家?guī)硪黄狢++/Php/Python 語言執(zhí)行shell命令的方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03
C++編程中隊內(nèi)聯(lián)函數(shù)的理解和使用
這篇文章主要介紹了C++編程中隊內(nèi)聯(lián)函數(shù)的理解和使用,簡單舉例講解了inline關(guān)鍵字引出的內(nèi)聯(lián)函數(shù)的相關(guān)知識,需要的朋友可以參考下2016-01-01

