【问题标题】:Post-increment within a self-assignment自分配中的后增量
【发布时间】:2016-02-20 10:05:52
【问题描述】:

我了解i++ and ++i 之间的区别,但我不太确定为什么会得到以下结果:

static void Main(string[] args)
{
    int c = 42;
    c = c++;
    Console.WriteLine(c);   //Output: 42
}

在上面的代码中,因为这是将变量分配给自身,然后增加值,我希望结果是43。但是,它返回42。我在使用c = c--; 时也得到了相同的结果。

我意识到我可以简单地使用 c++; 并完成它,但我更好奇它为什么会这样。谁能解释这里发生了什么?

【问题讨论】:

  • 首先声明“c”,它等于42。然后在“c++”上修改它的值,因此,它将首先执行赋值(“c = c”),然后增加值右边的“c”,但问题是这是一个值类型,而不是引用类型,所以增加的值与最终“c”中包含的值不同。
  • 我花了一点时间才意识到我们正在使用一种表达定义行为的语言。任何使用 C 或 C++ 工作的人,不要试图将这个问题的答案应用于这些语言!
  • 实际上,即使它是定义的行为,也不要在任何语言中使用该结构。因为它在所有语言中都令人困惑。
  • "我意识到我可以简单地使用 c++" 但是......但它在 c++ 中是未定义的行为!

标签: c# post-increment pre-increment


【解决方案1】:

文档说后缀状态:

运算的结果是操作数之前的值 增加了。

这意味着当你这样做时:

c = c++;

您实际上是在将42 重新分配给c,这就是您看到控制台打印42 的原因。但如果你这样做:

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

你会看到它输出43。

如果您查看compiler generates(在调试模式下),您会看到:

private static void Main(string[] args)
{
    int num = 42;
    int num2 = num;
    num = num2 + 1;
    num = num2;
    Console.WriteLine(num);
}

这会更清楚地显示覆盖。如果您查看发布模式,您会看到编译器优化了整个调用:

private static void Main(string[] args)
{
    Console.WriteLine(42);
}

