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

有帮助吗?

解决方案

正如另一位回答者已经指出的原因 ++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

第二条规则的原因是当引用是对 const 的引用时,允许使用文字初始化引用:

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

您可能会问为什么我们要引入右值。好吧,在为这两种情况构建语言规则时会出现这些术语:

  • 我们想要一个定位器值。这将代表一个包含可读取值的位置。
  • 我们想要表示表达式的值。

上述两点摘自 C99 标准,其中包含这个非常有用的脚注:

名称''lvalue''最初来自分配表达式e1 = e2,其中左操作数E1必须是(可修改的)lvalue。也许更好地将其视为表示对象“定位器值”。有时所谓的“ rvalue”在这个国际标准中被描述为“表达的价值”。]

定位器值称为 左值, ,而评估该位置所产生的值称为 右值. 。根据 C++ 标准(谈论左值到右值转换),这也是正确的:

4.1/2:LVALUE指示的对象中包含的值是RVALUE结果。

结论

使用上面的语义,现在很清楚为什么 i++ 不是左值而是右值。因为返回的表达式不位于 i 不再(它是递增的!),它只是我们感兴趣的值。修改返回的值 i++ 没有意义,因为我们没有可以再次读取该值的位置。因此标准说它是一个右值,因此它只能绑定到对 const 的引用。

然而,相比之下,返回的表达式 ++i 是位置(左值) i. 。引发左值到右值的转换,例如 int a = ++i; 将从中读取值。或者,我们可以对其进行引用,然后读出该值: int &a = ++i;.

另请注意生成右值的其他场合。例如,所有临时变量都是右值、二进制/一元 + 和减的结果以及所有不是引用的返回值表达式。所有这些表达式都不位于命名对象中,而是仅携带值。这些值当然可以由不恒定的对象来支持。

下一个 C++ 版本将包括所谓的 rvalue references 即使它们指向非常量,也可以绑定到右值。其基本原理是能够从这些匿名对象中“窃取”资源,并避免复制这样做。假设一个类类型具有重载前缀 ++ (返回 Object&) 和 postfix ++ (返回 Object),以下情况将首先导致复制,而对于第二种情况,它将从右值窃取资源:

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

其他提示

其他人已经解决后和预增量之间的功能差异。

至于作为一个的左值而言,i++不能被分配给,因为它不引用变量。它指的是一个计算值。

在分配方面,两个以下的使在相同的排序方式没有意义:

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

由于预增量返回到增加的变量,而不是一个临时副本的引用,++i是一个左值。

宁愿预增量性能方面的原因便在您递增像一个迭代器对象(例如,在STL),其很可能比int多好位重量级的一个特别好的主意。

这似乎是一个很多人都解释++i如何是一个左值,而不是在为什么,如,为什么的做了C ++标准委员会把这个功能在,特别是在,因为事实上,C没有允许无论是作为左值。从上comp.std.c ++ 这个讨论中,看来,它是让你带它的地址或指定的参考。从基督教巴乌的交摘录一个代码示例:

   int i;
   extern void f (int* p);
   extern void g (int& p);

   f (&++i);   /* Would be illegal C, but C programmers
                  havent missed this feature */
   g (++i);    /* C++ programmers would like this to be legal */
   g (i++);    /* Not legal C++, and it would be difficult to
                  give this meaningful semantics */

顺便提及,如果i恰好是一个内置的类型,则赋值语句如++i = 10调用的未定义的行为下,因为是i序列点之间改性两次。

我得到的左值错误,当我尝试编译

i++ = 2;

但不是当我将其更改为

++i = 2;

这是因为前缀操作符(++ⅰ)改变I的值,然后返回I,所以它仍然被分配给。后缀运算符(I ++)改变I的值,但返回旧的,它不能被赋值运算符被修改的临时副本。


<强>答案到原来的问题

如果你在谈论在使用增量运营商在一份声明中自己,像一个循环中,真的没有什么区别。预增量似乎是更有效的,因为后置具有递增本身并返回一个临时值,但是编译器将优化这种差异程。

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

是相同的

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

事情变得更加复杂,当你使用操作的返回值作为一个更大的语句的一部分。

