i = i++不会使i增加。为什么?

39

可能重复:
为什么会陷入无限循环?

C和C++中,像i = i++这样的语句行为是未定义的,因为在同一表达式中标量对象值改变了两次而没有介于的序列点。

然而,我认为这种表达式在C#或Java中具有明确定义的行为,因为据我所知,参数的评估是从左到右进行的,并且整个过程中都有序列点。

话虽如此,我期望i = i++等同于i++。但事实并非如此。以下程序输出0

using System;
class Program
{
    static void Main(string[] args)
    {
        int i = 0;
        i = i++;
        Console.WriteLine(i);
    }
}

你能帮我理解为什么吗?

免责声明: 我完全知道上述结构的行为是否已定义,它们是愚蠢的、无用的、难以阅读的、不必要的,不应该在代码中使用。我只是好奇。


6
@下投票者:是否能留下评论? - Armen Tsirunyan
5
去年有人问了Java相关的问题,但是回答的是C#(对于这种情况两者工作方式类似):https://dev59.com/fW865IYBdhLWcg3wZdxW - BoltClock
6
这不能是一个与相同问题但不同语言的重复,因为这个问题涉及到语言规范,很明显在不同的语言中规范会有所不同。特别是C/C++和C#的规范是不同的。虽然在这方面看起来C#和Java的行为是相同的,但这并不明显。 - CodesInChaos
5
你相信一个谬论:即 " i++ " 的意思是 "增量发生在其他所有事情之后"。这是完全错误的。 实际上,增量发生在表达式被评估时!有关此谬论的更多详细信息,请参阅我的文章:http://blogs.msdn.com/b/ericlippert/archive/2009/08/10/precedence-vs-order-redux.aspx - Eric Lippert
2
@Eric:我必须承认我本能地这样想...在C++中,你不知道它会在什么时候发生,所以我假设在C#中它会在最后发生。 - Armen Tsirunyan
4
@Armen:那是一个非常普遍的误解。有关更多详细信息,请参见此答案:https://dev59.com/tXA75IYBdhLWcg3wVniI#3346729 - Eric Lippert
4个回答

47

C#中该行为是良好定义的,且计算顺序如下:

  1. 首先对左侧的i进行求值,得到变量i
  2. 然后将右侧求值为0,并将i自增(现在i==1
  3. 执行赋值操作,将i设置为0(现在i==0

最终结果为i==0

一般情况下,首先创建一个表达式树。然后按照递归顺序依次求解左侧、右侧和根节点的操作。


啊!这很有道理。我太傻了 :) - Armen Tsirunyan
3
在Java中,如果你将i声明为volatile(在我的测试中通常是1亿次迭代中的65次),你偶尔会从并行线程观察到值1 - starblue
1
i = ++i 也应该可以正常工作。 - arkoak
2
@A-b i = ++i 最终会导致 i 的值为1。但是我不确定你所说的“正常工作”是什么意思,因为这个问题没有明确的代码目标。 - CodesInChaos
为什么右侧被计算为0?为什么右侧在增加时仍然保持为零? - Andrew J. Brehm
3
后缀自增运算符 i++ 表示将 i 的值加1,但返回这个操作前的值。而前缀自增运算符 ++i 则是返回加1后的值。具体来说,i++ 的运算过程如下:temp=i;i=i+1;return temp;++i 的运算过程如下:i=i+1;return i; - CodesInChaos

21

后缀自增表达式 i++ 的结果是变量 i 的原始值。因此,在计算 i++(将 i 递增)之后,您将旧值赋给了... i

只需使用:

i++;

;)


1
我知道前增量和后增量的区别。我感到困惑是因为我期望增量的副作用在整个表达式求值之后发生...显然我错了。 - Armen Tsirunyan
@Armen 是的,C#基本上是从左到右的。 - Marc Gravell
4
我觉得表达式从左到右计算的说法有些令人困惑。我更喜欢将表达式看作一棵树,然后进行递归地计算。每个节点首先从左到右计算它的参数,最后执行自身。 - CodesInChaos

18

i = ++i这段代码做的就是你想象的那样。不过i++的行为有些不同。

使用i++时,i的值会增加,但i++的值不是i的新值,而是先前的值。所以当你执行i = i++时,你的意思是“增加i的值,然后将i设置为旧值”。


5
关于第一行代码,只需写i++;即可满足他所寻找的代码。 - Marc Gravell
3
我认为他不是在寻找任何代码,而是在寻求更好地理解C#语言。尤其是希望了解副作用发生的顺序以及在表达式中对同一变量进行多次赋值的结果行为。 - CodesInChaos
1
@Marc:除了我不需要任何代码 :) - Armen Tsirunyan
@Armen 确定;我理解了。 我只是指这个答案的上下文(因此使用引号,虽然是改述的)。 - Marc Gravell
1
前缀(即++i)的优先级高于后缀(即i++)。 - user586399
@Mr. DDD,虽然这可能是真的,但对于这个问题来说无关紧要。 - CodesInChaos

6

在进行赋值操作之前,必须先计算右侧表达式。现在,i++会计算出i的当前值,然后i的值会随即增加1。但是,赋值操作还没有执行,当操作执行时,它将使用右侧表达式计算出的值覆盖i的当前值(1),而在这种情况下,右侧表达式计算出的值是0。

关键区别在于++i(前缀递增,在递增之后会计算出i的新值)和i++或者后缀递增,它会计算出i递增之前的值。

如果你使用了++i,那么右侧表达式就会计算出1,结果为i==1。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接