C++中的const和constexpr詳解
C++中的const可用于修飾變量、函數(shù),且在不同的地方有著不同的含義,現(xiàn)總結(jié)如下。
const的語(yǔ)義
C++中的const的目的是通過(guò)編譯器來(lái)保證對(duì)象的常量性,強(qiáng)制編譯器將所有可能違背const對(duì)象的常量性的操作都視為error。
對(duì)象的常量性可以分為兩種:物理常量性(即每個(gè)bit都不可改變)和邏輯常量性(即對(duì)象的表現(xiàn)保持不變)。C++中采用的是物理常量性,例如下面的例子:
struct A {
int *ptr;
};
int k = 5, r = 6;
const A a = {&k};
a.ptr = &r; // !error
*a.ptr = 7; // no error
a是const對(duì)象,則對(duì)a的任何成員進(jìn)行賦值都會(huì)被視為error,但如果不改動(dòng)ptr,而是改動(dòng)ptr指向的對(duì)象,編譯器就不會(huì)報(bào)錯(cuò)。這實(shí)際上違背了邏輯常量性,因?yàn)锳的表現(xiàn)已經(jīng)改變了!
邏輯常量性的另一個(gè)特點(diǎn)是,const對(duì)象中可以有某些用戶(hù)不可見(jiàn)的域,改變它們不會(huì)違背邏輯常量性。Effective C++中的例子是:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock
bool lengthIsValid; // whether length is currently valid
};
CTextBlock對(duì)象每次調(diào)用length方法后,都會(huì)將當(dāng)前的長(zhǎng)度緩存到textLength成員中,而lengthIsValid對(duì)象則表示緩存的有效性。這個(gè)場(chǎng)景中textLength和lengthIsValid如果改變了,其實(shí)是不違背CTextBlock對(duì)象的邏輯常量性的,但因?yàn)楦淖兞藢?duì)象中的某些bit,就會(huì)被編譯器阻止。C++中為了解決此問(wèn)題,增加了mutable關(guān)鍵字。
本部分總結(jié):C++中const的語(yǔ)義是保證物理常量性,但通過(guò)mutable關(guān)鍵字可以支持一部分的邏輯常量性。
const修飾變量
如上節(jié)所述,用const修飾變量的語(yǔ)義是要求編譯器去阻止所有對(duì)該變量的賦值行為。因此,必須在const變量初始化時(shí)就提供給它初值:
const int i; i = 5; // !error const int j = 10; // ok
這個(gè)初值可以是編譯時(shí)即確定的值,也可以是運(yùn)行期才確定的值。如果給整數(shù)類(lèi)型的const變量一個(gè)編譯時(shí)初值,那么可以用這個(gè)變量作為聲明數(shù)組時(shí)的長(zhǎng)度:
const int COMPILE_CONST = 10; const int RunTimeConst = cin.get(); int a1[COMPLIE_CONST]; // ok in C++ and error in C int a2[RunTimeConst]; // !error in C++
因?yàn)镃++編譯器可以將數(shù)組長(zhǎng)度中出現(xiàn)的編譯時(shí)常量直接替換為其字面值,相當(dāng)于自動(dòng)的宏替換。(gcc驗(yàn)證發(fā)現(xiàn),只有數(shù)組長(zhǎng)度那里直接做了替換,而其它用COMPILE_CONST賦值的地方并沒(méi)有進(jìn)行替換。)
文件域的const變量默認(rèn)是文件內(nèi)可見(jiàn)的,如果需要在b.cpp中使用a.cpp中的const變量M,需要在M的初始化處增加extern:
//a.cpp extern const int M = 20; //b.cpp extern const int M;
一般認(rèn)為將變量的定義放在.h文件中會(huì)導(dǎo)致所有include該.h文件的.cpp文件都有此變量的定義,在鏈接時(shí)會(huì)造成沖突。但將const變量的定義放在.h文件中是可以的,編譯器會(huì)將這個(gè)變量放入每個(gè).cpp文件的匿名namespace中,因而屬于是不同變量,不會(huì)造成鏈接沖突。(注意:但如果頭文件中的const量的初始值依賴(lài)于某個(gè)函數(shù),而每次調(diào)用此函數(shù)的返回值不固定的話(huà),會(huì)導(dǎo)致不同的編譯單元中看到的該const量的值不相等。猜測(cè):此時(shí)將該const量作為某個(gè)類(lèi)的static成員可能會(huì)解決此問(wèn)題。)
const修飾指針與引用
const修飾引用時(shí),其意義與修飾變量相同。但const在修飾指針時(shí),規(guī)則就有些復(fù)雜了。
簡(jiǎn)單的說(shuō),可以將指針變量的類(lèi)型按變量名左邊最近的‘*'分成兩部分,右邊的部分表示指針變量自己的性質(zhì),而左邊的部分則表示它指向元素的性質(zhì):
const int *p1; // p1 is a non-const pointer and points to a const int int * const p2; // p2 is a const pointer and points to a non-const int const int * const p3; // p3 is a const pointer and points to a const it const int *pa1[10]; // pa1 is an array and contains 10 non-const pointer point to a const int int * const pa2[10]; // pa2 is an array and contains 10 const pointer point to a non-const int const int (* p4)[10]; // p4 is a non-const pointer and points to an array contains 10 const int const int (*pf)(); // pf is a non-const pointer and points to a function which has no arguments and returns a const int ...
const指針的解讀規(guī)則差不多就是這些了……
指針自身為const表示不可對(duì)該指針進(jìn)行賦值,而指向物為const則表示不可對(duì)其指向進(jìn)行賦值。因此可以將引用看成是一個(gè)自身為const的指針,而const引用則是const Type * const指針。
指向?yàn)閏onst的指針是不可以賦值給指向?yàn)榉莄onst的指針,const引用也不可以賦值給非const引用,但反過(guò)來(lái)就沒(méi)有問(wèn)題了,這也是為了保證const語(yǔ)義不被破壞。
可以用const_cast來(lái)去掉某個(gè)指針或引用的const性質(zhì),或者用static_cast來(lái)為某個(gè)非const指針或引用加上const性質(zhì):
int i; const int *cp = &i; int *p = const_cast<int *>(cp); const int *cp2 = static_cast<const int *>(p); // here the static_cast is optional
C++類(lèi)中的this指針就是一個(gè)自身為const的指針,而類(lèi)的const方法中的this指針則是自身和指向都為const的指針。
類(lèi)中的const成員變量
類(lèi)中的const成員變量可分為兩種:非static常量和static常量。
非static常量:
類(lèi)中的非static常量必須在構(gòu)造函數(shù)的初始化列表中進(jìn)行初始化,因?yàn)轭?lèi)中的非static成員是在進(jìn)入構(gòu)造函數(shù)的函數(shù)體之前就要構(gòu)造完成的,而const常量在構(gòu)造時(shí)就必須初始化,構(gòu)造后的賦值會(huì)被編譯器阻止。
class B {
public:
B(): name("aaa") {
name = "bbb"; // !error
}
private:
const std::string name;
};
static常量:
static常量是在類(lèi)中直接聲明的,但要在類(lèi)外進(jìn)行唯一的定義和初始值,常用的方法是在對(duì)應(yīng)的.cpp中包含類(lèi)的static常量的定義:
// a.h
class A {
...
static const std::string name;
};
// a.cpp
const std::string A::name("aaa");
一個(gè)特例是,如果static常量的類(lèi)型是內(nèi)置的整數(shù)類(lèi)型,如char、int、size_t等,那么可以在類(lèi)中直接給出初始值,且不需要在類(lèi)外再進(jìn)行定義了。編譯器會(huì)將這種static常量直接替換為相應(yīng)的初始值,相當(dāng)于宏替換。但如果在代碼中我們像正常變量那樣使用這個(gè)static常量,如取它的地址,而不是像宏一樣只使用它的值,那么我們還是需要在類(lèi)外給它提供一個(gè)定義,但不需要初始值了(因?yàn)樵诼暶魈幰呀?jīng)有了)。
// a.h
class A {
...
static const int SIZE = 50;
};
// a.cpp
const int A::SIZE = 50; // if use SIZE as a variable, not a macro
const修飾函數(shù)
C++中可以用const去修飾一個(gè)類(lèi)的非static成員函數(shù),其語(yǔ)義是保證該函數(shù)所對(duì)應(yīng)的對(duì)象本身的const性。在const成員函數(shù)中,所有可能違背this指針const性(const成員函數(shù)中的this指針是一個(gè)雙const指針)的操作都會(huì)被阻止,如對(duì)其它成員變量的賦值以及調(diào)用它們的非const方法、調(diào)用對(duì)象本身的非const方法。但對(duì)一個(gè)聲明為mutable的成員變量所做的任何操作都不會(huì)被阻止。這里保證了一定的邏輯常量性。
另外,const修飾函數(shù)時(shí)還會(huì)參與到函數(shù)的重載中,即通過(guò)const對(duì)象、const指針或引用調(diào)用方法時(shí),優(yōu)先調(diào)用const方法。
class A {
public:
int &operator[](int i) {
++cachedReadCount;
return data[i];
}
const int &operator[](int i) const {
++size; // !error
--size; // !error
++cachedReadCount; // ok
return data[i];
}
private:
int size;
mutable cachedReadCount;
std::vector<int> data;
};
A &a = ...;
const A &ca = ...;
int i = a[0]; // call operator[]
int j = ca[0]; // call const operator[]
a[0] = 2; // ok
ca[0] = 2; // !error
這個(gè)例子中,如果兩個(gè)版本的operator[]有著基本相同的代碼,可以考慮在其中一個(gè)函數(shù)中去調(diào)用另一個(gè)函數(shù)來(lái)實(shí)現(xiàn)代碼的重用(參考Effective C++)。這里我們只能用非const版本去調(diào)用const版本。
int &A::operator[](int i) {
return const_cast<int &>(static_cast<const A &>(*this).operator[](i));
}
其中為了避免調(diào)用自身導(dǎo)致死循環(huán),首先要將*this轉(zhuǎn)型為const A &,可以使用static_cast來(lái)完成。而在獲取到const operator[]的返回值后,還要手動(dòng)去掉它的const,可以使用const_cast來(lái)完成。一般來(lái)說(shuō)const_cast是不推薦使用的,但這里我們明確知道我們處理的對(duì)象其實(shí)是非const的,那么這里使用const_cast就是安全的。
constexpr
constexpr是C++11中新增的關(guān)鍵字,其語(yǔ)義是“常量表達(dá)式”,也就是在編譯期可求值的表達(dá)式。最基礎(chǔ)的常量表達(dá)式就是字面值或全局變量/函數(shù)的地址或sizeof等關(guān)鍵字返回的結(jié)果,而其它常量表達(dá)式都是由基礎(chǔ)表達(dá)式通過(guò)各種確定的運(yùn)算得到的。constexpr值可用于enum、switch、數(shù)組長(zhǎng)度等場(chǎng)合。
constexpr所修飾的變量一定是編譯期可求值的,所修飾的函數(shù)在其所有參數(shù)都是constexpr時(shí),一定會(huì)返回constexpr。
constexpr int Inc(int i) {
return i + 1;
}
constexpr int a = Inc(1); // ok
constexpr int b = Inc(cin.get()); // !error
constexpr int c = a * 2 + 1; // ok
constexpr還能用于修飾類(lèi)的構(gòu)造函數(shù),即保證如果提供給該構(gòu)造函數(shù)的參數(shù)都是constexpr,那么產(chǎn)生的對(duì)象中的所有成員都會(huì)是constexpr,該對(duì)象也就是constexpr對(duì)象了,可用于各種只能使用constexpr的場(chǎng)合。注意,constexpr構(gòu)造函數(shù)必須有一個(gè)空的函數(shù)體,即所有成員變量的初始化都放到初始化列表中。
struct A {
constexpr A(int xx, int yy): x(xx), y(yy) {}
int x, y;
};
constexpr A a(1, 2);
enum {SIZE_X = a.x, SIZE_Y = a.y};
constexpr的好處:
是一種很強(qiáng)的約束,更好地保證程序的正確語(yǔ)義不被破壞。
編譯器可以在編譯期對(duì)constexpr的代碼進(jìn)行非常大的優(yōu)化,比如將用到的constexpr表達(dá)式都直接替換成最終結(jié)果等。
相比宏來(lái)說(shuō),沒(méi)有額外的開(kāi)銷(xiāo),但更安全可靠。
相關(guān)文章
C++構(gòu)造析構(gòu)賦值運(yùn)算函數(shù)應(yīng)用詳解
構(gòu)造函數(shù)主要作用在于創(chuàng)建對(duì)象時(shí)為對(duì)象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動(dòng)調(diào)用,無(wú)須手動(dòng)調(diào)用;析構(gòu)函數(shù)主要作用在于對(duì)象銷(xiāo)毀前系統(tǒng)自動(dòng)調(diào)用,執(zhí)行一 些清理工作2022-09-09
C語(yǔ)言中K-means算法實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言中K-means算法的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
Qt數(shù)據(jù)庫(kù)應(yīng)用之實(shí)現(xiàn)圖片轉(zhuǎn)pdf
這篇文章主要為大家詳細(xì)介紹了如何利用Qt實(shí)現(xiàn)圖片轉(zhuǎn)pdf功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定參考價(jià)值,需要的可以了解一下2022-06-06
C語(yǔ)言之素?cái)?shù)(質(zhì)數(shù))的判斷以及輸出
這篇文章主要介紹了C語(yǔ)言之素?cái)?shù)(質(zhì)數(shù))的判斷以及輸出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
C語(yǔ)言中使用qsort函數(shù)對(duì)自定義結(jié)構(gòu)體數(shù)組進(jìn)行排序
這篇文章主要介紹了C語(yǔ)言中使用qsort函數(shù)對(duì)自定義結(jié)構(gòu)體數(shù)組進(jìn)行排序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
C++處理輸入字符串并轉(zhuǎn)為數(shù)組的操作
這篇文章主要介紹了C++處理輸入字符串并轉(zhuǎn)為數(shù)組的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01
解決gcc編譯報(bào)錯(cuò)unknown type name ‘bool‘問(wèn)題
這篇文章主要介紹了解決gcc編譯報(bào)錯(cuò)unknown type name ‘bool‘問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07

