X = X++;与X++有什么区别?(涉及IT技术)

59

你以前试过这个吗?

static void Main(string[] args)
{
    int x = 10;
    x = x++;
    Console.WriteLine(x);
}

输出:10。

但是对于

static void Main(string[] args)
{
    int x = 10;
    x++;
    Console.WriteLine(x);
}

输出:11。

有人能解释一下为什么会是这样吗?


4
故事的寓意是要避免将这种写法写入生产代码中 :) - Robert Paulson
我完全同意你的观点,正如Jon所说,我们应该避免将这些代码片段写入生产代码中,因为这可能会导致许多错误的答案,在任何项目中都可能引起很多问题。 - Ahmed Atia
1
请注意,在 C 和 C++ 中,这两个输出都是 11。这完全取决于编译器是先执行赋值还是后置增量。C 和 C++ 先执行赋值,然后再执行后置增量。 - Alain
1
@Alain 对不起,Alain,但你错了。在C++中使用g++编译器的输出是10和11。 - Zaid Khan
说实话,这是一个未定义的表达式。但你仍然是错的。 :) - Zaid Khan
11个回答

78

X++会先将值加1,然后返回它的旧值。

因此,在这种情况下:

static void Main(string[] args)
{
    int x = 10;
    x = x++;
    Console.WriteLine(x);
}
你在11处只有短暂的一刻,之后它回到了10,因为(x++)的返回值是10。您也可以通过以下方式获得相同的结果:
static int plusplus(ref int x)
{
  int xOld = x;
  x++;
  return xOld;
}

static void Main(string[] args)
{
    int x = 10;
    x = plusplus(x);
    Console.WriteLine(x);
}

值得一提的是,如果你执行以下操作,你将会得到预期的结果 11:

static void Main(string[] args)
{
    int x = 10;
    x = ++x;
    Console.WriteLine(x);
}

3
x = ++x 看起来相当无用,我并不特别看到这两个结构的任何理由...只需要 x++ 或 ++x..或者 x = x + ((Ceiling(Abs(x) + 1)) / (Ceiling(Abs(x) + 1))) 也可以达到同样效果。 - stephenbayer
5
同意,但我认为问题只是在问为什么会有那种反应。 - Brian R. Bondy
5
第一个短语是错误的,x++ 会先返回值,然后再增加 - 然而 ++x 会先增加,然后再返回值。 - Oliver Friedrich

57
在赋值语句 x = x++ 中,你首先提取 x 的旧值以在评估右侧表达式(在此情况下为 'x')时使用;然后,你将 x 加 1。最后,你通过赋值语句,将表达式的结果(10)分配给 x
也许一个等效的代码会更清晰明了:
var tmp = x;
x++;
x = tmp;

这相当于在C#中使用x = x++的代码。


这对我听起来很奇怪...我的印象是当x到达Console.Writeline时,更改应该已经被应用了。 - Jon Limjap
问题在于前缀和后缀的差异。x++和++x行为不同。 - jjnguy
更改已应用但在分配中丢失。如果您使用y = x++,或者如果您执行x = ++x;,则第一个示例也将输出11。 - Vinko Vrsalovic
x被增加,然后被设置回10(这是x++的计算值)- 这并不是延迟到Console.WriteLine之后。 - Jon Skeet
Jon,您在这个主题中真正做着上帝的工作。;-) SO需要一个徽章来奖励这种彻底的评论。我相信您在这个主题中的评论比我的微不足道的回复更有价值。 - Konrad Rudolph
1
只是试图适当地纠正错误信息 :) 不幸的是我现在需要出门。但嘿,如果已经有像你这样正确的答案做得很好,那我会写一个答案的! - Jon Skeet

17

x++ 的行为是将 x 增加 1,但返回增加前的值。这也就是为什么它被称为后缀自增。

因此,x = x++ 简单来说会:

1. 返回 x 原始的值,然后

2. 将 x 增加 1,然后

3. 将 x 的原始值(在步骤 1 中返回)赋值给 x。


1
这是最简洁和精确的解释。其他答案混淆地试图过度解释这样一个简单的机制。 - sɐunıɔןɐqɐp

11
x = 10
x = ++x 

x最终将等于11。


8
x++;

以下是它的功能:

int returnValue = x;
x = x+1;
return returnValue;

正如你所看到的,原值被保存,x被递增,然后原值被返回。

这样做的结果是将值10保存在某个地方,将x设置为11,然后返回10,这会导致x被重新设置为10。请注意,在几个周期内(假设没有编译器优化),x实际上确实变成了11。


