提问



在C ++ 03中,表达式是右值左值


在C ++ 11中,表达式可以是:



  1. 右值

  2. 左值

  3. x值

  4. glvalue

  5. prvalue



两类已成为五大类。



  • 这些新的表达类别是什么?

  • 这些新类别如何与现有的左值和左值类别相关联?

  • C ++ 0x中的rvalue和左值类别是否与C ++ 03中的相同?

  • 为什么需要这些新类别? WG21众神只是试图迷惑我们凡人吗?


最佳参考


我想这篇文章可能不是那么简短的介绍:n3055 [104] [105]


整个大屠杀始于移动语义。一旦我们有可以移动而不是复制的表达式,突然容易掌握的规则要求区分可以移动的表达式,以及在哪个方向上。


根据我的猜测,基于草案,r/l值的区别保持不变,只有在移动事物变得混乱的情况下。


他们需要吗?如果我们希望放弃新功能,可能不会。但为了实现更好的优化,我们应该接受它们。


引用n3055: [106]



  • 左值(所谓的,历史上,
    因为左值可以出现在
    作业的左侧
    表达式)指定一个函数或
    一个东西。 [[例如:如果E
    指针类型的表达式,然后*E
    是一个左值表达式
    E的对象或功能
    点。作为另一个例子,
    调用一个函数的结果
    返回类型是左值引用
    左值。]]

  • xvalue (an
    eXpiring值)也指一个
    对象,通常接近其末尾
    一生(所以它的资源可能
    例如,被移动。 xvalue是
    某些种类的结果
    涉及右值的表达式
    引用。 [[例子:The
    调用一个函数的结果
    返回类型是右值引用
    xvalue。]]

  • glvalue (广义左值)是左值
    xvalue

  • 右值(所谓的,
    历史上,因为rvalues可以
    出现在一个右侧
    赋值表达式)是一个xvalue,
    临时对象或
    其子对象,或者是其值
    与对象无关。


  • prvalue (纯rvalue)是一个右值
    那不是xvalue。 [[例子:The
    调用一个函数的结果
    返回类型不是引用是一个
    prvalue]]



有问题的文件是这个问题的一个很好的参考,因为它显示了由于新命名法的引入而发生的标准的确切变化。

其它参考1



  这些新的表达类别是什么?



FCD(n3092)有一个很好的描述:[107]



   - 左值(所谓的,历史上,因为左值可能出现在左右
  作业的左侧
  表达式)指定一个函数或
  一个东西。 [[例子:如果E是
  那么指针类型的表达式
  * E是一个左值表达式,指的是E的对象或函数
  点。作为另一个例子,结果
  调用返回的函数
  type是左值引用是一个
  左值。 - 示例]]

  
   - 一个xvalue(一个
  eXpiring值)也指一个
  对象,通常接近其末尾
  一生(因此它的资源可能是
  例如,移动。 xvalue是
  某种表达方式的结果
  涉及右值参考(8.3.2)。 [[
  示例:调用a的结果
  返回类型为的函数
  右值参考是一个x值。 -结束
  例子]]

  
   - 一个glvalue(一般化)
  左值(lvalue)是左值或左值。

  
   -
  一个右值(所谓的历史,
  因为右值可以出现在
  作业的右侧
  表达式)是一个xvalue,一个临时的
  对象(12.2)或其子对象,或
  与a无关的值
  目的。

  
   - 一个prvalue(纯rvalue)是
  一个不是xvalue的rvalue。 [[
  示例:调用a的结果
  返回类型不是a的函数
  参考是一个prvalue。 a的价值
  文字如12,7.3e5或true是
  也是一个prvalue。 - 示例]]

  
  一切
  表达式恰好属于其中之一
  中国的基本分类
  这个分类:lvalue,xvalue或
  prvalue。这个属性
  表达式称为其值
  类别。 [[注:讨论
  第5章中的每个内置运算符
  表示它的值的类别
  收益率和价值类别
  它期望的操作数。例如,
  内置赋值运算符期望
  左操作数是左值和
  右操作数是一个prvalue
  并产生一个左值。
  用户定义的运算符是函数,
  和它们的价值类别
  期望和收益率由
  他们的参数和返回类型。 -结束
  注意



我建议你阅读整个 3.10 Lvalues和rvalues 部分。



  这些新类别如何与现有的右值和左值类别相关联?



再次:






  C ++ 0x中的右值和左值类别是否与它们在C ++ 03中的相同?



rvalues的语义特别随着移动语义的引入而发展。



  为什么需要这些新类别?



因此可以定义和支持移动构造/分配。

其它参考2


我将从你的最后一个问题开始:



  为什么需要这些新类别?



C ++标准包含许多处理表达式的值类别的规则。一些规则区分左值和右值。例如,当涉及到重载决策时。其他规则区分glvalue和prvalue。例如,您可以使用不完整或抽象类型的glvalue,但没有不完整或抽象类型的prvalue。在我们使用这个术语之前,实际需要区分glvalue/prvalue的规则是指lvalue/rvalue和它们无意中是错误的,或者包含了很多解释和例外的规则......除非rvalue是由于未命名右值参考......。因此,将glvalues和prvalues的概念作为自己的名称似乎是一个好主意。



  这些新的表达类别是什么?
  这些新类别如何与现有的右值和左值类别相关联?



我们仍然有与C ++ 98兼容的术语lvalue和rvalue。我们只是将rvalues分成两个子组,xvalues和prvalues,我们将lvalues和xvalues称为glvalues。 Xvalues是未命名的右值引用的一种新值类别。每个表达式都是以下三个中的一个:lvalue,xvalue,prvalue。维恩图看起来像这样:


    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r


功能示例:


int   prvalue();
int&  lvalue();
int&& xvalue();


但也不要忘记命名的右值引用是左值:


void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}

其它参考3



  为什么需要这些新类别? WG21众神只是试图迷惑我们凡人吗?



我不觉得其他答案(尽管其中很多都是好的)确实抓住了这个特定问题的答案。是的,这些类别等存在允许移动语义,但复杂性存在的原因之一。这就是一个在C ++ 11中不可侵犯的移动规则:


只有在毫无疑问安全的情况下才会移动。


这就是为什么存在这些类别的原因:能够谈论可以安全地离开它们的价值观,并谈论不存在的价值观。


在最早版本的r值参考中,运动很容易发生。 太容易了。当用户真的没有意思时,很容易隐藏移动的东西。


以下是移动物品安全的情况:



  1. 当它是临时或子对象时(prvalue)

  2. 当用户明确表示要移动时。



如果你这样做:


SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};


这是做什么的?在规范的旧版本中,在5个值出现之前,这将引发一个移动。当然可以。您将rvalue引用传递给构造函数,因此它绑定到采用rvalue引用的构造函数。这很明显。


这只有一个问题;你没有要求移动它。哦,你可能会说&&应该是一个线索,但这并没有改变它违反规则的事实。val并不是暂时的,因为临时工没有名字。你可能延长了临时的生命周期,但这意味着它不是临时的;它就像任何其他堆栈变量一样。


如果它不是暂时的,你没有要求移动它,那么移动错误。


显而易见的解决方案是使val成为左值。这意味着你不能离开它。好的,好的;它的名字,所以它是一个左值。


一旦你这样做,你就不能再说SomeType&&意味着同样的事情。你现在已经对命名的右值引用和未命名的右值引用进行了区分。好吧,命名的右值引用是左值;这是我们上面的解决方案。那么我们称之为未命名的右值引用(上面Func的返回值) ?


它不是左值,因为你不能从左值移动。我们需要能够通过返回&&来移动;你怎么能明确地说要移动一些东西?毕竟,这就是std::move的回归。它不是rvalue(旧式),因为它可以位于等式的左侧(事情实际上有点复杂,请看这个问题和下面的评论)。它既不是左值也不是右值;这是一种新事物。


我们拥有的是一个可以视为左值的值,除了它可以隐式移动。我们称之为xvalue。


请注意,xvalues使我们获得另外两类值:



  • prvalue实际上只是前一种rvalue的新名称,即它们是不是 xvalues的rvalues。

  • Glvalues是一组中xvalues和lvalues的并集,因为它们共享许多属性。



所以真的,这一切都归结为xvalues以及将运动限制在精确且仅限于某些地方的需要。这些地方由右值类别定义; prvalues是隐式移动,xvalues是显式移动(std::move返回xvalue)。

其它参考4


恕我直言,关于其含义的最佳解释给了我们Stroustrup +考虑了DánielSándor和Mohan的例子:[109]


Stroustrup的:



  现在我非常担心。显然,我们正陷入僵局或
  一团糟或两者皆有。我花了午餐时间做分析看看哪个
  属性(值)是独立的。只有两个
  独立财产:

  
  

      
  • has identity - 即地址,指针,用户可以确定两个副本是否相同等。

  •   
  • can be moved from - 即我们被允许在一些不确定但有效的状态下留下副本的来源

  •   

  
  这使我得出结论,正好有三种
  值(使用正则表达式使用大写字母的符号技巧)
  表示否定 - 我匆忙):

  
  

      
  • iM:具有身份,无法移动

  •   
  • im:具有身份并且可以移动(例如,将左值转换为右值参考的结果)

  •   
  • Im:没有身份,可以从第四种可能性中移出(IM:没有身份,不能被移动)不是
      在C++中使用(或者,我认为)在任何其他语言中都有用。

  •   

  
  除了这三个基本的价值分类,我们
  有两个明显的概括,对应于这两个
  独立财产:

  
  

      
  • i:有身份

  •   
  • m:可以从
  • 移动
      

  
  这导致我把这个图放在板上:
   [112]

  
  

命名


  
  我观察到我们只有有限的自由命名:两点指向
  左边(标有iMi)是或多或少的人
  形式已经lvalues和右边两点
  (标有mIm)是那些或多或少有形式的人
  有rvalues。这必须反映在我们的命名中。那是,
  W的左腿应该有与lvalue和。[[...]]相关的名称
  W的右边腿应该有与rvalue.相关的名字
  整个讨论/问题来自于引入
  右值引用和移动语义。这些概念根本不存在
  在Strachey的世界中,仅由rvalueslvalues组成。有人
  观察到的想法

  
  

      
  • 每个value都是lvaluervalue

  •   
  • lvalue不是rvaluervalue不是lvalue

  •   

  
  深深嵌入我们的意识,非常有用的属性,和
  在标准草案中可以找到这种二分法的痕迹。我们
  所有人都同意我们应该保留这些属性(并制作它们
  精确)。这进一步限制了我们的命名选择。我观察到了
  标准库措辞使用rvalue表示m(
  概括),以便保持期望和文本
  标准库应该命名W的右下角
  rvalue.

  
  这引发了对命名的集中讨论。首先,我们需要决定
  lvalue.应该lvalueiM还是概括i? LED
  通过Doug Gregor,我们列出了核心语言措辞的地方
  lvalue这个词有资格表示其中一个或另一个。一个
  列表是在大多数情况下以及最棘手/最脆弱的文本中制作的
  lvalue目前的意思是iM。这是左值的经典含义
  因为在过去没有任何动静; move是一个新颖的概念
  在C++0x中。另外,命名W lvalue的topleft点给我们
  每个值都是lvaluervalue的属性,但不是两者。

  
  因此,W的左上角是lvalue,右下角是
  是rvalue.左下角和右上角是什么意思?
  左下角是经典左值的推广,
  允许移动。所以它是generalized lvalue.我们命名它
  glvalue.你可以对缩写进行狡辩,但(我认为)不是
  与逻辑。我们认为严重使用generalized lvalue
  无论如何都会以某种方式缩写,所以我们最好这样做
  立即(或冒险混淆)。 W的右上角较少
  一般而不是右下角(现在,一如既往地称为rvalue)。那
  point代表您可以移动的对象的原始纯概念
  因为它不能再次引用(除了析构函数)。
  我喜欢specialized rvalue这个短语与generalized lvalue相反,但pure rvalue缩写为prvalue赢得了(和
  可能是正确的)。所以,W的左腿是lvalue
  glvalue右腿是prvaluervalue.顺便说一下,
  每个值都是glvalue或prvalue,但不是两者都有。

  
  这留下了W的顶部中间:im;也就是说,具有的价值
  身份和可以移动。我们真的没有任何指导
  我们为那些神秘的野兽命名。它们很重要
  使用(草案)标准文本的人,但不太可能
  成为家喻户晓的名字。我们没有发现任何真正的限制
  命名指导我们,所以我们选择'x'作为中心,未知,
  奇怪的是,xpert只有,甚至x级。

  
   [113]


其它参考5


引言



ISOC ++ 11(官方ISO/IEC 14882:2011)是C ++编程语言标准的最新版本。它包含一些新功能和概念,例如:



  • 右值参考

  • xvalue,glvalue,prvalue表达式值类别

  • 移动语义



如果我们想要理解新表达式值类别的概念,我们必须知道有rvalue和左值引用。
最好知道rvalues可以传递给非const rvalue引用。


int& r_i=7; // compile error
int&& rr_i=7; // OK


如果我们引用N3337工作草案(最相似的草案到已公布的ISOC ++ 11标准)中标题为Lvalues和rvalues的小节,我们可以对价值类别的概念有一些直觉。



   3.10 Lvalues和rvalues [[basic.lval]]

  
  1表达式根据图1中的分类法进行分类。

  
  

      
  • 左值(所谓的历史,因为左值可能出现在赋值表达式的左侧)指定一个函数
      或一个物体。 [[例子:如果E是指针类型的表达式,那么
      * E是一个左值表达式,指的是E指向的对象或函数。另一个例子,调用函数的结果
      其返回类型是左值引用是左值。 - 例子]]

  •   
  • xvalue(eXpiring值)也指对象,通常接近其生命周期的末尾(以便可以移动其资源,
      例)。 xvalue是某些表达式的结果
      涉及右值参考(8.3.2)。 [[例子:调用的结果
      返回类型为右值引用的函数是xvalue。 -结束
      例子]]

  •   
  • glvalue(generalized左值)是左值或x值。

  •   
  • 一个rvalue(历史上所谓的,因为rvalues可能出现在赋值表达式的右侧)是一个x值,一个

      临时对象(12.2)或其子对象,或不是
    的值
      与对象相关联。

  •   
  • prvalue(纯rvalue)是一个不是xvalue的rvalue。 [[示例:调用返回类型不是
    的函数的结果
      参考是一个prvalue。文字的值,例如12,7.3e5或

      true也是一个prvalue。 - 例子]]

  •   

  
  每个表达式都属于基本的一个
  此分类中的分类:lvalue,xvalue或prvalue。这个
  表达式的属性称为其值类别。



但我不太确定这个小节是否足以清楚地理解这些概念,因为通常并不是一般的,接近其生命的终点并不是真正具体,涉及右值参考并不是很清楚,和示例:调用返回类型为右值引用的函数的结果是xvalue。听起来像蛇咬着它的尾巴。


主要值类别



每个表达式都属于一个主要值类别。这些值类别是左值,右值和右值类别。


左值



表达式E属于左值类别,当且仅当E指的是ALREADY具有使其可在E外部访问的标识(地址,名称或别名)的实体时。


#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // This address ...
    std::cout<<&"www"<<std::endl; // ... and this address are the same.
    "www"; // The expression "www" in this row is an lvalue expression, because it refers to the same entity ...
    "www"; // ... as the entity the expression "www" in this row refers to.

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}


