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