左值和左值引用、右值和右值引用
左值和右值
左值(L-value):能用“取地址&”運算符獲得對象的內(nèi)存地址,表達式結束后依然存在的持久化對象。左值可以出現(xiàn)在等號左邊也能夠出現(xiàn)在等號右邊。
右值(R-value):不能用“取地址&”運算符獲得對象的內(nèi)存地址,表達式結束后就不再存在的臨時對象。只能出現(xiàn)在等號右邊。
當一個對象被用作右值的時候,用的是對象的值(內(nèi)容);而被用作左值的時候,用的是對象的身份(在內(nèi)存中的位置)??傊鹤笾悼吹刂?,右值看內(nèi)容。所有的具名變量或者對象都是左值,而右值不具名,如常見的右值有非引用返回的臨時變量、運算表達式產(chǎn)生的臨時變量、原始字面量和lambda表達式等。右值要么是字面常量,要么是在表達式求值過程中創(chuàng)建的對象。特例:因為可以用&取得字符串字面值常量的地址,雖然它不能被賦值,但它是一個左值。
為什么右值不能用&取地址呢?
對于臨時對象,它可以存儲于寄存器中,所以沒辦法用“取地址&”運算符;
對于(非字符串)常量,它可能被編碼到機器指令的“立即數(shù)”中,所以沒辦法用“取地址&”運算符。
左值引用和右值引用
使用引用的目的就在于減少不必要的拷貝。
對左值的引用,就是給左值取別名。其基本語法如下:
變量名實質上是一段連續(xù)存儲空間的別名,是一個標號(門牌號),通過變量的名字可以使用存儲空間。可以通過引用為一個內(nèi)存空間取多個別名。
普通引用在聲明時必須用其它的變量進行初始化,引用作為函數(shù)參數(shù)聲明時不進行初始化。
引用在C++中的內(nèi)部實現(xiàn)是一個常指針。Type& name <=> Type* const name。所以一旦一個引用被初始化之后,無法再更改它所指向的對象。C++編譯器在編譯過程中使用常指針作為引用的內(nèi)部實現(xiàn),因此引用名所占用的空間大小與指針相同。對指針的引用:
const引用:const Type& name <=>const Type* const name,當使用常量(字面量)這類右值對const引用進行初始化時,C++編譯器會為常量值分配空間,并將引用名作為這段空間的別名。初始化后,將生成一個只讀變量。只有常引用才可以用右值表達式初始化,這一點很重要,因為如果不加const,那么這個臨時的對象是無法進行傳遞給左值引用的,比如
因為MyString("hello")是一個臨時對象,即右值,所以MyString實現(xiàn)的拷貝構造函數(shù)參數(shù)不加const就會報錯。
對右值的引用,就是給右值取別名。其基本語法如下:
在C++中創(chuàng)建對象是一個費時、廢空間的一個操作,有些固然必不可少,但還有一些對象卻在我們不知道的情況下創(chuàng)建了。
以值的方式給函數(shù)傳參:給函數(shù)傳參有兩種方式----按值傳遞和按引用傳遞。按值傳遞時,首先將需要傳給函數(shù)的參數(shù),調(diào)用拷貝構造函數(shù)創(chuàng)建一個副本,所有在函數(shù)里的操作都是針對這個副本的,也正是因為這個原因,在函數(shù)體里對該副本進行任何操作,都不會影響原參數(shù)。
類型轉換生成的臨時對象。
函數(shù)返回一個對象:當函數(shù)需要返回一個對象,他會在棧中創(chuàng)建一個臨時對象或也叫匿名對象(如果是類對象,則會調(diào)用拷貝構造函數(shù)),存儲函數(shù)的返回值。這個臨時對象在表達式 sum = Double(tm) 結束后就自動銷毀了,這個臨時對象就是右值。
引入右值引用的目的:右值引用是C++11中新增加的一個很重要的特性,它主要用來解決以下問題。
1. 函數(shù)返回臨時對象造成不必要的拷貝操作:通過使用右值引用,右值不會在表達式結束之后就銷毀了,而是會被“續(xù)命”,的生命周期將會通過右值引用得以延續(xù),和變量的聲明周期一樣長。
通過右值引用,比之前少了一次拷貝構造和一次析構,原因在于右值引用綁定了右值,讓臨時右值的生命周期延長了。我們可以利用這個特點做一些性能優(yōu)化,即避免臨時對象的拷貝構造和析構。
2.?通過右值引用傳遞臨時參數(shù):使用字面值(如1、3.15f、true),或者表達式等臨時變量作為函數(shù)實參傳遞時,按左值引用傳遞參數(shù)會被編譯器阻止。而進行值傳遞時,將產(chǎn)生一個和參數(shù)同等大小的副本。C++11提供了右值引用傳遞參數(shù),不申請局部變量,也不會產(chǎn)生參數(shù)副本。