简体   繁体   English

为什么++ i被认为是l值,而i ++却不是?

[英]Why is ++i considered an l-value, but i++ is not?

为什么++ i是l值而i ++不是?

Other people have tackled the functional difference between post and pre increment. 其他人已经解决了后增量和前增量之间的功能差异。

As far as being an lvalue is concerned, i++ can't be assigned to because it doesn't refer to a variable. 左值而言, i++不能赋值,因为它没有引用变量。 It refers to a calculated value. 它指的是计算值。

In terms of assignment, both of the following make no sense in the same sort of way: 在分配方面,以下两种方式都没有意义:

i++   = 5;
i + 0 = 5;

Because pre-increment returns a reference to the incremented variable rather than a temporary copy, ++i is an lvalue. 因为预增量返回对增量变量的引用,而不是临时副本,所以++i是左值。

Preferring pre-increment for performance reasons becomes an especially good idea when you are incrementing something like an iterator object (eg in the STL) that may well be a good bit more heavyweight than an int. 当您要增加迭代器对象(例如,STL中的对象)之类的东西可能比int的权重大得多时,出于性能原因而首选pre-increation成为一个特别好的主意。

Well as another answerer pointed out already the reason why ++i is an lvalue is to pass it to a reference. 正如另一个回答者已经指出的那样, ++i是左值的原因是将其传递给引用。

int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue

The reason for the second rule is to allow to initialize a reference using a literal, when the reference is a reference to const: 第二条规则的原因是,当引用是对const的引用时,允许使用文字初始化引用:

void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

Why do we introduce an rvalue at all you may ask. 您为什么要问我们为什么要引入右值。 Well, these terms come up when building the language rules for these two situations: 好了,在为以下两种情况建立语言规则时会出现这些术语:

  • We want to have a locator value. 我们想要一个定位器值。 That will represent a location which contains a value that can be read. 那将代表一个包含可以读取的值的位置。
  • We want to represent the value of an expression. 我们要表示一个表达式的值。

The above two points are taken from the C99 Standard which includes this nice footnote quite helpful: 以上两点摘自C99标准,其中包括这个非常好的脚注,非常有帮助:

[ The name ''lvalue'' comes originally from the assignment expression E1 = E2, in which the left operand E1 is required to be a (modifiable) lvalue. [名称“左值”最初来自赋值表达式E1 = E2,其中左操作数E1必须是(可修改的)左值。 It is perhaps better considered as representing an object ''locator value''. 最好将其表示为对象“定位器值”。 What is sometimes called ''rvalue'' is in this International Standard described as the ''value of an expression''. 在本国际标准中有时称为“ rvalue”的内容称为“表达式的值”。 ] ]

The locator value is called lvalue , while the value resulting from evaluating that location is called rvalue . 定位器的值称为lvalue ,而评估该位置所得到的值称为rvalue That's right according also to the C++ Standard (talking about the lvalue-to-rvalue conversion): 根据C ++标准,这也是正确的(谈论从左值到右值的转换):

4.1/2: The value contained in the object indicated by the lvalue is the rvalue result. 4.1 / 2:由左值表示的对象中包含的值是右值结果。

Conclusion 结论

