溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C語言中什么是左值引用與右值引用

發(fā)布時間:2021-10-29 09:55:12 來源:億速云 閱讀:136 作者:iii 欄目:編程語言

這篇文章主要介紹“C語言中什么是左值引用與右值引用”,在日常操作中,相信很多人在C語言中什么是左值引用與右值引用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”C語言中什么是左值引用與右值引用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

左值、右值

概念1:

左值:可以放到等號左邊的東西叫左值。

右值:不可以放到等號左邊的東西就叫右值。

概念2:

左值:可以取地址并且有名字的東西就是左值。

右值:不能取地址的沒有名字的東西就是右值。

舉例:

int a = b + c;

a是左值,有變量名,可以取地址,也可以放到等號左邊, 表達(dá)式b+c的返回值是右值,沒有名字且不能取地址,&(b+c)不能通過編譯,而且也不能放到等號左邊。

int a = 4; // a是左值,4作為普通字面量是右值

左值一般有:

  •  函數(shù)名和變量名

  •  返回左值引用的函數(shù)調(diào)用

  •  前置自增自減表達(dá)式++i、--i

  •  由賦值表達(dá)式或賦值運算符連接的表達(dá)式(a=b, a += b等)

  •  解引用表達(dá)式*p

  •  字符串字面值"abcd"

純右值、將亡值

純右值和將亡值都屬于右值。

純右值

運算表達(dá)式產(chǎn)生的臨時變量、不和對象關(guān)聯(lián)的原始字面量、非引用返回的臨時變量、lambda表達(dá)式等都是純右值。

舉例:

  •  除字符串字面值外的字面值

  •  返回非引用類型的函數(shù)調(diào)用

  •  后置自增自減表達(dá)式i++、i--

  •  算術(shù)表達(dá)式(a+b, a*b, a&&b, a==b等)

  •  取地址表達(dá)式等(&a)

將亡值

將亡值是指C++11新增的和右值引用相關(guān)的表達(dá)式,通常指將要被移動的對象、T&&函數(shù)的返回值、std::move函數(shù)的返回值、轉(zhuǎn)換為T&&類型轉(zhuǎn)換函數(shù)的返回值,將亡值可以理解為即將要銷毀的值,通過“盜取”其它變量內(nèi)存空間方式獲取的值,在確保其它變量不再被使用或者即將被銷毀時,可以避免內(nèi)存空間的釋放和分配,延長變量值的生命周期,常用來完成移動構(gòu)造或者移動賦值的特殊任務(wù)。

舉例:

class A {      xxx;  };  A a;  auto c = std::move(a); // c是將亡值  auto d = static_cast<A&&>(a); // d是將亡值

左值引用、右值引用

根據(jù)名字大概就可以猜到意思,左值引用就是對左值進行引用的類型,右值引用就是對右值進行引用的類型,他們都是引用,都是對象的一個別名,并不擁有所綁定對象的堆存,所以都必須立即初始化。

type &name = exp; // 左值引用  type &&name = exp; // 右值引用

左值引用

看代碼:

int a = 5;  int &b = a; // b是左值引用  b = 4;  int &c = 10; // error,10無法取地址,無法進行引用  const int &d = 10; // ok,因為是常引用,引用常量數(shù)字,這個常量數(shù)字會存儲在內(nèi)存中,可以取地址

可以得出結(jié)論:對于左值引用,等號右邊的值必須可以取地址,如果不能取地址,則會編譯失敗,或者可以使用const引用形式,但這樣就只能通過引用來讀取輸出,不能修改數(shù)組,因為是常量引用。

右值引用

如果使用右值引用,那表達(dá)式等號右邊的值需要時右值,可以使用std::move函數(shù)強制把左值轉(zhuǎn)換為右值。

int a = 4;  int &&b = a; // error, a是左值  int &&c = std::move(a); // ok

移動語義

談移動語義前,我們首先需要了解深拷貝與淺拷貝的概念

深拷貝、淺拷貝

直接拿代碼舉例:

class A {  public:      A(int size) : size_(size) {          data_ = new int[size];      }      A(){}      A(const A& a) {          size_ = a.size_;          data_ = a.data_;          cout << "copy " << endl;      }      ~A() {          delete[] data_;      }      int *data_;      int size_;  };  int main() {      A a(10);      A b = a;      cout << "b " << b.data_ << endl;      cout << "a " << a.data_ << endl;      return 0;  }

上面代碼中,兩個輸出的是相同的地址,a和b的data_指針指向了同一塊內(nèi)存,這就是淺拷貝,只是數(shù)據(jù)的簡單賦值,那再析構(gòu)時data_內(nèi)存會被釋放兩次,導(dǎo)致程序出問題,這里正常會出現(xiàn)double free導(dǎo)致程序崩潰的,但是不知道為什么我自己測試程序卻沒有崩潰,能力有限,沒搞明白,無論怎樣,這樣的程序肯定是有隱患的,如何消除這種隱患呢,可以使用如下深拷貝:

class A {  public:      A(int size) : size_(size) {          data_ = new int[size];      }      A(){}      A(const A& a) {          size_ = a.size_;          data_ = new int[size_];         cout << "copy " << endl;      }      ~A() {          delete[] data_;      }      int *data_;      int size_;  };  int main() {      A a(10);      A b = a;      cout << "b " << b.data_ << endl;      cout << "a " << a.data_ << endl;      return 0;  }

深拷貝就是再拷貝對象時,如果被拷貝對象內(nèi)部還有指針引用指向其它資源,自己需要重新開辟一塊新內(nèi)存存儲資源,而不是簡單的賦值。

聊完了深拷貝淺拷貝,可以聊聊移動語義啦:

移動語義,在程序喵看來可以理解為轉(zhuǎn)移所有權(quán),之前的拷貝是對于別人的資源,自己重新分配一塊內(nèi)存存儲復(fù)制過來的資源,而對于移動語義,類似于轉(zhuǎn)讓或者資源竊取的意思,對于那塊資源,轉(zhuǎn)為自己所擁有,別人不再擁有也不會再使用,通過C++11新增的移動語義可以省去很多拷貝負(fù)擔(dān),怎么利用移動語義呢,是通過移動構(gòu)造函數(shù)。

