日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

詳解C++中的左值,純右值和將亡值_C 語言

作者:Mi?ronin ? 更新時間: 2022-11-16 編程語言

引入

C++中本身是存在左值,右值的概念,但是在C11中又出現了左值,純右值,將亡值得概念;這里我們主要介紹這些值的概念。

一.表達式

定義:由運算符和運算對象構成的計算式(類似數學中的算術表達式)

每個 C++ 表達式(帶有操作數的操作符、字面量、變量名等)可按照兩種獨立的特性加以辨別:**類型和值類別 **(value category)。每個表達式都具有某種非引用類型,且每個表達式只屬于三種基本值類別中的一種:純右值 (prvalue)、亡值 (xvalue)、左值 (lvalue)。

二.值類別

對于表達式來說:表達式是可以求值的,對表達式求值將得到一個結果,這個結果有兩個屬性:類型和值類別。

在C++11以后表達式按值類別分類,必然屬于以下三者之一:

  • 左值
  • 將亡值
  • 純右值

其中,左值和將亡值合稱為泛左值,純右值和將亡值合稱為右值

三.左值

左值:能夠用&取地址的表達式為左值表達式

下列表達式是左值表達式:

  1. 變量、函數、模板形參對象 (C++20 起)或數據成員的名字,不論類型,例如 std::cin 或 std::endl。即使變量的類型是右值引用,由它的名字構成的表達式仍是左值表達式;
  2. 返回類型是左值引用的函數調用或重載運算符表達式,例如 std::getline(std::cin, str)、std::cout << 1、str1 = str2 或 ++it;
  3. a = b,a += b,a %= b,以及所有其他內建的賦值及復合賦值表達式;
  4. ++a 和 --a,內建的前置自增與前置自減表達式;
  5. *p,內建的間接尋址表達式;
  6. a[n] 和 n[a],內建的下標表達式,當 a[n] 中的一個操作數是數組左值時 (C++11 起);
  7. a.m,對象成員表達式,除了 m 是成員枚舉項或非靜態成員函數,或者 a 是右值而 m 是對象類型的非靜態數據成員的情況;
  8. p->m,內建的指針成員表達式,除了 m 是成員枚舉項或非靜態成員函數的情況;
  9. a.*mp,對象的成員指針表達式,其中 a 是左值且 mp 是數據成員指針;
  10. p->*mp,內建的指針的成員指針表達式,其中 mp 是數據成員指針;
  11. a, b,內建的逗號表達式,其中 b 是左值;
  12. a ? b : c,對某些 b 和 c 的三元條件表達式(例如,當它們都是同類型左值時,但細節見定義);
  13. 字符串字面量,例如 “Hello, world!”;
  14. 轉換到左值引用類型的轉型表達式,例如 static_cast<int&>(x);
  15. 返回類型是到函數的右值引用的函數調用表達式或重載的運算符表達式;(C++11 起)
  16. 轉換到函數的右值引用類型的轉型表達式,如 static_cast<void (&&)(int)>(x)。(C++11 起)

性質:

  1. 可以通過內建的取址運算符取左值的地址:&++i[1] 及 &std::endl 是合法表達式。
  2. 可修改的左值可用作內建賦值和內建復合賦值運算符的左操作數。
  3. 左值可以用來初始化左值引用;這會將一個新名字關聯給該表達式所標識的對象。

四.純右值

滿足下列條件之一:

1.本身就是純粹的字面值,如3,false,12.13

2.求值結果相當于字面值或是一個不具名的臨時對象

