const int是什么类型_C++的const语义
背景
我們都知道,const作為修飾符的時候,用來表明這個變量所代表的內(nèi)存不可修改。因此,const修飾的變量必須在定義的時候就完成初始化,不然以后也沒有機會了:
const但是請注意,這個不可修改是編譯期的概念,如果你試圖修改gemfield,那么編譯器就會報錯。而在運行時是沒有const的概念的。事實上,在編譯的時候,編譯器大概率會將用到gemfield的地方直接替換為7030。
有了這個樸素的語法和概念后,我們下面就開始來詳細(xì)介紹C++中const的語義,特別是在C++11的新標(biāo)準(zhǔn)中,我們新增加了constexpr關(guān)鍵字來強化和豐富const語義。
references to const
當(dāng)我們將const概念應(yīng)用到reference類型上時,會產(chǎn)生兩種語義:const reference和reference to const,一個是說自身是const,一個是說綁定/指向的對象是const類型。但是,因為reference自身本來就是初始化后不能修改的,因此天然具備const語義。由此,上述的兩種語義我們只會說第二種,也就是reference to const。
設(shè)想我們要將上面的gemfield綁定到一個reference上,我們可能會這么做:
int& r = gemfield;但不好意思哈,編譯器會報錯,以Mac上的clang為例,編譯器會給出錯誤binding value of type 'const int' to reference to type 'int' drops 'const' qualifier:
error: binding value of type 'const int' to reference to type 'int' drops 'const' qualifierint& r = gemfield;^ ~~~~~~~~因為要綁定/指向const對象的reference必須也得是const類型,等等,這么說有點奇怪,向上文說過的那樣,因為reference本來初始化后就不能修改,天然具有const屬性,因此上面那句話的表述應(yīng)該修正為:綁定/指向const對象的reference必須得是"reference to const"類型,也就是:
const那么reference to const 類型如果指向的是non const類型的變量呢?
int not_const_gemfield = 7030; const int& r = non_const_gemfield;這樣是可以的,但要注意一點,雖然此時可以通過non_const_gemfield變量來修改其上的值,但通過r是不可能的,不然clang會報如下錯誤:
gemfield.cpp:6:7: error: cannot assign to variable 'r' with const-qualified type 'const int &'r = 56;~ ^ gemfield.cpp:4:16: note: variable 'r' declared const hereconst int& r = gemfield;~~~~~~~~~~~^~~~~~~~~~~~ 1 error generated.const和臨時對象
在C++中,要進一步理解const就不得不提到臨時對象(temporary object)這個概念。如果試圖將一個臨時對象綁定在非reference to const類型的reference上,那么編譯器會給出錯誤。
先看下面的例子:
double咦?臨時對象在哪里呢?這是因為你試圖將double類型的gemfield綁定到int型的reference上,編譯器就會進行隱含的類型轉(zhuǎn)化,相當(dāng)于:
double gemfield = 7030; int temp = gemfield int& r = temp;臨時對象temp就產(chǎn)生了。在C++中,如果你將一個引用綁定在臨時對象上(temp),編譯器會認(rèn)為這是完全沒有道理的事情,肯定不是程序員的意圖,因此直截了當(dāng)?shù)慕o出錯誤:
error: non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'double'int& r = gemfield;^ ~~~~~~~~ 1 error generated.但是臨時對象可以綁定在reference to const類型的reference上,因為這種類型的reference顯式的向編譯器表明了態(tài)度:程序員的我將不會通過該reference去改變綁定在其上的對象的值(也就是臨時對象上的值),那這種情況下就顯得很有道理了,因此編譯器會通過:
double有意思的事情來了,這種情況下你修改了gemfield對象的值:從7030到17030,那么r的輸出是什么呢?答案是7030,也就是說根本沒發(fā)生變化。正如上文所說,這是因為:r自始至終綁定的是那個臨時對象temp,并不是gemfield。
指針和const
在上文中,我們知道對于reference來說,有兩種const語義:一個是const reference,一個是reference to const,但是因為reference天然的具備const語義,因此我們只會提到reference to const。那么對于指針呢?指針自身作為可以實實在在修改的對象,是具備兩種const語義的,也就是const pointer和pointer to const。
先來一段簡單的例子:
const int gemfield = 7030; int* p = &gemfield;這段代碼會導(dǎo)致編譯器報錯:error: cannot initialize a variable of type 'int *' with an rvalue of type 'const int *'。這是因為gemfield是const類型的,因此左側(cè)的pointer的類型必須是pointer to const的。為什么呢?還記得文中一開始提到的嗎:“事實上,在編譯的時候,編譯器大概率會將用到gemfield的地方直接替換為7030“。至少從這個小細(xì)節(jié)上,我們就可以看出,gemfield對象已經(jīng)或多或少的變成了臨時語義,編譯器認(rèn)為用一個普通的指針指向它已經(jīng)毫無道理了,程序員的你的意圖一定不是這樣,因此會直截了當(dāng)?shù)慕o出錯誤。
如果要修復(fù)上述錯誤,必須將p的類型變?yōu)閜ointer to const,像下面這樣:
const既然現(xiàn)在我們已經(jīng)知道了pointer to const類型,我們再來說說const pointer類型。前者pointer to const類型的指針是說一個指針指向的對象是不可修改的,但是指針本身的值是可以修改的;而后者const pointer類型的指針則是說,指針本身是不可修改的。const pointer的語法是這樣的:將const關(guān)鍵字放到*之后:
int gemfield = 7030; int* const p = &gemfield;上述p就是const pointer,如果p既是const pointer又是pointer to const的呢?那就是:
const int gemfield = 7030; const int* const p = &gemfield;一個比較好的閱讀理解方式是從變量名p開始,從右到左看:
p //pointer名字 const p // const pointer int* const p //const pointer point to int const int* const p //const pointer pointer to const inttop-level const和low-level const
我們在這里可以提出top-level和low-level這兩個概念是因為,這兩個概念在好幾處都會被使用到:1,在拷貝對象時,可以無視top-level的const,但必須尊重low-level的const;2,在類型推導(dǎo)時,top-level的const會被無視/重視;3,在類型轉(zhuǎn)換時,top-level和low-level的const有不同的方法。
簡單來說,const pointer 是top-level const,pointer to const是low-level const。從人的眼睛出發(fā),我們是先看到pointer(top level),再看到pointer指向的對象(low level),層層剝開,高屋建瓴。對于 reference中的const語義來說,都是low-level的。
關(guān)于拷貝對象,我們來舉個例子:
int gemfield = 7030; int* const p1 = &gemfield;const int c_gemfield = 17030; const int* p2 = &c_gemfield;//沒問題,top-level被無視 gemfield = c_gemfield;//錯誤!p2是low-level const,但是tmp不是 int* tmp = p2;如果不尊重low-level const語義,編譯器就會給出下面的錯誤:
error: cannot initialize a variable of type 'int *' with an lvalue of type 'const int *'int* tmp = p;^ ~ 1 error generated關(guān)于類型推導(dǎo),因為過于復(fù)雜,放到下面單獨的章節(jié)理了。請往下看。
constexpr和constant expressions(常量表達(dá)式)
常量表達(dá)式(constant expressions)是說一個表達(dá)式的值不會被改變,并且在編譯期就能獲得這個表達(dá)式的值。相對應(yīng)的,一個表達(dá)式是否是constant expressions就取決于這兩個方面:類型(是否const)和初始化方式(編譯期是否能拿到值):
//下面是常量表達(dá)式上面的例子中,gemfield3不是const類型,gemfield4的值,也就是func()不能在編譯期得到,因此這兩者都不是constant expression。在C++11的新標(biāo)準(zhǔn)中,我們定義了constexpr關(guān)鍵字,用法如下所示:
constexpr int gemfield = 7030; constexpr int gemfield2 = gemfield + 1; constexpr int gemfield3 = func();這個關(guān)鍵字告訴編譯器,你來幫我確認(rèn)下這些個變量是否(可以)是constant expression,不行就報錯!上面的例子中,gemfield和gemfield2被編譯器裁定為可以是,但是gemfield3是不是呢?當(dāng)func是一個constexpr的函數(shù)時,那就是!如果func是一個普通的函數(shù)時,那就不是!
那什么是constexpr函數(shù)呢?這是C++11的新標(biāo)準(zhǔn),一個constexpr函數(shù)就是一個普通的函數(shù),再加上這些限制條件:1,參數(shù)的形參的類型必須是literal type(編譯期可以參與運算的類型);2,參數(shù)的實參必須是constant expression;3,函數(shù)體只能是一個return語句;4,并且語句中的表達(dá)式必須在編譯期可以resolve,而不是等到運行時。
因為constexpr函數(shù)的目的就是在編譯器用它的值來替換到使用它的地方,因此constexpr函數(shù)默認(rèn)具有inline語義,因此需要定義在多個編譯單元中,為了保證多個編譯單元中的同一個constexpr函數(shù)定義一致,我們通常需要把constexpr函數(shù)定義在頭文件中。
需要說明的是,當(dāng)指針遇到constexpr時,constexpr定義的const語義是top-level的:
constexpr int* gemfield = nullptr; const int* gemfield2 = nullptr;gemfield是const pointer,而gemfield2是pointer to const
const和類型推導(dǎo)
在C++11中,和const語義相關(guān)的,標(biāo)準(zhǔn)包含了兩種類型推導(dǎo):auto、decltype,以及RTTI中的類型識別:typeid。
1,auto
先說說auto,當(dāng)const語義遇到auto后,top-level的const會被auto忽略,這個和reference遇到auto的行為很像:
int gemfield = 7030; int& r = gemfield; auto a = r; //a的類型是int,而不是referenceconst int gemfield = 7030; auto a = gemfield; //a的類型是int,而不是const int如果在使用auto的時候想要帶reference或者const語義,那就顯式的加上:
auto& a = r; const auto& a = gemfield;2,decltype
auto的類型推導(dǎo)是根據(jù)初始化表達(dá)式來的,但有時候我們只想要表達(dá)式的類型,而不想用這個表達(dá)式來進行初始化,這就是decltype:
decltype(func()) gemfield = x;值得說明的時候,func()并不會被調(diào)用,decltype只是通過其推導(dǎo)出它的返回值類型而已。decltype的行為和auto有很大的區(qū)別,并且decltype進行類型推導(dǎo)的時候,可以輸入一個變量,也可以輸入一個表達(dá)式。
當(dāng)decltype的輸入是變量的時候,decltype返回這個變量的類型,并且會保留top-level的const語義,也會保留reference語義:
const int gemfield = 7030; const int& r = gemfield;decltype(gemfield) x = 0; //x是const int 類型 decltype(r) y = x; //y是const int&類型,因此必須初始化當(dāng)decltype的輸入是表達(dá)式的時候,decltype得到的類型是這個表達(dá)式返回的類型,下面是兩個有趣的例子:
int gemfield = 7030; int* p = &gemfield; int& r = gemfield;decltype(r) x; //x是int& decltype(r + 0) x; //x是int,不是int& decltype(*p) y; //y是int&,而不是inty之所以是int&,是因為*p是通過一個地址的索引來得到的值,更像是一個引用而不是普通的int。
說完了表達(dá)式,我們再回到變量。當(dāng)把變量用括號擴起來時,編譯器就任務(wù)這是一個表達(dá)式,當(dāng)作為decltype的輸入時,decltype會返回該類型的引用:
int gemfield = 7030;decltype(gemfield) x; //x是int decltype((gemfield)) x; //x是int&,不是int3,typeid
typeid是為RTTI提供的第二個operator,意思是問入?yún)?#xff1a;Hi,你的類型是什么呀?typeid的返回值是一個type_info類,在標(biāo)準(zhǔn)庫中定義。當(dāng)typeid的入?yún)⑹莄onst類型時,top-level的const語義會被忽略(順便說一句,當(dāng)typeid的入?yún)⑹莚eference類型時,reference語義也會被忽略)。哇,這個像極了auto類型推導(dǎo)啊!
函數(shù)參數(shù)中的const語義
const語義在函數(shù)參數(shù)的初始化中和變量的初始化中的行為是類似的。形參上的top-level的const會被無視:也即,如果形參是top-level的const語義,我們可以把const和non const的對象賦給形參。像下面這樣:
void gemfield(const int i) {/* can read i but not write to i */}對于low-level的const來說,記住一點:往更嚴(yán)格的方向轉(zhuǎn)換是沒有問題的,反之則不行。
void gemfield(int* i){} void gemfield(int& i){}由此得出一個好的實踐:函數(shù)的形參盡可能的使用reference to const。這樣帶來的一個好處就是,什么都可以傳。比如下面這樣:
void gemfield(const string& s1){} void gemfield(string& s2){} //不太好如果是第一種定義,我們的實參類型甚至可以是字符串常量。我們可以這樣調(diào)用函數(shù):gemfield("gemfield, a civilnet maintainer");如果是第二種定義,則會報錯。
最后我們還得提到函數(shù)重載,還記得重載的條件嗎:函數(shù)名相同、形參列表不同。其中,形參列表不同體現(xiàn)在兩個地方:參數(shù)個數(shù)不同,參數(shù)的類型不同。那么有趣的地方來了:
類型轉(zhuǎn)換和const_cast
眾所周知,C++中的類型轉(zhuǎn)換分為隱式和顯式轉(zhuǎn)換。
在類型的隱式轉(zhuǎn)換中,我們可以加上low-level的const,如下所示:
int但是,如果是想在類型的隱式轉(zhuǎn)換過程中去掉low-level的const,那則是萬萬不行的。
我們再來說說顯式轉(zhuǎn)換:const_cast。這個是專門用來操作low-level的const的,并且只能是這三種類型上的const語義:reference, pointer-to-object, or pointer-to-data-member。我們來看看下面的例子:
int gemfield = 7030; int& r = gemfield; const int& r2 = const_cast<const int&>(r);const_cast可以加上low-level的const語義,如上面所述;也可以去掉一個low-level的const語義,如下面所示:
const int gemfield = 7030; const int& r = gemfield; int& r2 = const_cast<int&>(r);這兩個的區(qū)別是,前者中r2指向的還是gemfield所在的內(nèi)存;而后者中r2則指向的是臨時對象,對r2的改動在標(biāo)準(zhǔn)是未定義的。
另外還有一個有趣的事實,就是顯式轉(zhuǎn)換中的static_cast,可以強制轉(zhuǎn)換任何類型,就是不能轉(zhuǎn)換low-level的const語義。對應(yīng)的,const_cast可以轉(zhuǎn)換low-level的const語義,但是不能進行其它類型的轉(zhuǎn)換。
類的const成員和const對象
類的const成員分為數(shù)據(jù)成員和函數(shù)成員,其中數(shù)據(jù)成員的語義和上述介紹沒有什么區(qū)別,只不過要注意的是,const數(shù)據(jù)成員的初始化方式——只能在構(gòu)造函數(shù)之前初始化;如果不對const數(shù)據(jù)成員顯式的進行初始化,編譯器將予以攔截并且報錯如下:
error: constructor for 'Gemfield' must explicitly initialize the const member 'data'而const函數(shù)成員指明了這個函數(shù)不會修改該類的任何成員數(shù)據(jù)的值,稱為常量成員函數(shù)——如果在const成員函數(shù)的定義中出現(xiàn)了任何修改對象成員數(shù)據(jù)的情況,都會在編譯時被編譯器攔截住。有了const成員函數(shù),我們就可以實例化const類型的對象(否則也沒有意義了),并且我們只能在const對象上調(diào)用const成員函數(shù),任何在const對象上調(diào)用非const成員函數(shù)的行為,都會被編譯器攔截住,并且報錯:
error: 'this' argument to member function 'getV' has type 'const Gemfield', but function is not marked const模版中const的語義以及完美轉(zhuǎn)發(fā)
這篇文章的內(nèi)容已經(jīng)太多了,這一小節(jié)的內(nèi)容將在《C++的perfect forwarding》中進行講述。
總結(jié)
以上是生活随笔為你收集整理的const int是什么类型_C++的const语义的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tensor flow lstm 图像
- 下一篇: CPU-Z支持Zen4线程撕裂者:96核