xvalues



当且仅当它是时,表达式E属于xvalue类别


- 调用函数的结果,无论是隐式还是显式,其返回类型是对返回的对象类型的右值引用,或
int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}


- 对对象类型的右值引用的强制转换,或


int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}


- 一个类成员访问表达式,指定非引用类型的非静态数据成员,其中对象表达式是xvalue,或


struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}


- 指向成员的表达式,其中第一个操作数是xvalue,第二个操作数是指向数据成员的指针。


请注意,上述规则的效果是对对象的命名rvalue引用被视为lvalues,对对象的未命名rvalue引用被视为xvalues;对函数的右值引用被视为左值,无论是否命名。


#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}


prvalues



当且仅当E既不属于左值也不属于xvalue类别时,表达式E属于prvalue类别。


struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}


混合价值类别



还有两个重要的混合价值类别。这些值类别是rvalue和glvalue类别。


右值



当且仅当E属于xvalue类别或属于prvalue类别时,表达式E属于右值类别。


请注意,此定义表示当且仅当E指的是没有任何标识使其可在E YET之外访问的实体时,表达式E属于右值类别。


glvalues



当且仅当E属于左值类别或xvalue类别时,表达式E属于glvalue类别。


实用规则



斯科特迈耶发表了一个非常有用的经验法则来区分左值与右值。[114]



  

      
  • 如果您可以获取表达式的地址,则表达式为左值。

  •   
  • 如果表达式的类型是左值引用(例如,T&或const T&等),则该表达式是左值。

  •   
  • 否则,表达式是右值。从概念上(通常也实际上),rvalues对应于临时对象,例如
      作为从函数返回或通过隐式类型创建的
      转换。大多数文字值(例如10和5.3)也是右值。

  •   


其它参考6


C ++ 03的类别太受限制,无法正确地将rvalue引用引入表达式属性。


随着它们的引入,据说一个未命名的右值引用求值为rvalue,这样重载解析更喜欢rvalue引用绑定,这将使它选择移动构造函数而不是复制构造函数。但是发现这会导致各种问题,例如动态类型和资格。 [115]


为了表明这一点,请考虑


int const&& f();

int main() {
  int &&i = f(); // disgusting!
}


在pre-xvalue草案中,允许这样做,因为在C ++ 03中,非类类型的rvalues永远不会被cv限定。但是const适用于rvalue-reference情况,因为这里我们做引用对象(= memory!),而从非类rvalues中删除const主要是为了原因是周围没有物体。


动态类型的问题具有类似的性质。在C ++ 03中,类类型的rvalues有一个已知的动态类型 - 它是该表达式的静态类型。因为要用另一种方式,你需要引用或解引用,它们计算为左值。这不是真的使用未命名的右值引用,但它们可以显示多态行为。所以要解决它,



  • 未命名的右值引用变为 xvalues 。它们可以是合格的,并且可能具有不同的动态类型。它们像预期的那样在重载期间更喜欢rvalue引用,并且不会绑定到非const左值引用。

  • 以前的rvalue(文字,由非强制转换为非参考类型创建的对象)现在变为 prvalue 。它们在重载期间具有与xvalues相同的偏好。

  • 之前的左值是一个左值。



并且完成了两个分组以捕获那些可以合格且可以具有不同动态类型( glvalues )的分组以及那些重载优先于右值引用绑定( rvalues )的分组。

其它参考7


我一直在努力解决这个问题,直到我遇到cppreference.com对价值类别的解释。[116]


它实际上相当简单,但我发现它通常以一种难以记忆的方式解释。这里有非常简单的解释。我将引用页面的某些部分:



  

主要类别


  
  主要值类别对应于表达式的两个属性:

  
  

      
  • 具有标识:可以确定表达式是否与另一个表达式引用相同的实体,例如通过比较对象的地址或它们识别的函数(直接或间接获得) ;

  •   
  • 可以从移动:移动构造函数,移动赋值运算符,或者实现移动语义的另一个函数重载可以绑定到表达式。

  •   

  
  表达:

  
  

      
  • 具有身份并且无法移动被称为左值表达式;

  •   
  • 具有身份并且可以被移动称为 xvalue表达式;

  •   
  • 没有身份且可以移动被称为 prvalue表达式;

  •   
  • 没有身份,不能移动不被使用。

  •   

  
  

左值


  
  左值(左值)表达式是具有标识并且无法从移动的表达式。

  
  

rvalue(直到C ++ 11),prvalue(从C ++ 11开始)


  
  prvalue(纯rvalue)表达式是没有标识的表达式,可以从移动。

  
  

x值


  
  xvalue(expiring value)表达式是具有标识并且可以从移动的表达式。

  
  

glvalue


  
  glvalue(generalized lvalue)表达式是一个左值或x值的表达式。它具有身份。它可能会或可能不会被移动。

  
  

rvalue(自C ++ 11起)


  
  rvalue(right value)表达式是prvalue或xvalue的表达式。它可以从移动。它可能有也可能没有身份。


其它参考8



  这些新类别如何与现有的右值和左值类别相关联?



C ++ 03左值仍然是C ++ 11左值,而C ++ 03右值在C ++ 11中称为prvalue。

其它参考9


上面的优秀答案的一个附录,即使在我阅读了Stroustrup并且认为我理解rvalue/lvalue区别后,这一点让我感到困惑。当你看到


int&& a = 3


int&&作为一种类型阅读是非常诱人的,并得出结论a是一个右值。它不是:


int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles


a有一个名字,并且事实上是一个左值。不要认为&&a类型的一部分;它只是一些东西告诉你a允许绑定到什么。


这对于构造函数中的T&&类型参数尤为重要。如果你写


Foo::Foo(T&& _t) : t{_t} {}


你将_t复制到t。你需要


Foo::Foo(T&& _t) : t{std::move(_t)} {}如果你想搬家。当我遗漏move时,我的编译器会警告我吗!

