为什么++我认为是一个l值,但我不是?
为什么++我是l值? 我不是
最初有两个问题被删除,因为这是完全重复的。 所以不要投票回答增加前后差异的答案。
那么另一个回答者已经指出, ++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!
为什么我们会介绍一个你可能会问的值。 那么,在为这两种情况build立语言规则时,会出现这些术语:
- 我们想要一个定位器值。 这将代表包含可读取的值的位置。
- 我们想要expression一个expression式的价值。
以上两点来自C99标准,其中包括这个不错的脚注颇有帮助:
[名称'左值'最初来自赋值expression式E1 = E2,其中左操作数E1被要求是(可修改的)左值。 它可能更好地被认为是代表一个对象“定位值”。 有时称为“右值”的是在这个国际标准中被描述为“expression的价值”。 ]
定位器值被称为左值 ,而评估该位置的值被称为右值 。 根据C ++标准(正在谈论左值到右值的转换),这是正确的:
4.1 / 2:左值表示的对象中包含的值是右值结果。
结论
使用上面的语义,现在很清楚为什么i++
不是左值而是右值。 因为返回的expression式不再位于i
(它是递增的!),这只是可能感兴趣的值。 修改由i++
返回的值将是没有意义的,因为我们没有可以再次读取该值的位置。 所以标准说这是一个右值,因此它只能绑定到一个const引用。
然而,相反,由++i
返回的expression式是++i
的位置(左值)。 激发一个左值到右值的转换,如int a = ++i;
会读出它的价值。 或者,我们可以给它一个参考点,然后读出这个值: int &a = ++i;
。
还要注意生成右值的其他场合。 例如,所有临时对象都是右值,二进制/一元+和负值的结果以及不是引用的所有返回值expression式。 所有这些expression式都不在一个命名对象中,而只是携带一些值。 这些值当然可以由不固定的对象来备份。
下一个C ++版本将包含所谓的rvalue references
,尽pipe它们指向nonconst,也可以绑定到右值。 理由是能够从这些匿名对象中“窃取”资源,并避免复制这样做。 假设一个重载了前缀++(返回Object&
)和postfix ++(返回Object
)的类types,下面的代码将首先创build一个副本,而第二个代码会从右值中窃取资源:
Object o1(++a); // lvalue => can't steal. It will deep copy. Object o2(a++); // rvalue => steal resources (like just swapping pointers)
其他人已经解决了后期和前期增量之间的function差异。
就左值而言, i++
不能被赋值,因为它没有引用variables。 它是指一个计算值。
在分配方面,以下两种方式是没有道理的:
i++ = 5; i + 0 = 5;
因为预增值是对增量variables而不是临时副本的引用,所以++i
是左值。
为了性能的原因,优先selectpre-increment会成为一个特别好的想法,当你像迭代器对象那样递增(比如在STL中),它可能比int更重要。
似乎很多人正在解释++i
是如何++i
,而不是为什么 , 为什么在C ++标准委员会把这个function,特别是鉴于C不允许作为左值。 从这个关于comp.std.c ++的讨论看来,它是这样的,你可以把它的地址或分配给一个引用。 从Christian Bau的post摘录的代码示例:
int i; extern void f(int * p); extern void g(int&p); f(&++ i); / *将是非法的C,但C程序员 没有错过这个function* / g(++ i); / * C ++程序员会希望这是合法的* / g(i ++); / *不合法的C ++,这将是困难的 给这个有意义的语义* /
顺便说一下,如果i
碰巧是一个内置types,那么赋值语句如++i = 10
调用未定义的行为 ,因为i
在两个序列点之间修改了两次。
我在编译时遇到了左值错误
i++ = 2;
但不是当我改变它
++i = 2;
这是因为前缀运算符(++ i)更改了i中的值,然后返回i,所以它仍然可以分配给。 后缀运算符(i ++)更改i中的值,但返回旧值的临时副本,该值不能由赋值运算符修改。
回答原来的问题 :
如果你正在谈论在自己的语句中使用增量操作符,就像在for循环中一样,这真的没有什么区别。 预增量似乎更有效率,因为后增量必须自行增加并返回一个临时值,但编译器会优化这个差异。
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;
是不同的。 您select哪个增量运算符作为多运算符语句的一部分取决于预期的行为。 总之,不,你不能只select一个。 你必须了解两者。
POD预增量:
预增量应该像在对象在expression式之前递增一样,并且可以在该expression式中使用,就好像发生了一样。 因此,C ++标准comitee认为它也可以用作l值。
POD后增量:
后增加应增加POD对象,并返回一个用于expression式的副本(参见n2521第5.2.6节)。 作为一个副本实际上并不是一个variables,使得它的价值没有任何意义。
对象:
对象的前后增量只是语言的语法糖提供了一种方法来调用对象的方法。 因此在技术上,对象不受语言的标准行为的限制,而只受到方法调用施加的限制。
这些方法的实现者可以使这些对象的行为反映POD对象的行为(这不是必需的但是预期的)。
对象预增量:
这里的要求(预期的行为)是对象递增(意味着依赖于对象),并且该方法返回一个可修改的值,看起来像增量发生之后的原始对象(就像增量在这个语句之前发生的那样)。
要做到这一点是siple,只需要该方法返回一个引用它自己。 一个参考是一个l值,因此将按预期行事。
对象后增量:
这里的要求(预期的行为)是对象增量(与预增量相同),返回的值看起来像旧值,是不可变的(所以它不像一个l值) 。
不可变:
要做到这一点,你应该返回一个对象。 如果对象在expression式中被使用,它将被复制到一个临时variables中。 临时variables是常量,因此它将不可变,并按预期行事。
看起来像旧的价值:
这可以通过在进行任何修改之前创build原始副本(可能使用复制构造函数)来实现。 副本应该是深层副本,否则对原始副本的任何更改都将影响副本,因此状态将与使用该对象的expression式相关地发生变化。
和预增量一样:
根据预增量来实现后增量可能是最好的,这样可以获得相同的行为。
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; } };
关于LValue
-
在
C
(例如Perl),++i
和i++
都不是LValues。 -
在
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返回后增值。 我通常使用++ i,除非我有一个非常有说服力的理由来使用i ++ – 也就是说,如果我真的需要预增值的话。
恕我直言,这是一个很好的做法,使用“++我”的forms。 虽然前后增量之间的差异在比较整数或其他POD时并不是真正可测量的,但使用“i ++”时必须制作和返回的附加对象副本可能会对性能产生重大影响,如果对象相当昂贵复制或频繁增加。
顺便说一句 – 避免在同一个语句中对同一个variables使用多个增量运算符。 至less在C语言中,你陷入了“序列点在哪里”和未定义的操作顺序的混乱。我认为其中的一部分已经在Java和C#中被清除了。
也许这与后期增量的实施方式有关。 也许是这样的:
- 在内存中创build原始值的副本
- 增加原始variables
- 返回副本
由于副本既不是variables也不是对dynamic分配内存的引用,因此它不能是l值。
一个例子:
var i = 1; var j = i++; // i = 2, j = 1
和
var i = 1; var j = ++i; // i = 2, j = 2
C#:
public void test(int n) { Console.WriteLine(n++); Console.WriteLine(++n); } /* Output: n n+2 */