深入理解C#中的自增和自减运算符

在编程实践中,当人们开始变得富有创造性时,问题也随之产生。基本上,前缀(如++i或--i)表示在变量使用前增加或减少其值,因此变量立即具有新值:

C# i = 10; x = ++i + 5;

可以这样理解:

C# i = 10; i = i + 1; x = i + 5;

对于--版本也类似:

C# i = 10; x = --i + 5;

可以这样理解:

C# i = 10; i = i - 1; x = i + 5;

后缀版本(i++或i--)执行相同的操作,但在变量使用后进行:

C# i = 10; x = i++ + 5;

可以这样理解:

C# i = 10; x = i + 5; i = i + 1;

同样地:

C# i = 10; x = i-- + 5;

可以这样理解:

C# i = 10; x = i + 5; i = i - 1;

就这么简单吗?是的,如果以简单的方式使用它 - 例如作为数组索引器:

C# x = myArray[index++];

或者作为循环增量:

C# for(i = 0; i < 10; i++) { WriteLine(myArray[i]); }

但之后,就进入了混乱和痛苦的世界!例如,这将i的值设置为什么:

C# int i, j; i = 10; for(j = 0; j < 5; j++) { i = i++; }

答案是:不变。 i保持在10。为什么?想象一下如果展开它:

C# int i = 10; i = i++;

如果在C#中写这个,那么IL看起来像这样:

MSIL .line 14,14 : 13,24 '' IL_0001: ldc.i4.s 10 Push a constant value '10' to the stack, 4 byte integer, IL_0003: stloc.0 Pop the top of the stack into local number 0 .line 15,15 : 13,21 '' IL_0004: ldloc.0 Push local number 0 to the stack IL_0005: dup Duplicate the top of the stack IL_0006: ldc.i4.1 Push a constant value '1', 4 byte integer IL_0007: add Pop the top two stack items, add them, and push the result IL_0008: stloc.0 Store the top of the stack in local number 0 IL_0009: stloc.0 Store the top of the stack in local number 0

什么?在扩展的C#代码中,这变成了:

C# int i = 10; int j = i; int k = j; k = k + 1; i = k; i = j;

这相当奇怪...因为可以在不影响结果的情况下丢弃中间的三行。它不应该这样做,对吧?是的。是的,它应该: i++是一个后缀操作:它说,“记住i的值,然后将i增加1,然后返回记住的值”。所以告诉编译器做的是忽略增量的结果,通过用开始的值覆盖它。有趣的是,如果在Visual Studio C++编译器中尝试...它不会...因为它以不同的方式处理!所以,现在有第一个原因为什么当开始使用增量和减量运算符时要谨慎:它实际上是一个副作用,一整行代码插入到行中,如果不仔细思考,它就不会做认为的事情。

C# int i1 = i; i1 = i1 + 1; i = i

问题出现在开始在同一行混合操作时:

C# i = 10; x = ++i + i++;

问题是语言规范没有定义前缀和后缀操作应该何时发生,甚至没有定义操作符的顺序,考虑到操作符优先级规则。

优先级和求值顺序:

MSDN写道: 只有顺序评估(,)、逻辑与(&&)、逻辑或(||)、条件表达式(?:)和函数调用运算符构成序列点,因此保证其操作数的特定求值顺序。

所以编译器作者可以评估这个表达式:

C# a = b * c + d * e;

通过首先计算b * c,然后计算d * e,或者首先计算d * e然后计算b * c,因为它对计算结果没有影响。

除非开始玩前缀和后缀增量运算符 - 然后结果会改变。

这意味着它完全是实现特定的,将得到什么结果:i的值应该总是相同的:

12

但x的值可能会因编译器(在一定程度上取决于目标处理器 - ARM例如有内置的前缀和后缀增量和减量到其“机器代码”LOAD操作,所以一个高效的编译器很可能会直接使用它们)而异,应该执行为:

C# i = 10; i = i + 1; x = i + i; i = i + 1;

结果为22或为:

C# i = 10; i1 = i; i = i + 1; x = i1 + i; i = i + 1;

结果为21或为:

C# i = 10; i1 = i; i = i + 1; x = i + i1; i = i + 1;

这也给出了21,通过不同的路线。并记住编译器不必以从左到右的顺序评估“+”的两个操作数,所以它甚至可能给出一些非常奇怪和意想不到的结果!像23...

更糟的是:求值顺序可能会在编译器版本之间改变(因为它没有定义)或者作为优化的结果:所以调试中有效的东西在发布中失败了!

所以避免组合它们:在“简单表达式”中使用它们,例如每次循环时递增数组索引,但不要花哨,否则代码可能会以有趣的方式失败...

有趣的点

这篇文章最初是作为一个问题的答案写的 - 但在需要它的时候从未找到过它,并且觉得它需要被保存以供后人参考。因此它被更新了一点,扩展了,发布为一篇文章,并且得到了更高的可见性。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485