C++工程代碼中經(jīng)常會(huì)看見(jiàn)這樣的函數(shù)入?yún)ⅲ?/p>
void fun(int & a, int* b){
...
return;
}
上面的函數(shù)使用了引用作為函數(shù)的入?yún)ⅲ?/p>
左值引用和右值引用
有時(shí)候還會(huì)見(jiàn)到更加奇怪的變量定義:
int a = 10;
int &b = a;
int &&c = 10;
上面代碼內(nèi)b就是左值引用了,而c就是右值引用;
c++左值與右值
int a = 10;
c++代碼原本就有左值和右值之分,其中上面代碼第一行變量a就是左值,而數(shù)字10就是右值看峻;
一般來(lái)說(shuō)左值在內(nèi)存中是有分配空間的,而右值很有可能因?yàn)橹皇侵虚g量,可能只存在cpu某個(gè)寄存器內(nèi)而沒(méi)有具體內(nèi)存地址侄旬;
簡(jiǎn)單來(lái)說(shuō),在‘=’左邊的是左值煌妈,而在‘=’右邊的就是右值儡羔。
回到引用的說(shuō)明宣羊,引用是變量的別名,它使用不同名稱“指向”了內(nèi)存的某一具體地址汰蜘;顯然根據(jù)上面左值右值的說(shuō)明可以發(fā)現(xiàn)仇冯,如果某個(gè)變量沒(méi)有在內(nèi)存內(nèi)(右值),而我們又想對(duì)他定義引用族操,怎么辦赞枕?這就有了右引用的用武之地;
int a = 10;
int &&c = 10;
正常情況下上面的代碼中坪创,第一行的右值“10”的生存周期只在當(dāng)行有效炕婶。在執(zhí)行第二行時(shí),對(duì)應(yīng)的寄存器或者內(nèi)存會(huì)釋放掉莱预。程序會(huì)申請(qǐng)一個(gè)內(nèi)存地址叫做‘a(chǎn)’柠掂,地址內(nèi)存放10。
對(duì)于第二行依沮,右值“10”在定義c后涯贞,會(huì)轉(zhuǎn)化為左值被c引用,并且在執(zhí)行完當(dāng)前行后危喉,“10”繼續(xù)存在在內(nèi)存中(有地址被c記錄)直到c被釋放宋渔,顯然c解決了右值生存周期的問(wèn)題。
那為什么需要右值引用這種看似很無(wú)聊的操作呢:
減少不必要的拷貝辜限,同時(shí)完成必要的數(shù)據(jù)轉(zhuǎn)換皇拣!
來(lái)個(gè)例子
#include <iostream>
using namespace std;
int fun(int &v1, const int &v2){
v1 = v2 + 1;
cout << "-------入?yún)⒁?-------" << endl;
cout << " v1 =" << v1 <<" "<<" &v1 = "<<&v1<< endl;
cout << " v2 =" << v2 <<" "<<" &v2 = "<<&v2<< endl;
cout << "-----------------------" << endl;
return 0;
}
int main()
{
int v1 = 1;
int v2 = 1;
cout << "-------入?yún)⒍x--------" << endl;
cout << " v1 =" << v1 <<" "<<" &v1 = "<<&v1<< endl;
cout << " v2 =" << v2 <<" "<<" &v2 = "<<&v2<< endl;
cout << "-----------------------" << endl;
fun(v1, v2);
return 0;
}
-------入?yún)⒍x--------
v1 =1 &v1 = 0x7fff062557dc
v2 =1 &v2 = 0x7fff062557d8
-----------------------
-------入?yún)⒁?-------
v1 =2 &v1 = 0x7fff062557dc
v2 =1 &v2 = 0x7fff062557d8
-----------------------
const 引用的特殊性
上面例子中const修飾函數(shù)入?yún)ⅲ瞬蛔屝薷暮推胀ㄈ雲(yún)⒁盟坪鯖](méi)什么區(qū)別薄嫡。
如果把變量v2修改為float氧急,我們發(fā)現(xiàn)編譯并不會(huì)報(bào)錯(cuò),修改后的代碼如下:
#include <iostream>
using namespace std;
int fun(int &v1, const int &v2){
v1 = v2 + 1;
cout << "-------入?yún)⒁?-------" << endl;
cout << " v1 =" << v1 <<" "<<" &v1 = "<<&v1<< endl;
cout << " v2 =" << v2 <<" "<<" &v2 = "<<&v2<< endl;
cout << "-----------------------" << endl;
return 0;
}
int main()
{
int v1 = 1;
float v2 = 1;
cout << "-------入?yún)⒍x--------" << endl;
cout << " v1 =" << v1 <<" "<<" &v1 = "<<&v1<< endl;
cout << " v2 =" << v2 <<" "<<" &v2 = "<<&v2<< endl;
cout << "-----------------------" << endl;
fun(v1, v2);
fun(v1,2);
return 0;
}
代碼執(zhí)行結(jié)果如下:
-------入?yún)⒍x--------
v1 =1 &v1 = 0x7fff7782a864
v2 =1 &v2 = 0x7fff7782a860
-----------------------
-------入?yún)⒁?-------
v1 =2 &v1 = 0x7fff7782a864
v2 =1 &v2 = 0x7fff7782a868
-----------------------
-------入?yún)⒁?-------
v1 =3 &v1 = 0x7fff7782a864
v2 =2 &v2 = 0x7fff7782a86c
-----------------------
發(fā)現(xiàn)編譯器通過(guò)const int & v2 自動(dòng)幫我們完成了數(shù)據(jù)類型轉(zhuǎn)換毫深。(個(gè)人理解是const關(guān)鍵字說(shuō)明該引用不會(huì)影響函數(shù)外的變量且會(huì)申請(qǐng)新的內(nèi)存地址變?yōu)樽笾捣园樱宰詣?dòng)完成內(nèi)存轉(zhuǎn)換不會(huì)有任何風(fēng)險(xiǎn))。
這里可以嘗試修改v1的類型為float 或者在fun函數(shù)的第一個(gè)入?yún)橛抑稻鶡o(wú)法編譯通過(guò)哑蔫。
當(dāng)然其實(shí)在復(fù)雜的工程代碼上钉寝,有些更加變態(tài)的代碼會(huì)有如下需求:
int fun(const T &a); // 函數(shù)申明
/**/
int b1 = fun(new T);
T t;
int b2 = fun(t)闸迷;
其中的差別需要積累經(jīng)驗(yàn)慢慢體會(huì)嵌纲。
其他
寫法上const關(guān)鍵字可以放在任何位置;常引用隨便綁稿黍;
int &a = 2; // 左值引用綁定到右值疹瘦,編譯失敗
int &&a = 2; // 右值引用綁定到右值,編譯ok
int b = 2; // 非常量左值
const int &c = b; // 常量左值引用綁定到非常量左值巡球,編譯ok
const int d = 2; // 常量左值
const int &e = c; // 常量左值引用綁定到常量左值言沐,編譯ok
const int &f =2; // 常量左值引用綁定到右值邓嘹,編程ok
int const &g =2; // 和上一句等價(jià),編程ok
右值引用不能直接綁定到左值险胰,如果要綁定可以使用std::move()函數(shù)汹押;
int a;
int &&r1 = a; // 編譯ok
int &&r2 = std::move(a); //編譯ok