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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

文章目錄

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

1.右值

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

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

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

通過描述可以看出,區分左值與右值的便捷方法是:可以對表達式取地址(&)就是左值,否則為右值 。
所有有名字的變量或對象都是左值,而右值是匿名的。

  • eg:一般情況下,位于 = 前的表達式為左值,位于 = 后邊的表達式為右值。
  • 也就是說例子中的 a, b 為左值,520,1314 為右值。a=b 是一種特殊情況,在這個表達式中 a, b 都是左值,因為變量 b 是可以被取地址的,不能視為右值。
int a = 520; int b = 1314; a = b;

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

  • 純右值:非引用返回的臨時變量、運算表達式產生的臨時變量、原始字面量和 lambda 表達式等

  • 將亡值:與右值引用相關的表達式,比如,T&& 類型函數的返回值、 std::move 的返回值等。

  • eg:在上面的語句中,value 是左值,520 是字面量也就是右值。其中 value 可以被引用,但是 520 就不行了,因為字面量都是右值。

int value = 520;

2. 右值引用

右值引用就是對一個右值進行引用的類型。因為右值是匿名的,所以我們只能通過引用的方式找到它。

  • 無論聲明左值引用還是右值引用都必須立即進行初始化,因為引用類型本身并不擁有所綁定對象的內存,只是該對象的一個別名。

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

  • 引用的本質是:void* const 指針常量

  • eg:

#include <iostream> using namespace std;//int&& value = 520; 里面 520 是純右值,value 是對字面量 520 這個右值的引用。 int&& value = 520; class Test { public:Test(){cout << "construct: my name is jerry" << endl;}//拷貝構造函數的作用是:防止淺拷貝Test(const Test& a){cout << "copy construct: my name is tom" << endl;} };Test getObj() {return Test(); }int main() {int a1;/*在 int &&a2 = a1; 中 a1 雖然寫在了 = 右邊,但是它仍然是一個左值,使用左值初始化一個右值引用類型是不合法的。*/int &&a2 = a1; // error//在 Test& t = getObj() 這句代碼中語法是錯誤的,右值不能給普通的左值引用賦值。Test& t = getObj(); // error/* 在 Test && t = getObj(); 中 getObj() 返回的臨時對象被稱之為將亡值,t 是這個將亡值的右值引用。*/Test&& t = getObj();/* const Test& t = getObj() 這句代碼的語法是正確的,常量左值引用是一個萬能引用類型,它可以接受左值、 右值、常量左值和常量右值、常量左值引用、常量右值引用。 */const Test& t = getObj();return 0; }
  • eg:常量引用:引用的值不能被修改;右值引用需要用右值進行初始化;左值初始化右值引用是錯誤的
#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.性能優化

在 C++ 中在進行對象賦值操作的時候,很多情況下會發生對象之間的深拷貝,如果堆內存很大,這個拷貝的代價也就非常大,在某些情況下,如果想要避免對象的深拷貝,就可以使用右值引用進行性能的優化。

  • 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優化,這里少一次拷貝構造 Test getObj() {Test t;return t; }int main() {Test t = getObj();cout << "t.m_num: " << *t.m_num << endl;return 0; };
  • 測試:
  • 解釋:

通過輸出的結果可以看到調用 Test t = getObj(); 的時候調用拷貝構造函數對返回的臨時對象進行了深拷貝得到了對象 t,在 getObj() 函數中創建的對象雖然進行了內存的申請操作,但是沒有使用就釋放掉了。

如果能夠使用臨時對象已經申請的資源,既能節省資源,還能節省資源申請和釋放的時間,如果要執行這樣的操作就需要使用右值引用了,右值引用具有移動語義,移動語義可以將資源(堆、系統對象等)通過淺拷貝從一個對象轉移到另一個對象這樣就能減少不必要的臨時對象的創建、拷貝以及銷毀,可以大幅提高 C++ 應用程序的性能。

  • 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先運行.后運行*Test(const Test& a) : m_num(new int(*a.m_num)){cout << "copy construct: my name is tom" << endl;}// 添加移動構造函數/*移動構造函數會復用其他對象內存中的資源(堆內存)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;//返回的是臨時對象,若有移動構造函數,則先調用移動構造函數return t; }int main() { /* 要求右側的對象是一個臨時對象,才會調用移動構造,若沒有移動構造則會調用拷貝構造 */Test t = getObj();cout << "t.m_num: " << *t.m_num << endl;/* 若沒有移動構造函數,使用右值引用要求:右側是一個臨時的不能取地址的對象*/Test&& t2 = getObj();cout << "t2.m_num: " << *t2.m_num << endl;return 0; };
  • 測試:

  • 解釋:

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

如果不使用移動構造,在執行 Test t = getObj() 的時候也是進行了淺拷貝,但是當臨時對象被析構的時候,類成員指針 int* m_num; 指向的內存也就被析構了,對象 t 也就無法訪問這塊內存地址了。

在測試程序中 getObj() 的返回值就是一個將亡值,也就是說是一個右值,在進行賦值操作的時候如果 = 右邊是一個右值,那么移動構造函數就會被調用。移動構造中使用了右值引用,會將臨時對象中的堆內存地址的所有權轉移給對象t,這塊內存被成功續命,因此在t對象中還可以繼續使用這塊內存。

對于需要動態申請大量資源的類,應該設計移動構造函數,以提高程序效率。需要注意的是,我們一般在提供移動構造函數的同時,也會提供常量左值引用的拷貝構造函數,以保證移動不成還可以使用拷貝構造函數。

4.&& 的特性

C++ 中,并不是所有情況下 && 都代表是一個右值引用,具體的場景體現在模板和自動類型推導中,

  • 如果是模板參數,需要指定為 T&&,如果是自動類型推導,需要指定為 auto &&,在這兩種場景下 && 被稱作未定的引用類型

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

  • eg:在函數模板中使用 &&
    例子中函數模板進行了自動類型推導,需要通過傳入的實參來確定參數 param 的實際類型。

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

由于上述代碼中存在 T&& 或者 auto&& 這種未定引用類型,當它作為參數時,有可能被一個右值引用初始化,也有可能被一個左值引用初始化,在進行類型推導時右值引用類型(&&)會發生變化,這種變化被稱為引用折疊。
在 C++11 中引用折疊的規則如下:

  • 通過右值推導 T&& 或者 auto&& 得到的是一個右值引用類型

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

  • eg:

int&& a1 = 5; //a1 為右值引用,推導出的 bb 為左值引用類型 auto&& bb = a1;//5 為右值,推導出的 bb1 為右值引用類型 auto&& bb1 = 5;int a2 = 5;int &a3 = a2; //a3 為左值引用,推導出的 cc 為左值引用類型 auto&& cc = a3;//a2 為左值,推導出的 cc1 為左值引用類型 auto&& cc1 = a2;const int& s1 = 100; const int&& s2 = 100;//s1 為常量左值引用,推導出的 dd 為常量左值引用類型 auto&& dd = s1;//s2 為常量右值引用,推導出的 ee 為常量左值引用類型 auto&& ee = s2;//x 為右值引用,不需要推導,只能通過右值初始化 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; };
  • 測試:

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

總結&&使用:

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

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

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

通過右值推導 T&& 或者 auto&& 得到的是一個右值引用類型,其余都是左值引用類型。

  • 參考:右值引用

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。