即使是两个简单的语句

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

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

是不同的。这增加运营商选择的多运营商陈述的一部分使用取决于预期的行为是什么。总之,没有你不能只选择一个。你必须明白两者。

POD预增量:

预增量应作为如果该对象被表达之前递增,并且在这个表达式是可用的,犹如该发生了。因此,C ++标准comitee决定它也可以用作1-值。

POD后增量:

在后增量应该递增POD对象和用于在使用表达返回一个拷贝(见n2521第5.2.6节)。作为复制实际上不是使其成为一个-1-值的变量没有任何意义。

物件:

前处理和后上的对象增量是语言的只是语法糖提供了在对象上调用的方法的装置。因此在技术上对象不是由语言的标准行为而是仅由方法调用所施加的限制的限制。

它是由这些方法的实现者以使这些对象的行为镜像POD对象(它不是必需的,但预期)的行为。

对象前递增:

要求(预期的行为)这里是该对象被递增(意味着依赖于对象),并且该方法返回一个值,该值是可以修改的,看起来像原始对象中的增量发生后(如如果增量已在此之前发生语句)。

要做到这一点是赛普尔,只要求,该方法返回到它自身的引用。引用是一个L值,因此预期行为。

对象后递增:

要求(预期的行为)这里是对象被递增(以同样的方式作为预增量)和返回的值看起来像旧值并且是不可变(使得它不表现得像一个升 - 值)。

非易变:点击要做到这一点,你应该返回一个对象。如果对象被表达中使用将被复制到构成一个临时变量。临时变量是常量,因此它将非易变的和预期行为。

看起来像旧值:结果,这是简单地通过makeing任何修改之前创建的原始(可能使用拷贝构造)的拷贝来实现。副本应该是一个深拷贝否则到原来的任何变化将影响复制和由此状态将在关系使用对象改变为表达

在相同的方式,预增:点击这可能是最好的实现预增的条款后递增,让你得到相同的行为

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;
     }
};

关于L值

  • C (例如 Perl), 两者都不 ++i 也不 i++ 是 L 值。

  • C++, i++ 不是 LValue 但是 ++i 是。

    ++i 相当于 i += 1, ,这相当于 i = i + 1.
    结果是我们仍然处理同一个对象 i.
    可以将其视为:

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

    i++ 另一方面可以被视为:
    首先我们使用 价值i, ,然后增加 目的 i.

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

(编辑:第一次尝试出现斑点后更新)。

的主要区别在于,而++ i的返回增量后的值,该值i ++在返回预先递增值。我通常使用++我,除非我有一个非常令人信服的理由使用我++ - 即,如果我真的的需要预先递增值

IMHO它是使用“++ I”形式好的做法。而当你比较整数或其他荚前,后增量之间的差别其实并不是衡量的,额外的对象复制你必须让使用时返回“我++”可以代表显著的性能影响,如果对象是相当昂贵复制,或频繁递增。

顺便说一句 - 避免使用多个递增操作符在同一语句中的相同变量。你进入的一塌糊涂“在哪里序列点”和操作的未确定的订单,至少在C.我认为一些在Java中ND C#进行清理。

也许这和后置增量的实现方式有关。也许是这样的:

  • 在内存中创建原始值的副本
  • 增加原始变量
  • 返回副本

由于副本既不是变量也不是对动态分配内存的引用,因此它不能是左值。

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. We also want to increment a as a side effect. 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.

The value which is returned is a copy of a which is placed into a register. 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. But the copy is stored inside a register in the CPU, not in memory. 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. 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.

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. It is actually two instructions in 1. First we have to move the old value of i into the register, then we increment i. In pseudo-assembly code:

mov i, eax
inc i

eax register now contains the old version of i as a copy. 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. 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! 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?

We want to return the incremented version of a, the new version of a after the increment. The new version of a represents the current state of a, because it is the variable itself.

First a is incremented. Since we want to get the updated version of a, why not just return the variable a itself? 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. That is why you should only use a++ if you really need to return the unincremented value. For all other purposes, just use ++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! However, when I see a++ in someone else's code, I wonder why do they want to return the old value? What is it for?

C#:

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

/* Output:
n
n+2
*/
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top