下列表達式是純右值表達式:

  1. (除了字符串字面量之外的)字面量,例如 42、true 或 nullptr;
  2. 返回類型是非引用的函數調用或重載運算符表達式,例如 str.substr(1, 2)、str1 + str2 或 it++;
  3. a++ 和 a–,內建的后置自增與后置自減表達式;
  4. a + b、a % b、a & b、a << b,以及其他所有內建的算術表達式;
  5. a && b、a || b、!a,內建的邏輯表達式;
  6. a < b、a == b、a >= b 以及其他所有內建的比較表達式;
  7. &a,內建的取地址表達式;
  8. a.m,對象成員表達式,其中 m 是成員枚舉項或非靜態成員函數[2],或其中 a 是右值且 m 是非- - 引用類型的非靜態數據成員 (C++11 前);
  9. p->m,內建的指針成員表達式,其中 m 是成員枚舉項或非靜態成員函數[2];
  10. a.*mp,對象的成員指針表達式,其中 mp 是成員函數指針[2],或其中 a 是右值且 mp 是數據成員指針 (C++11 前);
  11. p->*mp,內建的指針的成員指針表達式,其中 mp 是成員函數指針[2];
  12. a, b,內建的逗號表達式,其中 b 是右值;
  13. a ? b : c,對某些 b 和 c 的三元條件表達式(細節見定義);
  14. 轉換到非引用類型的轉型表達式,例如 static_cast(x)、std::string{} 或 (int)42;
  15. this 指針;
  16. 枚舉項;
  17. 非類型模板形參,除非它的類型是類或 (C++20 起)左值引用類型;
  18. lambda 表達式,例如 [](int x){ return x * x; };(C++11 起)
  19. requires 表達式,例如 requires (T i) { typename T::type; };(C++20 起)
  20. 概念的特化,例如 std::equality_comparable (C++20 起)

性質:

純右值不具有多態:它所標識的對象的動態類型始終是該表達式的類型。

非類非數組的純右值不能有 cv 限定,除非它被實質化以綁定到 cv 限定類型的引用 (C++17 起)。(注意:函數調用或轉型表達式可能生成非類的 cv 限定類型的純右值,但它的 cv 限定符通常被立即剝除。)

純右值不能具有不完整類型(除了類型 void(見下文),或在 decltype 說明符中使用之外)

純右值不能具有抽象類類型或它的數組類型。

易混:

++i是左值,i++是右值

前者,對i加1后再賦給i,最終的返回值就是i,所以,++i的結果是具名的,名字就是i;而對于i++而言,是先對i進行一次拷貝,將得到的副本作為返回結果,然后再對i加1,由于i++的結果是對i加1前i的一份拷貝,所以它是不具名的。

假設自增前i的值是6,那么,++i得到的結果是7,這個7有個名字,就是i;而i++得到的結果是6,這個6是i加1前的一個副本,它沒有名字,i不是它的名字,i的值此時也是7。可見,++i和i++都達到了使i加1的目的,但兩個表達式的結果不同。

解引用表達式 * p是左值,取地址表達式 &a 是純右值。

&(*p) 一定是正確的,因為 *p得到的是p指向的實體,&( *p)得到的就是這一實體的地址,正是p的值。由于 &(*p)的正確,所以 *p是左值。而對&a而言,得到的是a的地址,相當于unsigned int型的字面值,所以是純右值。

a+b、a&&b、ab 都是純右值

a+b得到的是不具名的臨時對象,而 a&&b 和 ab 的結果非 true 即 false,相當于字面值。

五.將亡值

在C++11之前的右值和C++11中的純右值是等價的。C++11中的將亡值是隨著右值引用的引入而新引入的。換言之,“將亡值”概念的產生,是由右值引用的產生而引起的,將亡值與右值引用息息相關。所謂的將亡值表達式,就是下列表達式:

  • 返回右值引用的函數的調用表達式
  • 轉換為右值引用的轉換函數的調用表達式

在C++11中,我們用左值去初始化一個對象或為一個已有對象賦值時,會調用拷貝構造函數或拷貝賦值運算符來拷貝資源(所謂資源,就是指new出來的東西),而當我們用一個右值(包括純右值和將亡值)來初始化或賦值時,會調用移動構造函數或移動賦值運算符來移動資源,從而避免拷貝,提高效率。當該右值完成初始化或賦值的任務時,它的資源已經移動給了被初始化者或被賦值者,同時該右值也將會馬上被銷毀(析構)。