class A {  public:      A(int size) : size_(size) {          data_ = new int[size];      }      A(){}      A(const A& a) {          size_ = a.size_;          data_ = new int[size_];          cout << "copy " << endl;      }      A(A&& a) {          this->data_ = a.data_;          a.data_ = nullptr;          cout << "move " << endl;      }      ~A() {          if (data_ != nullptr) {           delete[] data_;          }      }      int *data_;      int size_;  };  int main() {      A a(10);      A b = a;      A c = std::move(a); // 調(diào)用移動構(gòu)造函數(shù)      return 0;  }

如果不使用std::move(),會有很大的拷貝代價,使用移動語義可以避免很多無用的拷貝,提供程序性能,C++所有的STL都實現(xiàn)了移動語義,方便我們使用。例如:

std::vector<string> vecs;  ...  std::vector<string> vecm = std::move(vecs); // 免去很多拷貝

注意:移動語義僅針對于那些實現(xiàn)了移動構(gòu)造函數(shù)的類的對象,對于那種基本類型int、float等沒有任何優(yōu)化作用,還是會拷貝,因為它們實現(xiàn)沒有對應(yīng)的移動構(gòu)造函數(shù)。

完美轉(zhuǎn)發(fā)

完美轉(zhuǎn)發(fā)指可以寫一個接受任意實參的函數(shù)模板,并轉(zhuǎn)發(fā)到其它函數(shù),目標(biāo)函數(shù)會收到與轉(zhuǎn)發(fā)函數(shù)完全相同的實參,轉(zhuǎn)發(fā)函數(shù)實參是左值那目標(biāo)函數(shù)實參也是左值,轉(zhuǎn)發(fā)函數(shù)實參是右值那目標(biāo)函數(shù)實參也是右值。那如何實現(xiàn)完美轉(zhuǎn)發(fā)呢,答案是使用std::forward()。

void PrintV(int &t) {      cout << "lvalue" << endl;  }  void PrintV(int &&t) {      cout << "rvalue" << endl;  }  template<typename T>  void Test(T &&t) {      PrintV(t);      PrintV(std::forward<T>(t));       PrintV(std::move(t));  }  int main() {      Test(1); // lvalue rvalue rvalue      int a = 1;      Test(a); // lvalue lvalue rvalue      Test(std::forward<int>(a)); // lvalue rvalue rvalue      Test(std::forward<int&>(a)); // lvalue lvalue rvalue      Test(std::forward<int&&>(a)); // lvalue rvalue rvalue      return 0;  }

分析

  •  Test(1):1是右值,模板中T &&t這種為萬能引用,右值1傳到Test函數(shù)中變成了右值引用,但是調(diào)用PrintV()時候,t變成了左值,因為它變成了一個擁有名字的變量,所以打印lvalue,而PrintV(std::forward<T>(t))時候,會進行完美轉(zhuǎn)發(fā),按照原來的類型轉(zhuǎn)發(fā),所以打印rvalue,PrintV(std::move(t))毫無疑問會打印rvalue。

  •  Test(a):a是左值,模板中T &&這種為萬能引用,左值a傳到Test函數(shù)中變成了左值引用,所以有代碼中打印。

  •  Test(std::forward<T>(a)):轉(zhuǎn)發(fā)為左值還是右值,依賴于T,T是左值那就轉(zhuǎn)發(fā)為左值,T是右值那就轉(zhuǎn)發(fā)為右值。

返回值優(yōu)化

返回值優(yōu)化(RVO)是一種C++編譯優(yōu)化技術(shù),當(dāng)函數(shù)需要返回一個對象實例時候,就會創(chuàng)建一個臨時對象并通過復(fù)制構(gòu)造函數(shù)將目標(biāo)對象復(fù)制到臨時對象,這里有復(fù)制構(gòu)造函數(shù)和析構(gòu)函數(shù)會被多余的調(diào)用到,有代價,而通過返回值優(yōu)化,C++標(biāo)準(zhǔn)允許省略調(diào)用這些復(fù)制構(gòu)造函數(shù)。

那什么時候編譯器會進行返回值優(yōu)化呢?

  •  return的值類型與函數(shù)的返回值類型相同

  •  return的是一個局部對象

看幾個例子:

示例1:

std::vector<int> return_vector(void) {      std::vector<int> tmp {1,2,3,4,5};      return tmp;  }  std::vector<int> &&rval_ref = return_vector();

不會觸發(fā)RVO,拷貝構(gòu)造了一個臨時的對象,臨時對象的生命周期和rval_ref綁定,等價于下面這段代碼:

const std::vector<int>& rval_ref = return_vector();

示例2:

std::vector<int>&& return_vector(void) {      std::vector<int> tmp {1,2,3,4,5};      return std::move(tmp); }  std::vector<int> &&rval_ref = return_vector();

這段代碼會造成運行時錯誤,因為rval_ref引用了被析構(gòu)的tmp。講道理來說這段代碼是錯的,但我自己運行過程中卻成功了,我沒有那么幸運,這里不糾結(jié),繼續(xù)向下看什么時候會觸發(fā)RVO。

示例3:

std::vector<int> return_vector(void) {      std::vector<int> tmp {1,2,3,4,5};      return std::move(tmp);  }  std::vector<int> &&rval_ref = return_vector();

和示例1類似,std::move一個臨時對象是沒有必要的,也會忽略掉返回值優(yōu)化。

最好的代碼:

std::vector<int> return_vector(void) {      std::vector<int> tmp {1,2,3,4,5};      return tmp;  }  std::vector<int> rval_ref = return_vector();

這段代碼會觸發(fā)RVO,不拷貝也不移動,不生成臨時對象。

到此,關(guān)于“C語言中什么是左值引用與右值引用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI