一文掌握C++ const與constexpr及區(qū)別
1. constexpr:常量表達(dá)式
所謂常量表達(dá)式,指的就是由多個(≥1)常量組成的表達(dá)式。換句話說,如果表達(dá)式中的成員都是常量,那么該表達(dá)式就是一個常量表達(dá)式。這也意味著,常量表達(dá)式一旦確定,其值將無法修改。
實(shí)際開發(fā)中,我們經(jīng)常會用到常量表達(dá)式。以定義數(shù)組為例,數(shù)組的長度就必須是一個常量表達(dá)式:
// 1) int url[10];//正確 // 2) int url[6 + 4];//正確 // 3) int length = 6; int url[length];//錯誤,length是變量
上述代碼演示了 3 種定義 url 數(shù)組的方式,其中第 1、2 種定義 url 數(shù)組時,長度分別為 10 和 6+4,顯然它們都是常量表達(dá)式,可以用于表示數(shù)組的長度;第 3 種 url 數(shù)組的長度為 length,它是變量而非常量,因此不是一個常量表達(dá)式,無法用于表示數(shù)組的長度。
常量表達(dá)式的應(yīng)用場景還有很多,比如匿名枚舉、switch-case 結(jié)構(gòu)中的 case 表達(dá)式等,感興趣的讀者可自行編碼測試,這里不再過多舉例。
我們知道,C++ 程序的執(zhí)行過程大致要經(jīng)歷編譯、鏈接、運(yùn)行這 3 個階段。值得一提的是,常量表達(dá)式和非常量表達(dá)式的計(jì)算時機(jī)不同,非常量表達(dá)式只能在程序運(yùn)行階段計(jì)算出結(jié)果;而常量表達(dá)式的計(jì)算往往發(fā)生在程序的編譯階段,這可以極大提高程序的執(zhí)行效率,因?yàn)楸磉_(dá)式只需要在編譯階段計(jì)算一次,節(jié)省了每次程序運(yùn)行時都需要計(jì)算一次的時間。
對于用 C++ 編寫的程序,性能往往是永恒的追求。那么在實(shí)際開發(fā)中,如何才能判定一個表達(dá)式是否為常量表達(dá)式,進(jìn)而獲得在編譯階段即可執(zhí)行的“特權(quán)”呢?除了人為判定外,C++11 標(biāo)準(zhǔn)還提供有 constexpr
關(guān)鍵字。
constexpr
關(guān)鍵字的功能是使指定的常量表達(dá)式獲得在程序編譯階段計(jì)算出結(jié)果的能力,而不必等到程序運(yùn)行階段。C++ 11 標(biāo)準(zhǔn)中,constexpr
可用于修飾普通變量、函數(shù)(包括模板函數(shù))以及類的構(gòu)造函數(shù)。
注意:獲得在編譯階段計(jì)算出結(jié)果的能力,并不代表 constexpr修飾的表達(dá)式一定會在程序編譯階段被執(zhí)行,具體的計(jì)算時機(jī)還是編譯器說了算。
1.1 constexpr修飾普通變量
C++11 標(biāo)準(zhǔn)中,定義變量時可以用constexpr
修飾,從而使該變量獲得在編譯階段即可計(jì)算出結(jié)果的能力。
注意:使用 constexpr
修改普通變量時,變量必須經(jīng)過初始化且初始值必須是一個常量表達(dá)式。舉個例子:
#include <iostream> using namespace std; int main() { constexpr int num = 1 + 2 + 3; int url[num] = {1,2,3,4,5,6}; couts<< url[1] << endl; return 0; }
程序執(zhí)行結(jié)果為:
2
讀者可嘗試將 constexpr
刪除,此時編譯器會提示“url[num] 定義中 num 不可用作常量”。
可以看到,程序第 6 行使用 constexpr
修飾 num 變量,同時將 “1+2+3” 這個常量表達(dá)式賦值給 num。由此,編譯器就可以在編譯時期對 num 這個表達(dá)式進(jìn)行計(jì)算,因?yàn)?num 可以作為定義數(shù)組時的長度。
有讀者可能發(fā)現(xiàn),將此示例程序中的 constexpr
用const
關(guān)鍵字替換也可以正常執(zhí)行,這是因?yàn)?num 的定義同時滿足“num
是 const
常量且使用常量表達(dá)式為其初始化”這 2 個條件,由此編譯器會認(rèn)定 num
是一個常量表達(dá)式。
另外需要重點(diǎn)提出的是,當(dāng)常量表達(dá)式中包含浮點(diǎn)數(shù)時,考慮到程序編譯和運(yùn)行所在的系統(tǒng)環(huán)境可能不同,常量表達(dá)式在編譯階段和運(yùn)行階段計(jì)算出的結(jié)果精度很可能會受到影響,因此 C++11 標(biāo)準(zhǔn)規(guī)定,浮點(diǎn)常量表達(dá)式在編譯階段計(jì)算的精度要至少等于(或者高于)運(yùn)行階段計(jì)算出的精度。
1.2 constexpr修飾函數(shù)
constexpr
還可以用于修飾函數(shù)的返回值,這樣的函數(shù)又稱為“常量表達(dá)式函數(shù)”。
注意,constexpr
并非可以修改任意函數(shù)的返回值。換句話說,一個函數(shù)要想成為常量表達(dá)式函數(shù),必須滿足如下 4 個條件:
1) 該函數(shù)必須有返回值,即函數(shù)的返回值類型不能是 void。
舉個例子:
constexpr void display() { //函數(shù)體 }
像上面這樣定義的返回值類型為 void 的函數(shù),不屬于常量表達(dá)式函數(shù)。原因很簡單,因?yàn)橥ㄟ^類似的函數(shù)根本無法獲得一個常量。
2) 整個函數(shù)的函數(shù)體中,除了可以包含 using
指令、typedef
語句以及 static_assert
斷言外,只能包含一條 return 返回語句。
舉個例子:
constexpr int display(int x) { int ret = 1 + 2 + x; return ret; }
注意,這個函數(shù)是無法通過編譯的,因?yàn)樵摵瘮?shù)的返回值用 constexpr 修飾,但函數(shù)內(nèi)部包含多條語句。
如下是正確的定義 display() 常量表達(dá)式函數(shù)的寫法:
constexpr int display(int x) { //可以添加 using 執(zhí)行、typedef 語句以及 static_assert 斷言 return 1 + 2 + x; }
可以看到,display()
函數(shù)的返回值是用 constexpr
修飾的 int
類型值,且該函數(shù)的函數(shù)體中只包含一個return
語句。
3)return 返回的表達(dá)式必須是常量表達(dá)式。
舉個例子:
#include <iostream> using namespace std; int num = 3; constexpr int display(int x){ return num + x; } int main() { //調(diào)用常量表達(dá)式函數(shù) int a[display(3)] = { 1,2,3,4 }; return 0; }
該程序無法通過編譯,編譯器報(bào)“display(3)
的結(jié)果不是常量”的異常。
常量表達(dá)式函數(shù)的返回值必須是常量表達(dá)式的原因很簡單,如果想在程序編譯階段獲得某個函數(shù)返回的常量,則該函數(shù)的 return 語句中就不能包含程序運(yùn)行階段才能確定值的變量。
注意,在常量表達(dá)式函數(shù)的 return
語句中,不能包含賦值的操作(例如 return x=1
在常量表達(dá)式函數(shù)中不允許的)。另外,用 constexpr
修改函數(shù)時,函數(shù)本身也是支持遞歸的,感興趣的讀者可自行嘗試編碼測試。
4) 函數(shù)在使用之前,必須有對應(yīng)的定義語句。我們知道,函數(shù)的使用分為“聲明”和“定義”兩部分,普通的函數(shù)調(diào)用只需要提前寫好該函數(shù)的聲明部分即可(函數(shù)的定義部分可以放在調(diào)用位置之后甚至其它文件中),但常量表達(dá)式函數(shù)在使用前,必須要有該函數(shù)的定義。
舉個例子:
#include <iostream> using namespace std; //普通函數(shù)的聲明 int noconst_dis(int x); //常量表達(dá)式函數(shù)的聲明 constexpr int display(int x); //常量表達(dá)式函數(shù)的定義 constexpr int display(int x){ return 1 + 2 + x; } int main() { //調(diào)用常量表達(dá)式函數(shù) int a[display(3)] = { 1,2,3,4 }; cout << a[2] << endl; //調(diào)用普通函數(shù) cout << noconst_dis(3) << endl; return 0; } //普通函數(shù)的定義 int noconst_dis(int x) { return 1 + 2 + x; }
程序執(zhí)行結(jié)果為:
3
6
讀者可自行將 display()
常量表達(dá)式函數(shù)的定義調(diào)整到 main()
函數(shù)之后,查看編譯器的報(bào)錯信息。可以看到,普通函數(shù)在調(diào)用時,只需要保證調(diào)用位置之前有相應(yīng)的聲明即可;而常量表達(dá)式函數(shù)則不同,調(diào)用位置之前必須要有該函數(shù)的定義,否則會導(dǎo)致程序編譯失敗。
1.3 constexpr修飾類的構(gòu)造函數(shù)
對于 C++ 內(nèi)置類型的數(shù)據(jù),可以直接用 constexpr
修飾,但如果是自定義的數(shù)據(jù)類型(用 struct 或者 class 實(shí)現(xiàn)),直接用 constexpr
修飾是不行的。
舉個例子:
#include <iostream> using namespace std; //自定義類型的定義 constexpr struct myType { const char* name; int age; //其它結(jié)構(gòu)體成員 }; int main() { constexpr struct myType mt{ "zhangsan", 10 }; cout << mt.name << " " << mt.age << endl; return 0; }
此程序是無法通過編譯的,編譯器會拋出“constexpr不能修飾自定義類型”的異常。
當(dāng)我們想自定義一個可產(chǎn)生常量的類型時,正確的做法是在該類型的內(nèi)部添加一個常量構(gòu)造函數(shù)。例如,修改上面的錯誤示例如下:
#include <iostream> using namespace std; //自定義類型的定義 struct myType { constexpr myType(char *name,int age):name(name),age(age){}; const char* name; int age; //其它結(jié)構(gòu)體成員 }; int main() { constexpr struct myType mt { "zhangsan", 10 }; cout << mt.name << " " << mt.age << endl; return 0; }
程序執(zhí)行結(jié)果為:
zhangsan 10
可以看到,在 myType
結(jié)構(gòu)體中自定義有一個構(gòu)造函數(shù),借助此函數(shù),用 constexpr
修飾的 myType
類型的mt
常量即可通過編譯。
注意,constexpr
修飾類的構(gòu)造函數(shù)時,要求該構(gòu)造函數(shù)的函數(shù)體必須為空,且采用初始化列表的方式為各個成員賦值時,必須使用常量表達(dá)式。
前面提到,constexpr
可用于修飾函數(shù),而類中的成員方法完全可以看做是“位于類這個命名空間中的函數(shù)”,所以 constexpr 也可以修飾類中的成員函數(shù),只不過此函數(shù)必須滿足前面提到的 4 個條件。
舉個例子:
#include <iostream> using namespace std; //自定義類型的定義 class myType { public: constexpr myType(const char *name,int age):name(name),age(age){}; constexpr const char * getname(){ return name; } constexpr int getage(){ return age; } private: const char* name; int age; //其它結(jié)構(gòu)體成員 }; int main() { constexpr struct myType mt { "zhangsan", 10 }; constexpr const char * name = mt.getname(); constexpr int age = mt.getage(); cout << name << " " << age << endl; return 0; }
程序執(zhí)行結(jié)果為:
zhangsan 10
注意,C++11 標(biāo)準(zhǔn)中,不支持用 constexpr 修飾帶有 virtual 的成員方法。
1.4 constexpr修飾模板函數(shù)
C++11 語法中,constexpr
可以修飾模板函數(shù),但由于模板中類型的不確定性,因此模板函數(shù)實(shí)例化后的函數(shù)是否符合常量表達(dá)式函數(shù)的要求也是不確定的。
針對這種情況下,C++11 標(biāo)準(zhǔn)規(guī)定,如果 constexpr
修飾的模板函數(shù)實(shí)例化結(jié)果不滿足常量表達(dá)式函數(shù)的要求,則 constexpr
會被自動忽略,即該函數(shù)就等同于一個普通函數(shù)。
舉個例子:
#include <iostream> using namespace std; //自定義類型的定義 struct myType { const char* name; int age; //其它結(jié)構(gòu)體成員 }; //模板函數(shù) template<typename T> constexpr T dispaly(T t){ return t; } int main() { struct myType stu{"zhangsan",10}; //普通函數(shù) struct myType ret = dispaly(stu); cout << ret.name << " " << ret.age << endl; //常量表達(dá)式函數(shù) constexpr int ret1 = dispaly(10); cout << ret1 << endl; return 0; }
程序執(zhí)行結(jié)果為:
zhangsan 10
10
可以看到,示例程序中定義了一個模板函數(shù) display()
,但由于其返回值類型未定,因此在實(shí)例化之前無法判斷其是否符合常量表達(dá)式函數(shù)的要求.
2. const與constexpr區(qū)別:
一些讀者在學(xué)習(xí)過程中,經(jīng)常會把 const
和 constexpr
搞混,不知道什么時候用 const
,什么時候用 constexpr
。本節(jié)就帶領(lǐng)大家對 const
和 constexpr
做系統(tǒng)地區(qū)分。
有關(guān) constexpr
的功能和用法,您可以閱讀《C++11 constexpr》一節(jié)。
我們知道,constexpr
是 C++ 11 標(biāo)準(zhǔn)新添加的關(guān)鍵字,在此之前(C++ 98/03標(biāo)準(zhǔn))只有const
關(guān)鍵字,其在實(shí)際使用中經(jīng)常會表現(xiàn)出兩種不同的語義。舉個例子:
#include <iostream> #include <array> using namespace std; void dis_1(const int x){ //錯誤,x是只讀的變量 array <int,x> myarr{1,2,3,4,5}; cout << myarr[1] << endl; } void dis_2(){ const int x = 5; array <int,x> myarr{1,2,3,4,5}; cout << myarr[1] << endl; } int main() { dis_1(5); dis_2(); }
可以看到,dis_1()
和 dis_2()
函數(shù)中都包含一個 const int x
,但 dis_1()
函數(shù)中的 x
無法完成初始化 array
容器的任務(wù),而 dis_2()
函數(shù)中的 x
卻可以。
這是因?yàn)椋?code>dis_1() 函數(shù)中的“const int x”
只是想強(qiáng)調(diào) x
是一個只讀的變量,其本質(zhì)仍為變量,無法用來初始化 array
容器;而 dis_2()
函數(shù)中的“const int x”
,表明 x
是一個只讀變量的同時,x
還是一個值為 5 的常量,所以可以用來初始化array
容器。
C++ 11標(biāo)準(zhǔn)中,為了解決const
關(guān)鍵字的雙重語義問題,保留了const
表示“只讀”的語義,而將“常量”的語義劃分給了新添加的 constexpr
關(guān)鍵字。因此 C++11 標(biāo)準(zhǔn)中,建議將const
和 constexpr
的功能區(qū)分開,即凡是表達(dá)“只讀”語義的場景都使用 const
,表達(dá)“常量”語義的場景都使用 constexpr
。
在上面的實(shí)例程序中,dis_2()
函數(shù)中使用 const int x
是不規(guī)范的,應(yīng)使用 constexpr
關(guān)鍵字。
有讀者可能會問,“只讀”不就意味著其不能被修改嗎?答案是否定的,“只讀”和“不允許被修改”之間并沒有必然的聯(lián)系,舉個例子:
#include <iostream> using namespace std; int main() { int a = 10; const int & con_b = a; cout << con_b << endl; a = 20; cout << con_b << endl; }
程序執(zhí)行結(jié)果為:
10
20
可以看到,程序中用 const
修飾了 con_b
變量,表示該變量“只讀”,即無法通過變量自身去修改自己的值。但這并不意味著 con_b
的值不能借助其它變量間接改變,通過改變 a
的值就可以使con_b
的值發(fā)生變化。
在大部分實(shí)際場景中,const
和 constexpr
是可以混用的,例如:
const int a = 5 + 4; constexpr int a = 5 + 4;
它們是完全等價的,都可以在程序的編譯階段計(jì)算出結(jié)果。但在某些場景中,必須明確使用 constexpr
,例如:
#include <iostream> #include <array> using namespace std; constexpr int sqr1(int arg){ return arg*arg; } const int sqr2(int arg){ return arg*arg; } int main() { array<int,sqr1(10)> mylist1;//可以,因?yàn)閟qr1時constexpr函數(shù) array<int,sqr2(10)> mylist1;//不可以,因?yàn)閟qr2不是constexpr函數(shù) return 0; }
其中,因?yàn)?code> sqr2() 函數(shù)的返回值僅有const
修飾,而沒有用更明確的 constexpr
修飾,導(dǎo)致其無法用于初始化 array
容器(只有常量才能初始化array
容器)。
總的來說在 C++ 11 標(biāo)準(zhǔn)中,const
用于為修飾的變量添加“只讀”屬性;而 constexpr
關(guān)鍵字則用于指明其后是一個常量(或者常量表達(dá)式),編譯器在編譯程序時可以順帶將其結(jié)果計(jì)算出來,而無需等到程序運(yùn)行階段,這樣的優(yōu)化極大地提高了程序的執(zhí)行效率。
參考資料: https://blog.csdn.net/lemonxiaoxiao/article/details/128344695
到此這篇關(guān)于一文掌握C++ const與constexpr的文章就介紹到這了,更多相關(guān)C++ const與constexpr內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)酒店客房管理系統(tǒng)課程設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)酒店客房管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03C語言實(shí)現(xiàn)簡易通訊錄(靜態(tài)版本)的代碼分享
這篇文章主要為大家詳細(xì)介紹了如何錄音C語言實(shí)現(xiàn)一個簡易的通訊錄(靜態(tài)版本),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-10-10C++實(shí)現(xiàn)景區(qū)信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)景區(qū)信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01C++缺省參數(shù)與重載函數(shù)(超詳細(xì)!)
無論使用什么語言函數(shù)都是代碼段中必不可少的部分,因此我們有必要深入認(rèn)識一下C++中函數(shù)的兩種特殊用法,缺省參數(shù),函數(shù)重載,這篇文章主要給大家介紹了關(guān)于C++缺省參數(shù)與重載函數(shù)的相關(guān)資料,需要的朋友可以參考下2024-06-06Linux系統(tǒng)下如何使用C++解析json文件詳解
JSON(JavaScript Object Notation, JS 對象簡譜) 是一種輕量級的數(shù)據(jù)交換格式。下面這篇文章主要給大家介紹了關(guān)于Linux系統(tǒng)下如何使用C++解析json文件的相關(guān)資料,需要的朋友可以參考下2021-06-06