其它参考10


在C ++中,变量是一种l值(发音为ell-value)。 l值是具有持久地址(在内存中)的值。由于所有变量都有地址,因此所有变量都是l值。名称l值是因为l值是可以在赋值语句左侧的唯一值。当我们进行赋值时,赋值运算符的左侧必须是l值。因此,声明如5=6;将导致编译错误,因为5不是l值。值5没有内存,因此没有任何内容可以分配给它。 5表示5,其值不能重新分配。当l值具有分配给它的值时,将覆盖该存储器地址处的当前值。


与l值相反的是r值(发音为arr-values)。 r值是指与持久性内存地址无关的值。 r值的示例是单个数字(例如5,其评估为5)和表达式(例如2 + x,其评估变量x的值加2)。 r值通常是临时的,并且在它们出现的语句的末尾被丢弃。


以下是一些赋值语句的示例,显示了r值如何计算:


int y;      // define y as an integer variable
y = 4;      // r-value 4 evaluates to 4, which is then assigned to l-value y
y = 2 + 5;  // r-value 2 + r-value 5 evaluates to r-value 7, which is then assigned to l-value y

int x;      // define x as an integer variable
x = y;      // l-value y evaluates to 7 (from before), which is then assigned to l-value x.
x = x;      // l-value x evaluates to 7, which is then assigned to l-value x (useless!)
x = x + 1;  // l-value x + r-value 1 evaluate to r-value 8, which is then assigned to l-value x.


让我们仔细看看上面的最后一个赋值语句,因为它会导致最混乱。


x = x+1


在此语句中,变量x用于两个不同的上下文中。在赋值运算符的左侧,x用作存储值的l值(带地址的变量)。在赋值运算符的右侧,计算x以生成值(在本例中为7)。当C ++评估上述语句时,它的计算结果如下:


x = 7+1


这很明显C ++会将值8赋值给变量x。


目前,您不需要太多担心l值或r值,但是当我们开始讨论一些更高级的主题时,我们将在稍后返回它们。


这里的关键点是,在赋值的左侧,您必须具有表示内存地址(例如变量)的内容。将评估作业右侧的所有内容以生成值。