Using the above semantics, it is clear now why i++ is no lvalue but an rvalue. 使用上述语义,现在很清楚为什么i++不是左值而是右值。 Because the expression returned is not located in i anymore (it's incremented!), it is just the value that can be of interest. 因为返回的表达式不再位于i (它已递增!),所以它只是可能值得关注的值。 Modifying that value returned by i++ would make not sense, because we don't have a location from which we could read that value again. 修改i++返回的值是没有意义的,因为我们没有可以再次读取该值的位置。 And so the Standard says it is an rvalue, and it thus can only bind to a reference-to-const. 因此,标准说它是一个右值,因此它只能绑定到对常量的引用。

However, in constrast, the expression returned by ++i is the location (lvalue) of i . 然而,在constrast,表达由返回++i是的位置(左值) i Provoking an lvalue-to-rvalue conversion, like in int a = ++i; 引发从左值到右值的转换,例如int a = ++i; will read the value out of it. 将从中读取值。 Alternatively, we can make a reference point to it, and read out the value later: int &a = ++i; 另外,我们可以对其进行参考,然后稍后读出该值: int &a = ++i; .

Note also the other occasions where rvalues are generated. 还请注意生成右值的其他情况。 For example, all temporaries are rvalues, the result of binary/unary + and minus and all return value expressions that are not references. 例如,所有临时变量都是右值,二进制/一元+和负号的结果以及所有不是引用的返回值表达式。 All those expressions are not located in an named object, but carry rather values only. 所有这些表达式都不位于命名对象中,而是仅带有值。 Those values can of course be backed up by objects that are not constant. 这些值当然可以由不恒定的对象备份。

The next C++ Version will include so-called rvalue references that, even though they point to nonconst, can bind to an rvalue. 下一个C ++版本将包含所谓的rvalue references ,即使它们指向非常量,也可以绑定到右值。 The rationale is to be able to "steal" away resources from those anonymous objects, and avoid copies doing that. 基本原理是能够“窃取”那些匿名对象的资源,并避免使用副本进行复制。 Assuming a class-type that has overloaded prefix ++ (returning Object& ) and postfix ++ (returning Object ), the following would cause a copy first, and for the second case it will steal the resources from the rvalue: 假设一个类类型的前缀++(返回Object& )和后缀++(返回Object )都已重载,则以下内容将首先导致复制,而在第二种情况下,它将从右值中窃取资源:

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)

It seem that a lot of people are explaining how ++i is an lvalue, but not the why , as in, why did the C++ standards committee put this feature in, especially in light of the fact that C doesn't allow either as lvalues. 似乎很多人都在解释++i是一个左值,但为什么这样 ,例如, 为什么 C ++标准委员会采用了此功能,尤其是考虑到C不允许将其作为左值的事实。左值。 From this discussion on comp.std.c++ , it appears that it is so you can take its address or assign to a reference. 对comp.std.c ++的讨论中可以看出,您可以使用它的地址或将其分配给引用。 A code sample excerpted from Christian Bau's post: 克里斯蒂安·鲍(Christian Bau)的帖子摘录的代码示例:

\n   int i; \n   extern void f (int* p); extern void f(int * p);\n   extern void g (int& p); extern void g(int&p);\n\n   f (&++i); f(&++ i); /* Would be illegal C, but C programmers / *将是非法的C,但是C程序员\n                  havent missed this feature */ 尚未错过此功能* /\n   g (++i); g(++ i); /* C++ programmers would like this to be legal */ / * C ++程序员希望这是合法的* /\n   g (i++); g(i ++); /* Not legal C++, and it would be difficult to / *不是合法的C ++,因此很难\n                  give this meaningful semantics */ 给出有意义的语义* /\n\n

By the way, if i happens to be a built-in type, then assignment statements such as ++i = 10 invoke undefined behavior , because i is modified twice between sequence points. 顺便说一句,如果i碰巧是内置类型,则诸如++i = 10类的赋值语句会调用未定义的行为 ,因为i在序列点之间被修改了两次。

I'm getting the lvalue error when I try to compile 尝试编译时出现左值错误

i++ = 2;

but not when I change it to 但是当我将其更改为

++i = 2;

This is because the prefix operator (++i) changes the value in i, then returns i, so it can still be assigned to. 这是因为前缀运算符(++ i)更改i中的值,然后返回i,因此仍可以将其分配给它。 The postfix operator (i++) changes the value in i, but returns a temporary copy of the old value , which cannot be modified by the assignment operator. 后缀运算符(i ++)更改了i中的值,但返回了旧的临时副本,该副本不能由赋值运算符修改。


Answer to original question : 回答原始问题

If you're talking about using the increment operators in a statement by themselves, like in a for loop, it really makes no difference. 如果您是在谈论自己在语句中使用增量运算符(例如在for循环中),则实际上没有什么区别。 Preincrement appears to be more efficient, because postincrement has to increment itself and return a temporary value, but a compiler will optimize this difference away. 预增量似乎更有效,因为后增量必须自己递增并返回一个临时值,但是编译器会消除这种差异。

for(int i=0; i<limit; i++)
...

is the same as 是相同的

for(int i=0; i<limit; ++i)
...

