自我分配中的后递增

本文关键字:分配 自我 | 更新日期: 2023-09-27 18:22:40

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

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

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

我意识到我可以简单地使用c++;并完成它,但我更好奇为什么它的行为方式如此。 谁能解释一下这里发生了什么?

自我分配中的后递增

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

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,但在将值分配给变量之前,增量仍然会发生。所以顺序更像是这样的:

  1. c中获取价值
  2. 递增后c
  3. 将以前的读取值分配给c

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


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

c = c++ + c;

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


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

根据 MSDN 关于 C# 运算符的页面,赋值运算符 (=( 的优先级低于任何主运算符,例如 ++xx++ 。这意味着在生产线上

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

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

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

你会看到它输出43 .

如果查看编译器生成的内容(在调试模式下(,您将看到:

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++将 c 增加到 43,但返回 42,然后再次分配给 c。

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

   c = c++;

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

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

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( 这样改变变量并以其他方式使用它的表达式通常是未定义的,这意味着欢迎编译器(就语言法而言(以任何方式翻译它们,例如将钱从您的银行账户转移到编译器编写者的账户。