按照您的逻辑,++x; 的作用如下: x = x+1; int returnValue = x; return returnValue;即交换第1行和第2行的代码。 - john
1
我认为这是最好的答案,也是最接近实现后缀递增的“幕后”汇编语言。 - john

4
你可以这样想:
int x = 10;

X是一个容器,包含一个值10。

x = x++;

这可以分解为:

1) increment the value contained in x 
    now x contains 11

2) return the value that was contained in x before it was incremented
    that is 10

3) assign that value to x
    now, x contains 10

现在,打印 x 中包含的值。
Console.WriteLine(x);

并且,毫不意外地,它会打印出10。


这似乎是最好的解释。这里的顺序实际上很有意义。 同时,使用JavaScript也很容易实现,在Chrome或Firefox中按F-12打开Web浏览器控制台(记得使用Shift+Enter进行多行输入)。 - john
问题:你能交换1)和2)的顺序吗? - john

1

我知道已经有很多答案和一个被接受的答案,但我仍然想要提出我的意见来展示另一种观点。

我知道这个问题是关于C#的,但我认为对于像后缀运算符这样的东西,它的行为与C并没有不同:

int main(){
    int x = 0;
    while (x<1)
        x = x++;
}

编译器生成的汇编代码(是的,我已经编辑过了以使其更易读)显示:
...
    mov    -8(rbp), 0       ; x = 0
L1:
    cmp    -8(rbp), 1       ; if x >= 1,
    jge    L2               ;     leave the loop
    mov    eax, -8(rbp)     ; t1 = x
    mov    ecx, eax         ; t2 = t1
    add    ecx, 1           ; t2 = t2 + 1
    mov    -8(rbp), ecx     ; x  = t2 (so x = x + 1 !)
    mov    -8(rbp), eax     ; x  = t1 (kidding, it's the original value again)
    jmp    L1
L2:
...

等效地,循环正在执行类似以下的操作:
t = x
x = x + 1
x = t

顺便提一下:开启任何优化都会得到类似于这样的汇编结果:

...
L1:
    jmp    L1
...

它甚至不会存储你告诉它给x的值!

1

你要做的第一件事叫做“后增量”,意思是

    int x = 10;
    x++; //x still is 10
    Console.WriteLine(x); //x is now 11(post increment)

当你执行 x = x++; 这一行时,x 的值仍然是10。如果你需要在这一行让 x 的值变成11,可以写成 ++x(我认为这被称为前缀自增,如果我错了请纠正我)... 或者写成 x++; 然后再写成 x = x++;

问题是,它是基于行还是基于语句的,也就是说,它会在分号之后递增吗?


0

也许我不正确,但是在一个类似的例子中更容易理解结果:

public static void main(String[] args) {
    int x = 10;
    int y = 0;
    y = x + x++;  //1, 2, 3, 4
    x += x;       //5
    System.out.println("x = " + x + "; y = " + y);  //6
}

让我们逐步看一下操作 y = x + x++:

  1. 计算机获取 x 的值并将其加到 x 的值上(10 + 10 = 20)
  2. 计算机将结果放入临时变量中(temp = 20)
  3. 计算机增加 x 的值(10 + 1 = 11)
  4. 计算机将存储在 temp 中的右侧操作的结果分配给变量 y(20)
  5. 计算机获取 x 的值并将其加到 x 的值上(11 + 11 = 22)
  6. 最终结果为:x = 22;y = 20

现在让我们回到我们的例子并执行相同的步骤:

public static void main(String[] args) {
    int x = 10;
    x = x++; //1, 2, 3, 4
    System.out.println(x);  //5
}
  1. 计算机获取变量x的值(10)
  2. 计算机将结果放入临时变量中(temp = 10)
  3. 计算机增加x的值(10 + 1 = 11)
  4. 计算机将存储在temp中的右侧操作的结果分配给变量x(10)
  5. 最终结果为:x = 10

0
将递增运算符放在变量后面意味着递增和赋值在表达式求值之后发生... 因此,原始语句 x = x ++; 翻译为 1. 评估x并将值存储在临时存储器中 ... 现在执行++运算符调用的代码.... (步骤2和3) 2. 增加x的值(在临时存储器中) 3. 将递增后的值分配给x的存储位置 ... 现在,继续执行该行的其余部分,向左有一个=号... 5. 因此,将步骤1中存储的值(未增加的值)分配给=号左侧的表达式... 即x。

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