也就是說,當一個右值準備完成初始化或賦值任務時,它已經“將亡”了。而上面1)和2)兩種表達式的結果都是不具名的右值引用,它們屬于右值。

又因為

1)這種右值是與C++11新生事物——“右值引用”相關的“新右值”

2)這種右值常用來完成移動構造或移動賦值的特殊任務,扮演著“將亡”的角色,所以C++11給這類右值起了一個新的名字——將亡值。

下列表達式是將亡值表達式:

  1. 返回類型為對象的右值引用的函數調用或重載運算符表達式,例如 std::move(x);
  2. a[n],內建的下標表達式,它的操作數之一是數組右值;
  3. a.m,對象成員表達式,其中 a 是右值且 m 是非引用類型的非靜態數據成員;
  4. a.*mp,對象的成員指針表達式,其中 a 是右值且 mp 是數據成員指針;
  5. a ? b : c,對某些 b 和 c 的三元條件表達式(細節見定義);
  6. 轉換到對象的右值引用類型的轉型表達式,例如 static_cast<char&&>(x);
  7. 在臨時量實質化后,任何指代該臨時對象的表達式。(C++17 起)

性質:

1.與右值相同。

2.與泛左值相同。

特別是,與所有的右值類似,亡值可以綁定到右值引用上,而且與所有的泛左值類似,亡值可以是多態的,而且非類的亡值可以有 cv 限定。

六.注意

1)字符串字面值是左值。

不是所有的字面值都是純右值,字符串字面值是唯一例外。

早期C++將字符串字面值實現為char型數組,實實在在地為每個字符都分配了空間并且允許程序員對其進行操作,

cout<<&("abc")<<endl;
const char *p_char="abc";//注意不是char *p_char=&("abc");

這樣的代碼都是可以編譯通過的。

2)具名的右值引用是左值,不具名的右值引用是右值。

void foo(X&& x) 
{ 
    X anotherX = x; //后面還可以訪問x
}

上面X是自設計的類型,并且,其有一個指針成員p指向了在堆中分配的內存;參數x是X的右值引用。如果將x視為右值,那么,X another X = x;一句將調用X類的移動構造函數,而我們知道,這個移動構造函數的主要工作就是將x的p指針的值賦給anotherX的p指針,然后將x的p指針置為nullptr。而在后面,我們還可以訪問x,也就是可以訪問x.p,而此時x.p已經變成了nullptr,這就可能發生意想不到的錯誤。

3)注釋

①只有當存在兩個或兩個以上的運算對象時才需要運算符連接,單獨的運算對象也可以是表達式,例如上面提到的字面值和變量。

②確切說,是表達式的結果的值類別,但我們一般不刻意區分表達式和表達式的求值結果,所以這里稱“表達式的值類別”。

③當我們將函數名作為一個值來使用時,該函數名自動轉換為指向對應函數的指針。

④關于右值引用本身,沒什么可說的,就是指可以綁定到右值上的引用,用"&&"表示,如int&&rra=6;。相比之下,與右值引用相關的一些主題,如移動語義、引用疊加、完美轉發等,更值得我們深入探討。這些內容,在下在后續文章中都會詳細介紹。

⑤前提是該右值(如自定義的類X)有移動構造函數或移動賦值運算符可供調用(有時候是沒有的,關于這些知識,后續文章在講移動構造函數和移動賦值運算符時會詳述)。

⑥在本文的例二中,如果將get_a_X()的返回值由X的右值引用改為X對象,則get_a_X()是純右值表達式(如前所述,返回非引用類型的函數調用是純右值),此時Foo(get_a_X());一句調用的仍然是類X的移動構造函數,這就是一個純右值完成移動構造的例子。

原文鏈接:https://blog.csdn.net/weixin_56935264/article/details/125351781

欄目分類
最近更新