Post-自赋值内自增
Post-increment within a self-assignment
我理解 i++ and ++i
之间的区别,但我不太清楚为什么会得到以下结果:
static void Main(string[] args)
{
int c = 42;
c = c++;
Console.WriteLine(c); //Output: 42
}
在上面的代码中,因为这是将变量分配给自身然后递增值,所以我希望结果为 43
。但是,它正在返回 42
。我在使用 c = c--;
时也得到相同的结果。
我意识到我可以简单地使用 c++;
并完成它,但我更好奇为什么它会以现在的方式运行。谁能解释一下这里发生了什么?
文档说后缀状态:
The result of the operation is the value of the operand before it has
been incremented.
这意味着当您这样做时:
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);
}
哪个更清楚地显示了覆盖。如果查看 Release 模式,您会看到编译器优化了对以下内容的整个调用:
private static void Main(string[] args)
{
Console.WriteLine(42);
}
根据 the MSDN page on C# operators,赋值运算符 (=
) 的优先级低于任何主要运算符,例如 ++x
或 x++
。
这意味着在行
c = c++;
首先评估右侧。表达式 c++
将 c
递增到 43,然后 returns 原始值 42
作为结果,用于赋值。
作为您链接到状态的文档,
[The second form is a] postfix increment operation. The result of the operation is the value of the operand before it has been incremented.
换句话说,你的代码相当于
// 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
...as this is assigning the variable to itself and then incrementing the value...
不,这不是它的作用。
post-increment 运算符递增变量,returns old值。 pre-increment 运算符递增变量和 returns new value.
所以你的 c++
将 c 增加到 43,但是 returns 42 然后又被分配给 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 在 结果返回之后,递增仍然发生在将值分配给变量。所以序列更像这样:
- 从
c
获取值
- Post-增量
c
- 将先前读取的值分配给
c
这应该可以解释为什么你会得到这个结果:)
再添加一个例子,因为在一条评论中提到了这一点,该评论已被删除:
c = c++ + c;
这非常相似:再次假设初始值为 2,首先计算加法的左侧。因此从变量 (2) 中读取值,然后 c
递增(c
变为 3)。然后计算加法的右侧。 c
的值被读取(现在是 3)。然后进行加法 (2 + 3) 并将结果 (5) 赋值给变量。
由此得出的结论是,您应该避免在普通表达式中混合递增和递减运算。虽然该行为定义明确且绝对有意义(如上所示),但有时您仍然难以理解它。特别是当您将某些内容分配给您在表达式中递增的同一个变量时,这很快就会变得混乱。所以帮自己和别人一个忙,避免 increment/decrement 操作,当他们不完全靠自己时:)
赋值右边的表达式被完全求值,然后执行赋值。
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) 这样更改变量并以其他方式使用它的表达式通常是未定义的,这意味着欢迎编译器(就语言法而言)进行翻译他们以任何方式,例如将钱从您的银行账户转入编译器作者的账户。
我理解 i++ and ++i
之间的区别,但我不太清楚为什么会得到以下结果:
static void Main(string[] args)
{
int c = 42;
c = c++;
Console.WriteLine(c); //Output: 42
}
在上面的代码中,因为这是将变量分配给自身然后递增值,所以我希望结果为 43
。但是,它正在返回 42
。我在使用 c = c--;
时也得到相同的结果。
我意识到我可以简单地使用 c++;
并完成它,但我更好奇为什么它会以现在的方式运行。谁能解释一下这里发生了什么?
文档说后缀状态:
The result of the operation is the value of the operand before it has been incremented.
这意味着当您这样做时:
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);
}
哪个更清楚地显示了覆盖。如果查看 Release 模式,您会看到编译器优化了对以下内容的整个调用:
private static void Main(string[] args)
{
Console.WriteLine(42);
}
根据 the MSDN page on C# operators,赋值运算符 (=
) 的优先级低于任何主要运算符,例如 ++x
或 x++
。
这意味着在行
c = c++;
首先评估右侧。表达式 c++
将 c
递增到 43,然后 returns 原始值 42
作为结果,用于赋值。
作为您链接到状态的文档,
[The second form is a] postfix increment operation. The result of the operation is the value of the operand before it has been incremented.
换句话说,你的代码相当于
// 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
...as this is assigning the variable to itself and then incrementing the value...
不,这不是它的作用。
post-increment 运算符递增变量,returns old值。 pre-increment 运算符递增变量和 returns new value.
所以你的 c++
将 c 增加到 43,但是 returns 42 然后又被分配给 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 在 结果返回之后,递增仍然发生在将值分配给变量。所以序列更像这样:
- 从
c
获取值
- Post-增量
c
- 将先前读取的值分配给
c
这应该可以解释为什么你会得到这个结果:)
再添加一个例子,因为在一条评论中提到了这一点,该评论已被删除:
c = c++ + c;
这非常相似:再次假设初始值为 2,首先计算加法的左侧。因此从变量 (2) 中读取值,然后 c
递增(c
变为 3)。然后计算加法的右侧。 c
的值被读取(现在是 3)。然后进行加法 (2 + 3) 并将结果 (5) 赋值给变量。
由此得出的结论是,您应该避免在普通表达式中混合递增和递减运算。虽然该行为定义明确且绝对有意义(如上所示),但有时您仍然难以理解它。特别是当您将某些内容分配给您在表达式中递增的同一个变量时,这很快就会变得混乱。所以帮自己和别人一个忙,避免 increment/decrement 操作,当他们不完全靠自己时:)
赋值右边的表达式被完全求值,然后执行赋值。
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) 这样更改变量并以其他方式使用它的表达式通常是未定义的,这意味着欢迎编译器(就语言法而言)进行翻译他们以任何方式,例如将钱从您的银行账户转入编译器作者的账户。