日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

(P36-P39)右值和右值引用、右值引用的作用以及使用、未定引用类型的推导、右值引用的传递

發(fā)布時(shí)間:2024/3/26 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (P36-P39)右值和右值引用、右值引用的作用以及使用、未定引用类型的推导、右值引用的传递 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 1.右值
    • 2. 右值引用
    • 3.性能優(yōu)化
    • 4.&& 的特性
    • 5.右值引用的傳遞

1.右值

C++11 增加了一個(gè)新的類型,稱為右值引用( R-value reference),標(biāo)記為 &&。

在介紹右值引用類型之前先要了解什么是左值和右值:

  • lvalue 是 loactor value 的縮寫,rvalue 是 read value 的縮寫
  • 左值是指存儲(chǔ)在內(nèi)存中、有明確存儲(chǔ)地址 (可取地址)的數(shù)據(jù);
  • 右值是指可以提供數(shù)據(jù)值的數(shù)據(jù)(不可取地址);

通過描述可以看出,區(qū)分左值與右值的便捷方法是:可以對(duì)表達(dá)式取地址(&)就是左值,否則為右值 。
所有有名字的變量或?qū)ο蠖际亲笾?#xff0c;而右值是匿名的。

  • eg:一般情況下,位于 = 前的表達(dá)式為左值,位于 = 后邊的表達(dá)式為右值。
  • 也就是說例子中的 a, b 為左值,520,1314 為右值。a=b 是一種特殊情況,在這個(gè)表達(dá)式中 a, b 都是左值,因?yàn)樽兞?b 是可以被取地址的,不能視為右值。
int a = 520; int b = 1314; a = b;

C++11 中右值可以分為兩種:一個(gè)是將亡值( xvalue, expiring value),另一個(gè)則是純右值( prvalue, PureRvalue):

  • 純右值:非引用返回的臨時(shí)變量、運(yùn)算表達(dá)式產(chǎn)生的臨時(shí)變量、原始字面量和 lambda 表達(dá)式等

  • 將亡值:與右值引用相關(guān)的表達(dá)式,比如,T&& 類型函數(shù)的返回值、 std::move 的返回值等。

  • eg:在上面的語句中,value 是左值,520 是字面量也就是右值。其中 value 可以被引用,但是 520 就不行了,因?yàn)樽置媪慷际怯抑怠?/p>

int value = 520;

2. 右值引用

右值引用就是對(duì)一個(gè)右值進(jìn)行引用的類型。因?yàn)橛抑凳悄涿?#xff0c;所以我們只能通過引用的方式找到它。

  • 無論聲明左值引用還是右值引用都必須立即進(jìn)行初始化,因?yàn)橐妙愋捅旧聿⒉粨碛兴壎▽?duì)象的內(nèi)存,只是該對(duì)象的一個(gè)別名。

  • 通過右值引用的聲明,該右值又“重獲新生”,其生命周期與右值引用類型變量的生命周期一樣,只要該變量還活著,該右值臨時(shí)量將會(huì)一直存活下去。

  • 引用的本質(zhì)是:void* const 指針常量

  • eg:

#include <iostream> using namespace std;//int&& value = 520; 里面 520 是純右值,value 是對(duì)字面量 520 這個(gè)右值的引用。 int&& value = 520; class Test { public:Test(){cout << "construct: my name is jerry" << endl;}//拷貝構(gòu)造函數(shù)的作用是:防止淺拷貝Test(const Test& a){cout << "copy construct: my name is tom" << endl;} };Test getObj() {return Test(); }int main() {int a1;/*在 int &&a2 = a1; 中 a1 雖然寫在了 = 右邊,但是它仍然是一個(gè)左值,使用左值初始化一個(gè)右值引用類型是不合法的。*/int &&a2 = a1; // error//在 Test& t = getObj() 這句代碼中語法是錯(cuò)誤的,右值不能給普通的左值引用賦值。Test& t = getObj(); // error/* 在 Test && t = getObj(); 中 getObj() 返回的臨時(shí)對(duì)象被稱之為將亡值,t 是這個(gè)將亡值的右值引用。*/Test&& t = getObj();/* const Test& t = getObj() 這句代碼的語法是正確的,常量左值引用是一個(gè)萬能引用類型,它可以接受左值、 右值、常量左值和常量右值、常量左值引用、常量右值引用。 */const Test& t = getObj();return 0; }
  • eg:常量引用:引用的值不能被修改;右值引用需要用右值進(jìn)行初始化;左值初始化右值引用是錯(cuò)誤的