Things get a little more complicated when you're using the return value of the operation as part of a larger statement. 当您将操作的返回值用作较大语句的一部分时,事情会变得有些复杂。

Even the two simple statements 即使是两个简单的陈述

int i = 0;
int a = i++;

and

int i = 0;
int a = ++i;

are different. 是不同的。 Which increment operator you choose to use as a part of multi-operator statements depends on what the intended behavior is. 选择用作多运算符语句一部分的增量运算符取决于预期的行为。 In short, no you can't just choose one. 简而言之,不,您不能只选择一个。 You have to understand both. 您必须了解两者。

POD Pre increment: POD预增:

The pre-increment should act as if the object was incremented before the expression and be usable in this expression as if that happened. 预增量应该像对象在表达式之前增加一样,并且可以在表达式中使用,就像发生了一样。 Thus the C++ standards comitee decided it can also be used as an l-value. 因此,C ++标准委员会决定也可以将其用作l值。

POD Post increment: POD发布增量:

The post-increment should increment the POD object and return a copy for use in the expression (See n2521 Section 5.2.6). 后递增应递增POD对象并返回用于表达式的副本(请参见n2521第5.2.6节)。 As a copy is not actually a variable making it an l-value does not make any sense. 由于副本实际上并不是使它成为l值的变量,因此没有任何意义。

Objects: 对象:

Pre and Post increment on objects is just syntactic sugar of the language provides a means to call methods on the object. 对象上的Pre和Post增量仅仅是该语言的语法糖,它提供了一种在对象上调用方法的方法。 Thus technically Objects are not restricted by the standard behavior of the language but only by the restrictions imposed by method calls. 因此,从技术上讲,对象不受语言的标准行为限制,而仅受方法调用施加的限制。

It is up to the implementor of these methods to make the behavior of these objects mirror the behavior of the POD objects (It is not required but expected). 这些方法的实现者必须使这些对象的行为反映POD对象的行为(这不是必需的,但是是预期的)。

Objects Pre-increment: 对象预递增:

The requirement (expected behavior) here is that the objects is incremented (meaning dependant on object) and the method return a value that is modifiable and looks like the original object after the increment happened (as if the increment had happened before this statement). 这里的要求(预期行为)是对象增加(意味着依赖于对象),并且该方法返回一个可修改的值,该值在发生增量后看起来像原始对象(就像该增量发生在此语句之前)。

To do this is siple and only require that the method return a reference to it-self. 要做到这一点很简单,只要求该方法返回对自身的引用。 A reference is an l-value and thus will behave as expected. 引用是一个l值,因此将表现出预期的效果。

Objects Post-increment: 对象后递增:

The requirement (expected behavior) here is that the object is incremented (in the same way as pre-increment) and the value returned looks like the old value and is non-mutable (so that it does not behave like an l-value). 这里的要求(预期行为)是对象增加(与预增量相同),并且返回的值看起来像旧值并且是不可变的(因此它的行为不像l值) 。

Non-Mutable: 不可更改:
To do this you should return an object. 为此,您应该返回一个对象。 If the object is being used within an expression it will be copy constructed into a temporary variable. 如果在表达式中使用该对象,它将被复制构造为一个临时变量。 Temporary variables are const and thus it will non-mutable and behave as expected. 临时变量是const,因此它将是不可变的,并且行为符合预期。

Looks like the old value: 看起来像旧值:
This is simply achieved by creating a copy of the original (probably using the copy constructor) before makeing any modifications. 这可以通过在进行任何修改之前创建原始副本(可能使用副本构造函数)来简单地实现。 The copy should be a deep copy otherwise any changes to the original will affect the copy and thus the state will change in relationship to the expression using the object. 该副本应为深层副本,否则对原始副本的任何更改都会影响该副本,因此状态将与使用该对象的表达式的关系发生变化。

In the same way as pre-increment: 以与预增量相同的方式:
It is probably best to implement post increment in terms of pre-increment so that you get the same behavior. 最好以预增量的方式实现后增量,以便获得相同的行为。

class Node // Simple Example
{
     /*
      * Pre-Increment:
      * To make the result non-mutable return an object
      */
     Node operator++(int)
     {
         Node result(*this);   // Make a copy
         operator++();         // Define Post increment in terms of Pre-Increment

         return result;        // return the copy (which looks like the original)
     }

