const 以及 引用& 的用法
const T
{ //【const T】 int i = 5; const int constInt = 10; //正確,宣告常量必須初始化, const int constInt2 = i; //正確 //const int constInt3; //錯誤,未被初始化 //constInt = 20; //錯誤,常量值不可被改變 }
在《C程式設計專家》一書中,介紹const 關鍵字不是常量;【2.2多做之過 章節中】,但是現在實際編譯器是能夠通過的
const T*
{ //【const T*】 int t = 3; const int i = t; const int i2 = 10; const int* pInt = &i; //正確,指向一個const int物件,即i的地址 //*pInt = 10; //錯誤,不能改變其所指向的物件值(儲存資料) pInt = &i2; //正確,可以改變pInt指標本身的值,此時pInt指向的是i2的地址 const int* p2 = new int(8); //正確,指向一個new出來的物件的地址 delete p2; //正確 //int* pInt = &i; //錯誤,i是const int型別,型別不匹配,不能將const int * 初始化為int * int nValue = 15; const int * pConstInt = &nValue; //正確,可以把int 賦給const int *,但是pConstInt不能改變其所指向物件的值,即nValue //*pConstInt = 40; //錯誤,不能改變其所指向物件的值 }
T *const
{ int nValue = 10; int* const p = &nValue; int* const p2 = &nValue; int nValue1 = 10; int nValue2 = 20; int* const constPoint = &nValue1; //constPoint = & nValue2; //錯誤,不能改變constPoint本身的值(指向地址) *constPoint = 40; //正確,可以改變constPoint所指向的物件值,此時nValue1 = 40 const int nConstValue1 = 5; const int nConstValue2 = 15; const int* pPoint = &nConstValue1; //*pPoint = 55; //錯誤,不能改變pPoint所指向物件的值 pPoint = &nConstValue2; //正確,可以改變pPoint指標本身的值,此時pPoint邦定的是nConstValue2物件,即pPoint為nConstValue2的地址 const int* const pPoint2 = &nConstValue1; //*pPoint2 = 55; //錯誤,不能改變pPoint所指向物件的值 //pPoint2 = &nConstValue2; //錯誤,不能改變pPoint本身的值 }
小結:
const int* 指標指向的物件值(儲存的資料)不可以改變,但指標本身的值(指向的地址)可以改變;
int* const 指標本身的值(指向的地址)不可改變,但其指向的物件值(儲存的資料)可以改變。
const int* const 是一個指向常量物件的常量指標,即不可以改變指標本身的值,也不可以改變指標指向的物件。
【左值、右值】:
在C++11中所有的值必屬於左值、右值兩者之一,右值又可以細分為純右值、將亡值。
在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、沒有名字的就是右值(將亡值或純右值)。
舉個例子,int a = b + c, a 就是左值,其有變數名為a,通過&a可以獲取該變數的地址;
表示式b + c、函式int func()的返回值是右值,在其被賦值給某一變數前,我們不能通過變數名找到它,&(b + c)這樣的操作則不會通過編譯。【右值、將亡值】:
在理解C++11的右值前,先看看C++98中右值的概念:C++98中右值是純右值,純右值指的是臨時變數值、不跟物件關聯的字面量值。
臨時變數指的是非引用返回的函式返回值、表示式等,例如函式int func()的返回值,表示式a + b;不跟物件關聯的字面量值,例如true,2,”C”等。C++11對C++98中的右值進行了擴充。在C++11中右值又分為純右值(prvalue,Pure Rvalue)和將亡值(xvalue,eXpiring Value)。
其中純右值的概念等同於我們在C++98標準中右值的概念,指的是【臨時變數和不跟物件關聯的字面量值】;
將亡值則是C++11新增的跟右值引用相關的表示式,這樣表示式通常是將要被移動的物件(移為他用),
比如返回右值引用T&&的函式返回值、std::move的返回值,或者轉換為T&&的型別轉換函式的返回值。將亡值可以理解為通過“盜取”其他變數記憶體空間的方式獲取到的值。
在確保其他變數不再被使用、或即將被銷燬時,通過“盜取”的方式可以避免記憶體空間的釋放和分配,能夠延長變數值的生命期。總結!:左值表示儲存資料的地方,右值表示儲存的資料。
左值可以在等式的左邊,也可以是右邊,但是右值只能是放在等式的右邊;
可以理解為,左值是具有儲存地址 + 儲存資料的特性,右值只是具有儲存資料的特性;*///下面的例子為了好註釋,將左值用 nonConst 右值用Const 代替說明
{ //為了更好的理解,常量和非常量的初始化,用右值、左值帶入說明。 int c = 2; //正確,左值用右值初始化(變數初始化) const int d = c; //正確,左值用左值初始化(常量初始化) int e = d; //正確,左值用左值初始化(變數初始化) /*結論:初始化的是左值,const定義的是常量而不是右值,常量不代表右值,左值也不代表非常量。 可以看出左值除 const定義的常量是左值,左值是非常量。 為好理解我們稱:常量左值為Const左值,非常量左值為nonConst左值*/ } { //左值好寫,右值除一切常數、字元和字串都是右值外幾乎寫不出來的,關鍵是右值不具有名字; int aIn; // //int &d; //錯誤,Const左值引用不初始化,編譯失敗 int &bIn = aIn; //正確, //const int &acn; //錯誤,nonConst左值引用不初始化,編譯失敗 const int &acn = aIn; //正確, /*結論:無論是宣告一個左值引用還是右值引用,都必須立即進行初始化。 可以理解為是引用型別本身自己並不擁有所繫結物件的記憶體,只是該物件的一個別名, 【引用(bIn)是物件(aIn)的另一個名字,注意!!aIn也不是真名,但可以這樣理解。】 而其原因是,左值引用是具名變數值的別名,而右值引用則是不具名(匿名)變數的別名。*/ } { ///左值引用就是對一個左值進行引用的型別。右值引用就是對一個右值進行引用的型別, ///事實上,由於右值通常不具有名字,我們也只能通過引用的方式找到它的存在。 int aIn = 1; // nomConst左值 const int bCn = 2; // Const左值 const int &cCn = aIn; // Const左值引用繫結到nonConst左值,編譯通過 const int &dCn = bCn; // Const左值引用繫結到Const左值,編譯通過 const int &eCn = 3; // Const左值引用繫結到右值,程式設計通過 #為甚麼可以???# /*結論:常量左值引用是個“萬能”的引用型別,它可以接受nonConst左值、Const左值、右值對其進行初始化。 不過常量左值所引用的右值在它的“餘生”中只能是隻讀的。*/ //int &f = 2; //錯誤,nonConst左值引用繫結到右值,編譯失敗 int &gIn = aIn; //正確,nonConst左值引用繫結到nonConst左值,編譯通過 //int &fIn = bCn; //錯誤nonConst左值引用繫結到Const左值,編譯失敗 /*結論:nonConst左值只能接受nonConst左值對其進行初始化,左值引用通常也不能繫結到右值。*/ } { /* cosnt T& 深入瞭解: (1)首先,如果需要的話,將應用到型別T的隱式型別轉換。 (2)而後,將結果存入一個型別T的臨時變數。 (3)最後,將此臨時變數用作初始化的值。*/ //例如: const double& cd = 1; //ok //初始化的解釋是: double temp = double(1); //首先建立一個具有正確資料型別的臨時變數 const double& cd = temp; //而後用這個臨時變數作為cd的初始式,temp是個右值 /*結論:由於左值的一大特點是可以對其賦值,而const正好將這個特點給閹割了,使該表示式成為了一個右值,所以可以用右值來初始化。*/ }
知道了引用的概念,就可以更進一步的學習,const T*& 、T const* &
const int&
int i = 5; int& rInt = i; //正確,int的引用 const int constInt = 10; const int& rConstInt = constInt; //正確,引用及邦定的值都是常量 const int& rConstInt2 = rInt; //正確,用rInt邦定的物件(非常量)進行賦值 const int& rConstInt2_1 = i; //與上,作用相同 rInt = 30; //這時,rConstInt2、rInt、i的值都為30 // rConstInt2 = 30; //錯誤,rConstInt2是常量引用,rConstInt2本身不能改變所指向的物件 /*除錯檢視記憶體如下: +&i 0x006ffe74 {30} int * +&rConstInt2 0x006ffe74 {30} const int * +&rConstInt2_1 0x006ffe74 {30} const int * +&rInt 0x006ffe74 {30} int * 結論:引用繫結的是常量,且繫結無法改變。*/ /**/ int i2 = 15; const int& rConstInt3 = i2; //正確,用非常量的物件為其賦值 const int& rConstInt4 = 50; //正解,用一個常量值為其賦值 ///如果用表示式賦值後,它的值是表示式的“臨時變數”,其值不會再變,它是純右值【在左值、右值中說明】。 const int& rConstInt5 = i + i2; //正確,用表示式為其賦值,值為45 /*除錯檢視記憶體如下: +&rConstInt5 0x004ff6f8 {45} const int * +&rConstInt3 0x004ff740 {15} const int * +&i2 0x004ff740 {15} int * +&i 0x004ff77c {30} int * 結論:可以看出表示式賦值時,它產生了一個不同於,i和i2 的新地址。*/ i = 20; //此時i=20, rInt = 20, rConstInt4 = 45,說明rConstInt4邦定的是i + i2的臨時變數
【1.const T*】是指向常量的指標;【2.const T*&】指向常量的指標的引用。
const int nConstValue = 1; //常量物件 const int nConstValue2 = 2; //常量物件 const int* pConstValue = &nConstValue; //指向常量(物件)的常量指標 const int* pConstValue2 = &nConstValue2; //指向常量(物件)的常量指標 const int*& rpConstValue = pConstValue; //指向常量(物件)的常量指標的引用 //*rpConstValue = 10; //錯誤,pConstValue指向的是常量(物件),常量(物件)的值不可改變 rpConstValue = pConstValue2; //正確,此時rpConstValue是引用指向常量(nConstValue2)的常量指標(pConstValue2) pConstValue = pConstValue2; //與上,作用相同 /*除錯檢視記憶體如下: +&nConstValue 0x012ffa8c {1} const int * +&nConstValue2 0x012ffa80 {2} const int * +&rpConstValue 0x012ffa74 {0x012ffa80 {2}} const int * * +&pConstValue 0x012ffa74 {0x012ffa80 {2}} const int * * 結論:引用,等同於一個常量指標同時擁有兩個名字,它的本質沒變。 const T* 是指向常量指標,這意味著不能通過指標操作改變物件的值,因為物件是常量, 但是可以改變指標指向的常量物件*/
【1.T *const】是指向非常量的指標;【2.T *const &】指向非常量的指標的引用。
int nValue = 5; int nValue2 = 10; int *const constPoint = &nValue; //指向非常量(物件)的常量指標 int *const constPoint2 = &nValue2; //指向非常量(物件)的常量指標 int *const &rpConstPoint = constPoint; //指向非常量(物件)的常量指標引用, //rpConstPoint = constPoint2; //錯誤,constPoint是常量指標,指標本身的值(指標地址)不可改變 *rpConstPoint = 20; //正確,指標指向的非常量(物件)值(儲存資料)可以改變, /*結論:T *const 是指向非常量指標,這意味著通過指標操作改變物件的值,因為物件是非常量*/
int nValue = 5; //nonConst左值 const int nConstValue = 1; //也可以為:int const nValue = 5; 一般不要這樣寫。 const int nConstValue2 = nValue; //正確, const int nConstValue2_ = nConstValue; //正確, const int* pConstValue3 = &nValue; //正確, const int* pConstValue3_ = &nConstValue; //正確, const int*& rpConstValue = pConstValue3; //正確, int *const rpConstPoint = &nValue; //正確, int *const rpConstPoint2 = rpConstPoint; //正確 //int *const rpConstPointt_ = &nConstValue; //錯誤,"const int *" 型別的值不能用於初始化 "int *const" 型別的實體。 //int *const &rpConstPoint2 = &pConstValue3; //錯誤,"const int **" 型別的值不能用於初始化 "int *const&" 型別的實體。 int *const &rpConstPoint2_ = rpConstPoint; //正確, /*總結!:【cosnt T*】 指向常量的指標,非常量和常量初始化。 【cosnt T*&】 指向常量指標的引用,cosnt T* 初始化。 【T *const】 指向非常量的指標用,非常量初始化。 【T *const&】指向非常量的指標引用,T *const 初始化。*/