#include <iostream> #include <functional> using namespace std;int main(void) {//左值int num = 9;//左值引用int& a = num;//右值//右值引用int&& b = 8;//常量右值引用const int&& d = 6;//常量左值引用const int& c = num;const int& f = b;const int& g = d;const int&& h = b;//errorint&& i = b;//errorreturn 0; }

3.性能優(yōu)化

在 C++ 中在進(jìn)行對(duì)象賦值操作的時(shí)候,很多情況下會(huì)發(fā)生對(duì)象之間的深拷貝,如果堆內(nèi)存很大,這個(gè)拷貝的代價(jià)也就非常大,在某些情況下,如果想要避免對(duì)象的深拷貝,就可以使用右值引用進(jìn)行性能的優(yōu)化。

  • eg:
#include <iostream> using namespace std;class Test { public:Test() : m_num(new int(100)){cout << "construct: my name is jerry" << endl;}Test(const Test& a) : m_num(new int(*a.m_num)){cout << "copy construct: my name is tom" << endl;}~Test(){delete m_num;}int* m_num; };//rvo優(yōu)化,這里少一次拷貝構(gòu)造 Test getObj() {Test t;return t; }int main() {Test t = getObj();cout << "t.m_num: " << *t.m_num << endl;return 0; };
  • 測(cè)試:
  • 解釋:

通過輸出的結(jié)果可以看到調(diào)用 Test t = getObj(); 的時(shí)候調(diào)用拷貝構(gòu)造函數(shù)對(duì)返回的臨時(shí)對(duì)象進(jìn)行了深拷貝得到了對(duì)象 t,在 getObj() 函數(shù)中創(chuàng)建的對(duì)象雖然進(jìn)行了內(nèi)存的申請(qǐng)操作,但是沒有使用就釋放掉了。

如果能夠使用臨時(shí)對(duì)象已經(jīng)申請(qǐng)的資源,既能節(jié)省資源,還能節(jié)省資源申請(qǐng)和釋放的時(shí)間,如果要執(zhí)行這樣的操作就需要使用右值引用了,右值引用具有移動(dòng)語義,移動(dòng)語義可以將資源(堆、系統(tǒng)對(duì)象等)通過淺拷貝從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象這樣就能減少不必要的臨時(shí)對(duì)象的創(chuàng)建、拷貝以及銷毀,可以大幅提高 C++ 應(yīng)用程序的性能。

  • eg:
#include <iostream> using namespace std;class Test { public:Test() : m_num(new int(100)){cout << "construct: my name is jerry" << endl;}//*a.m_num先運(yùn)行.后運(yùn)行*Test(const Test& a) : m_num(new int(*a.m_num)){cout << "copy construct: my name is tom" << endl;}// 添加移動(dòng)構(gòu)造函數(shù)/*移動(dòng)構(gòu)造函數(shù)會(huì)復(fù)用其他對(duì)象內(nèi)存中的資源(堆內(nèi)存)m_num淺拷貝*/Test(Test&& a) : m_num(a.m_num){a.m_num = nullptr;cout << "move construct: my name is sunny" << endl;}~Test(){delete m_num;cout << "destruct Test class ..." << endl;}int* m_num; };Test getObj() {Test t;//返回的是臨時(shí)對(duì)象,若有移動(dòng)構(gòu)造函數(shù),則先調(diào)用移動(dòng)構(gòu)造函數(shù)return t; }int main() { /* 要求右側(cè)的對(duì)象是一個(gè)臨時(shí)對(duì)象,才會(huì)調(diào)用移動(dòng)構(gòu)造,若沒有移動(dòng)構(gòu)造則會(huì)調(diào)用拷貝構(gòu)造 */Test t = getObj();cout << "t.m_num: " << *t.m_num << endl;/* 若沒有移動(dòng)構(gòu)造函數(shù),使用右值引用要求:右側(cè)是一個(gè)臨時(shí)的不能取地址的對(duì)象*/Test&& t2 = getObj();cout << "t2.m_num: " << *t2.m_num << endl;return 0; };
  • 測(cè)試:

  • 解釋:

通過修改,在上面的代碼給 Test 類添加了移動(dòng)構(gòu)造函數(shù)(參數(shù)為右值引用類型),這樣在進(jìn)行 Test t = getObj(); 操作的時(shí)候并沒有調(diào)用拷貝構(gòu)造函數(shù)進(jìn)行深拷貝,而是調(diào)用了移動(dòng)構(gòu)造函數(shù),在這個(gè)函數(shù)中只是進(jìn)行了淺拷貝,沒有對(duì)臨時(shí)對(duì)象進(jìn)行深拷貝,提高了性能。

如果不使用移動(dòng)構(gòu)造,在執(zhí)行 Test t = getObj() 的時(shí)候也是進(jìn)行了淺拷貝,但是當(dāng)臨時(shí)對(duì)象被析構(gòu)的時(shí)候,類成員指針 int* m_num; 指向的內(nèi)存也就被析構(gòu)了,對(duì)象 t 也就無法訪問這塊內(nèi)存地址了。

在測(cè)試程序中 getObj() 的返回值就是一個(gè)將亡值,也就是說是一個(gè)右值,在進(jìn)行賦值操作的時(shí)候如果 = 右邊是一個(gè)右值,那么移動(dòng)構(gòu)造函數(shù)就會(huì)被調(diào)用。移動(dòng)構(gòu)造中使用了右值引用,會(huì)將臨時(shí)對(duì)象中的堆內(nèi)存地址的所有權(quán)轉(zhuǎn)移給對(duì)象t,這塊內(nèi)存被成功續(xù)命,因此在t對(duì)象中還可以繼續(xù)使用這塊內(nèi)存。

對(duì)于需要?jiǎng)討B(tài)申請(qǐng)大量資源的類,應(yīng)該設(shè)計(jì)移動(dòng)構(gòu)造函數(shù),以提高程序效率。需要注意的是,我們一般在提供移動(dòng)構(gòu)造函數(shù)的同時(shí),也會(huì)提供常量左值引用的拷貝構(gòu)造函數(shù),以保證移動(dòng)不成還可以使用拷貝構(gòu)造函數(shù)。

4.&& 的特性

C++ 中,并不是所有情況下 && 都代表是一個(gè)右值引用,具體的場(chǎng)景體現(xiàn)在模板和自動(dòng)類型推導(dǎo)中,

  • 如果是模板參數(shù),需要指定為 T&&,如果是自動(dòng)類型推導(dǎo),需要指定為 auto &&,在這兩種場(chǎng)景下 && 被稱作未定的引用類型

  • 另外還有一點(diǎn)需要額外注意,const T&& 表示一個(gè)右值引用,不是未定引用類型

  • eg:在函數(shù)模板中使用 &&
    例子中函數(shù)模板進(jìn)行了自動(dòng)類型推導(dǎo),需要通過傳入的實(shí)參來確定參數(shù) param 的實(shí)際類型。

template<typename T> void f(T&& param); void f1(const T&& param);//對(duì)于 f(10) 來說傳入的實(shí)參 10 是右值,因此 T&& 表示右值引用 f(10); int x = 10;//對(duì)于 f(x) 來說傳入的實(shí)參是 x 是左值,因此 T&& 表示左值引用 f(x); //f1(x) 的參數(shù)是 const T&& 不是未定引用類型,不需要推導(dǎo),本身就表示一個(gè)右值引用 f1(x);
  • eg:
int main() {int x = 520, y = 1314;//auto&& 表示一個(gè)整形的左值引用auto&& v1 = x;//auto&& 表示一個(gè)整形的右值引用auto&& v2 = 250;//decltype(x)&& 等價(jià)于 int&& 是一個(gè)右值引用不是未定引用類型,y 是一個(gè)左值,不能使用左值初始化一個(gè)右值引用類型。decltype(x)&& v3 = y; // errorcout << "v1: " << v1 << ", v2: " << v2 << endl;return 0; };

由于上述代碼中存在 T&& 或者 auto&& 這種未定引用類型,當(dāng)它作為參數(shù)時(shí),有可能被一個(gè)右值引用初始化,也有可能被一個(gè)左值引用初始化,在進(jìn)行類型推導(dǎo)時(shí)右值引用類型(&&)會(huì)發(fā)生變化,這種變化被稱為引用折疊。
在 C++11 中引用折疊的規(guī)則如下:

  • 通過右值推導(dǎo) T&& 或者 auto&& 得到的是一個(gè)右值引用類型

  • 通過非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推導(dǎo) T&& 或者 auto&& 得到的是一個(gè)左值引用類型

  • eg:

int&& a1 = 5; //a1 為右值引用,推導(dǎo)出的 bb 為左值引用類型 auto&& bb = a1;//5 為右值,推導(dǎo)出的 bb1 為右值引用類型 auto&& bb1 = 5;int a2 = 5;int &a3 = a2; //a3 為左值引用,推導(dǎo)出的 cc 為左值引用類型 auto&& cc = a3;//a2 為左值,推導(dǎo)出的 cc1 為左值引用類型 auto&& cc1 = a2;const int& s1 = 100; const int&& s2 = 100;//s1 為常量左值引用,推導(dǎo)出的 dd 為常量左值引用類型 auto&& dd = s1;//s2 為常量右值引用,推導(dǎo)出的 ee 為常量左值引用類型 auto&& ee = s2;//x 為右值引用,不需要推導(dǎo),只能通過右值初始化 const auto&& x = 5;

5.右值引用的傳遞

  • eg:
#include <iostream> using namespace std;void printValue(int &i) {cout << "l-value: " << i << endl; }void printValue(int &&i) {cout << "r-value: " << i << endl; }void forward(int &&k) {printValue(k); }int main() {int i = 520;printValue(i);printValue(1314);forward(250);return 0; };
  • 測(cè)試:

  • 解釋:
    根據(jù)測(cè)試代碼可以得知,編譯器會(huì)根據(jù)傳入的參數(shù)的類型(左值還是右值)調(diào)用對(duì)應(yīng)的重置函數(shù)(printValue);
    函數(shù) forward () 接收的是一個(gè)右值,但是在這個(gè)函數(shù)中調(diào)用函數(shù) printValue () 時(shí),參數(shù) k 變成了一個(gè)命名對(duì)象,編譯器會(huì)將其當(dāng)做左值(當(dāng)成左值引用更好理解些?)來處理。

總結(jié)&&使用:

左值和右值是獨(dú)立于他們的類型的,右值引用類型可能是左值也可能是右值。

編譯器會(huì)將已命名的右值引用視為左值,將未命名的右值引用視為右值。

auto&&或者函數(shù)參數(shù)類型自動(dòng)推導(dǎo)的T&&是一個(gè)未定的引用類型,它可能是左值引用也可能是右值引用類型,這取決于初始化的值類型(上面有例子)。

通過右值推導(dǎo) T&& 或者 auto&& 得到的是一個(gè)右值引用類型,其余都是左值引用類型。

  • 參考:右值引用

總結(jié)

以上是生活随笔為你收集整理的(P36-P39)右值和右值引用、右值引用的作用以及使用、未定引用类型的推导、右值引用的传递的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。