     /*
      * Post-Increment:
      * To make the result an l-value return a reference to this object
      */
     Node& operator++()
     {
         /*
          * Update the state appropriatetly */
         return *this;
     }
};

Regarding LValue 关于左值

  • In C (and Perl for instance), neither ++i nor i++ are LValues. C (和Perl例如), 既不 ++i也不i++是左值。

  • In C++ , i++ is not and LValue but ++i is. C++i++不是,LValue是++i

    ++i is equivalent to i += 1 , which is equivalent to i = i + 1 . ++i等效于i += 1 ,等效于i = i + 1
    The result is that we're still dealing with the same object i . 结果是我们仍在处理同一对象i
    It can be viewed as: 它可以被视为:

     int i = 0; ++i = 3; // is understood as i = i + 1; // i now equals 1 i = 3; 

    i++ on the other hand could be viewed as: 另一方面, i++可以被视为:
    First we use the value of i , then increment the object i . 首先,我们使用i ,然后递增对象 i

     int i = 0; i++ = 3; // would be understood as 0 = 3 // Wrong! i = i + 1; 

(edit: updated after a blotched first-attempt). (编辑:在第一次尝试失败后更新)。

The main difference is that i++ returns the pre-increment value whereas ++i returns the post-increment value. 主要区别在于,i ++返回前递增值,而++ i返回后递增值。 I normally use ++i unless I have a very compelling reason to use i++ - namely, if I really do need the pre-increment value. 我通常使用++ i,除非我有非常令人信服的理由使用i ++-即,如果我确实确实需要pre-increment值。

IMHO it is good practise to use the '++i' form. 恕我直言,使用“ ++ i”形式是一种好习惯。 While the difference between pre- and post-increment is not really measurable when you compare integers or other PODs, the additional object copy you have to make and return when using 'i++' can represent a significant performance impact if the object is either quite expensive to copy, or incremented frequently. 当您比较整数或其他POD时,虽然前增量和后增量之间的差异实际上无法测量,但是如果对象非常昂贵,则在使用“ i ++”时必须创建并返回的其他对象副本可能会对性能产生重大影响。复制或频繁增加。

By the way - avoid using multiple increment operators on the same variable in the same statement. 顺便说一句-避免对同一语句中的同一变量使用多个增量运算符。 You get into a mess of "where are the sequence points" and undefined order of operations, at least in C. I think some of that was cleaned up in Java nd C#. 至少在C语言中,您陷入了“序列点在哪里”和操作的不确定顺序的混乱局面。我认为其中一些已在Java nd C#中清除。

Maybe this has something to do with the way the post-increment is implemented. 也许这与后增量的实现方式有关。 Perhaps it's something like this: 也许是这样的:

  • Create a copy of the original value in memory 在内存中创建原始值的副本
  • Increment the original variable 增加原始变量
  • Return the copy 退还副本

Since the copy is neither a variable nor a reference to dynamically allocated memory, it can't be a l-value. 由于副本既不是变量,也不是对动态分配的内存的引用,因此它不能是l值。

How does the compiler translate this expression? 编译器如何翻译此表达式? a++

We know that we want to return the unincremented version of a , the old version of a before the increment. 我们知道,我们要返回的unincremented版本a增量之前 ,老版的。 We also want to increment a as a side effect. 我们还想增加a作为副作用。 In other words, we are returning the old version of a , which no longer represents the current state of a , it no longer is the variable itself. 换句话说,我们正在返回旧版本的a ,它不再代表的当前状态a ,它不再是变量本身。

The value which is returned is a copy of a which is placed into a register . 该返回的值是副本a被放入寄存器 Then the variable is incremented. 然后变量增加。 So here you are not returning the variable itself, but you are returning a copy which is a separate entity! 因此,这里您没有返回变量本身,而是返回了一个单独实体的副本! This copy is temporarily stored inside a register and then it is returned. 该副本临时存储在寄存器中,然后返回。 Recall that a lvalue in C++ is an object that has an identifiable location in memory . 回想一下,C ++中的左值是在内存中具有可识别位置的对象。 But the copy is stored inside a register in the CPU, not in memory. 但是副本存储在CPU的寄存器中,而不是内存中。 All rvalues are objects which do not have an identifiable location in memory . 所有右值都是在内存中没有可识别位置的对象。 That explains why the copy of the old version of a is an rvalue, because it gets temporarily stored in a register. 这就解释了为什么旧版本a的副本是右值的原因,因为它被临时存储在寄存器中。 In general, any copies, temporary values, or the results of long expressions like (5 + a) * b are stored in registers, and then they are assigned into the variable, which is a lvalue. 通常,任何副本,临时值或长表达式(5 + a) * b(5 + a) * b都存储在寄存器中,然后将它们分配到变量中,该变量为左值。