【讨论】:

  • 难道不应该在表达式末尾增加 c 吗?
  • @Siyual 它首先评估右手边,增加c,然后立即将42 赋值给它,覆盖增量操作。
  • @Siyual,该行应该读作c = (c++),而不是(c = c)++(这甚至不是合法的C#,但只有在将c = c简化为c时才在概念上有意义) .
【解决方案2】:

根据the MSDN page on C# operators,赋值运算符(=) 的优先级低于任何主运算符,例如++x 或x++。 这意味着在

c = c++;

首先评估右侧。表达式c++ 将c 递增到43,然后返回原始值42 作为结果,用于赋值。

作为您链接到状态的文档,

【第二种形式是】后缀自增操作。运算的结果是操作数在递增之前的值。

换句话说,你的代码相当于

// Evaluate the right hand side:
int incrementResult = c;   // Store the original value, int incrementResult = 42
c = c + 1;                 // Increment c, i.e. c = 43

// Perform the assignment:
c = incrementResult;       // Assign c to the "result of the operation", i.e. c = 42

将此与前缀形式进行比较

c = ++c;

评估为

// Evaluate the right hand side:
c = c + 1;                 // Increment c, i.e. c = 43
int incrementResult = c;   // Store the new value, i.e. int incrementResult = 43

// Perform the assignment:
c = incrementResult;       // Assign c to the "result of the operation", i.e. c = 43

【讨论】:

  • 原因是事情发生的顺序。递增是表达式的一部分,因此表达式c++ 会将 c 递增到 43 并返回 42,但这两种情况都发生在赋值回 c 之前。很可能(除了任何编译时优化),c 首先变为 43,然后将其覆盖以使其再次变为 42。您的代码示例很好地解释了这一点,但重点没有。强调的位只是说表达式将返回 42 而不是 43.OP 知道这一点,但希望在赋值之后发生递增。
  • 这真的帮助我理解了为什么分别调用后/前增量运算符。在后增量中,结果首先存储 - 然后递增,而在预增量中,它首先递增然后存储。谢谢。
【解决方案3】:

...因为这是将变量分配给自身,然后递增值...

不,这不是它的作用。

post-increment 运算符增加变量并返回旧值。 pre-increment 运算符递增变量并返回新值。

所以你的c++ 将 c 增加到 43,但返回 42,然后再次分配给 c。

【讨论】:

    【解决方案4】:

    让我们看一下中间语言代码:

    IL_0000:  nop
    IL_0001:  ldc.i4.s    2A
    IL_0003:  stloc.0     // c
    IL_0004:  ldloc.0     // c
    

    这会将常量整数42 加载到堆栈中,然后将其存储到变量c 中,然后立即将其再次加载到堆栈中。

    IL_0005:  stloc.1
    IL_0006:  ldloc.1
    

    这会将值复制到另一个寄存器中,然后再次加载它。

    IL_0007:  ldc.i4.1
    IL_0008:  add
    

    这会将常量 1 添加到加载的值中

    IL_0009:  stloc.0     // c
    

    ... 并将结果 (43) 存储到变量 c。

    IL_000A:  ldloc.1
    IL_000B:  stloc.0     // c
    

    然后加载来自另一个寄存器的值(仍然是 42!)并存储到变量 c。

    IL_000C:  ldloc.0     // c
    IL_000D:  call        System.Console.WriteLine
    IL_0012:  nop
    IL_0013:  ret
    

    然后从变量中加载值 (42),并打印出来。


    因此,您可以从中看到,虽然c++ 在返回结果之后 将变量递增一,但在将值赋给变量之前,递增仍然发生。所以顺序更像这样:

    1. 从c获取值
    2. 后增量c
    3. 将先前读取的值分配给c

    这应该可以解释为什么你会得到这个结果:)


    再添加一个示例,因为在已删除的评论中提到了这一点:

    c = c++ + c;
    

    这非常相似:再次假设初始值为 2,则首先计算加法的左侧。因此从变量 (2) 中读取值,然后将 c 递增(c 变为 3)。然后评估加法的右侧。读取c 的值(现在为 3)。然后进行加法 (2 + 3) 并将结果 (5) 分配给变量。


    从中得出的结论是,您应该避免在普通表达式中混合递增和递减操作。虽然行为定义非常明确并且绝对有意义(如上所示),但有时仍然难以理解它。尤其是当您将某些内容分配给您在表达式中递增的同一变量时,这很快就会变得混乱。因此,请帮自己和他人一个忙,并避免在他们不完全靠自己时进行递增/递减操作:)

    【讨论】:

    • 啊!现在这很有意义。所以本质上,它将变量设置为等于自身,然后增加原始变量的副本,从而迷路了?
    • 它确实增加了实变量,但它在将(旧)值分配给赋值语句中的变量之前这样做。所以后增量的效果会立即被赋值覆盖。
    • 什么时候将递增的值赋给变量c?您可以使用问题中提出的相同示例并扩展代码吗?@poke
    • @NileshJain 我不确定你的意思。我的回答几乎完全是关于c = c++ 的例子。递增的值永远不会分配给变量,因为分配是使用旧值应用的,但之后它已经递增了。
    • @poke 我想知道的是未分配给变量的更新值会发生什么。它丢失了吗?这可能是一个愚蠢的问题,但仍然想知道。
    【解决方案5】:

    赋值右侧的表达式被完全计算,然后执行赋值。

       c = c++;
    

    和

    一样
       // Right hand side is calculated first.
       _tmp = c;
       c = c + 1;
    
       // Then the assignment is performed
       c = _tmp;
    

    【讨论】:

      【解决方案6】:

      我想我明白最初的提问者在想什么。他们在想(我认为)后增量意味着在评估整个表达式之后增加变量,例如那个

      x = a[i++] + a[j++];   // (0)
      

      和

      一样
      { x = a[i] + a[j] ; i += 1 ; j += 1; }   // (1)
      

      (当然这些是等价的)和那个

      c = c++;  // (2)
      

      意思

      { c = c ; c +=1 ; } // (3)
      

      还有那个

      x = a[i++] + a[i++];  // (4)
      

      意思

      { x = a[i] + a[i] ; i += 2 ; } // (5)
      

      但事实并非如此。 v++ 表示立即增加v,但使用旧值作为表达式的值。所以在 (4) 的情况下,一个实际上等价的语句是

      {int t0 = a[i] ; i += 1 ; int t1 = a[i] ; i += 1 ; x = t0 + t1 ; } // (6)
      

      正如其他人所指出的,像 (2) 和 (4) 这样的语句在 C#(和 Java)中定义良好,但在 C 和 C++ 中定义不明确。

      在 C 和 C++ 中,如 (2) 和 (4) 更改变量并以其他方式使用它的表达式通常是未定义的,这意味着欢迎编译器(就语言法律而言)将它们翻译成任何方式,例如将钱从您的银行账户转移到编译器作者的账户。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-09
        • 2012-07-08
        • 2011-11-23
        • 2013-06-21
        • 2016-02-20
        • 1970-01-01
        • 1970-01-01
        • 2012-11-13
        相关资源
        最近更新 更多