1. 程式人生 > >const 以及 引用& 的用法

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 初始化。*/