The postfix operator must store the original value into a register so that it can return the unincremented value as its result. 后缀运算符必须将原始值存储到寄存器中,以便它可以返回未递增的值作为结果。 Consider the following code: 考虑以下代码:

for (int i = 0; i != 5; i++) {...}

This for-loop counts up to five, but i++ is the most interesting part. 这个for循环最多可以计数五个,但是i++是最有趣的部分。 It is actually two instructions in 1. First we have to move the old value of i into the register, then we increment i . 实际上,它是1中的两条指令。首先,我们必须将i的旧值移入寄存器,然后再递增i In pseudo-assembly code: 在伪汇编代码中:

mov i, eax
inc i

eax register now contains the old version of i as a copy. eax寄存器现在包含i的旧版本作为副本。 If the variable i resides in the main memory, it might take the CPU a lot of time to go and get the copy all the way from the main memory and move it into the register. 如果变量i驻留在主存储器中,则可能要花费大量的CPU时间才能从主存储器中获取所有副本并将其复制到寄存器中。 That is usually very fast for modern computer systems, but if your for-loop iterates a hundred thousand times, all those extra operations start to add up! 对于现代计算机系统来说,这通常非常快,但是如果您的for循环迭代了十万次,那么所有这些额外的操作就会加起来! It would be a significant performance penalty. 这将是巨大的性能损失。

Modern compilers are usually smart enough to optimize away this extra work for integer and pointer types. 现代编译器通常很聪明,可以优化整数和指针类型的这项工作。 For more complicated iterator types, or maybe class types, this extra work potentially might be more costly. 对于更复杂的迭代器类型,或者可能是类类型,这种额外的工作可能会更加昂贵。

What about the prefix increment ++a ? 前缀增量++a呢?

We want to return the incremented version of a , the new version of a after the increment. 我们希望返回的递增版本a ,新版本的a增量之后 The new version of a represents the current state of a , because it is the variable itself. 新版本的a代表的当前状态a ,因为它是变量本身。

First a is incremented. 首先, a增加。 Since we want to get the updated version of a , why not just return the variable a itself ? 因为我们想要得到的更新版本a ,为什么不返回变量a本身 We do not need to make a temporary copy into the register to generate an rvalue. 我们无需将临时副本复制到寄存器中即可生成右值。 That would require unnecessary extra work. 这将需要不必要的额外工作。 So we just return the variable itself as an lvalue. 因此,我们只是将变量本身作为左值返回。

If we don't need the unincremented value, there's no need for the extra work of copying the old version of a into a register, which is done by the postfix operator. 如果我们不需要增加的值,则无需进行额外的工作,即将旧版本的a复制到寄存器中,这是由postfix运算符完成的。 That is why you should only use a++ if you really need to return the unincremented value. 这就是为什么仅在确实需要返回未增加的值的情况下才应使用a++的原因。 For all other purposes, just use ++a . 对于其他所有目的,只需使用++a By habitually using the prefix versions, we do not have to worry about whether the performance difference matters. 通过习惯性地使用前缀版本,我们不必担心性能差异是否很重要。

Another advantage of using ++a is that it expresses the intent of the program more directly: I just want to increment a ! 使用++a另一个好处是,它可以更直接地表达程序的意图:我只想增加a However, when I see a++ in someone else's code, I wonder why do they want to return the old value? 但是,当我在别人的代码中看到a++时,我想知道他们为什么要返回旧值? What is it for? 这是为了什么

C#: C#:

public void test(int n)
{
  Console.WriteLine(n++);
  Console.WriteLine(++n);
}

/* Output:
n
n+2
*/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM