今早看到了《你真的了解a ^= (b ^= (a ^= b))吗?》一文,文章中提到了一个有趣的表达式:a ^= (b ^= (a ^= b)); 很简单的一个程序:
1 int a = 2; 2 int b = 9; 3 a ^= (b ^= (a ^= b)); 4 Console.WriteLine("{0}, {1}", a.ToString(), b.ToString());
最后的运算结果不是预期的9,2,而是0,2。同时,还有1个表达式:
1 int i=0; 2 int j=(i++)+(i++); 3 j=?
j的值是多少?2么?不是,正确的结果是1。 为啥会有这样的结果,难道是编译器的bug么?其实不然,编写编译器的人可不是数学白痴,是我们对计算机的理解有误。 C#中,表达式的计算遵循一个规律:从左到右依次计算。 例如,Z= X operation Y,计算机依次计算X,Y的值,然后OperationX和Y,最后赋值给Z。 同理,Z = X operation1 ( X operation2 Y),计算机则把(X operation2 Y)当做一个变量Temp。此时Z = X operation1 Temp。然后计算机再计算Temp表达式,并压栈,计算完Temp表达式后,将结果出栈。这是个递归过程。注意:这个过程中,X与Temp是两个并列的表达式,如果Temp中有对X进行再操作,并不会影响X的结果。因此表达式规律是从左到右依次计算,X在左,已经计算完毕。如果是Z = Temp operation1 X,那么Temp中对X的操作将会影响到X本身。 OK,理论分析完毕,让我们来实践一下为什么上面2个表达式的结果令人诧异。 对于第一个表达式:a ^= (b ^= (a ^= b))。先将运算符分解:a = a ^ (b = b ^ (a = a ^ b)); 这样看起来更清晰。 1. 计算机分解表达式为 a = a ^ Temp1。计算左边的a,a本身是一个变量,因此a用2来代替。接下来计算右边的Temp1表达式,发现其中比较复杂,无法直接用值来代替,因此开始对Temp1堆栈计算。 2. Temp1 是 b = b ^ (a = a ^ b)。计算左边,b是变量,可以直接赋值为9。右边是表达式,用Temp2代替。 3. Temp2 是 a = a ^ b。计算左边,a是变量,替换为2; 右边,b也是变量,替换为9。并计算出Temp2=2 ^ 9 = 11。 4. 回归计算Temp 1 = 9 ^ Temp2 = 9 ^ 11 = 2,也就是b = 2。 5. 回归计算a = 2 ^ Temp1 = 2 ^ 2 = 0。也就是a = 0。 OK,这样0, 2结果就出现了。 对于第二个表达式: j = (i++) + (i++);。 1. 先计算左边的表达式,i++,因为是后缀,所以(i++)表达式返回i,此时i = 0, 意味着返回0。 2. 执行++运算符,0++就是1,因此i = 1; 3. 计算右边的表达式(i++),也是后缀,因此返回i ,此时 i = 1,意味着返回1; 4. 执行++运算符,1++就是2,因此i = 2; 5 赋值给j. j = 0 + 1 = 1。 综上,其实不是什么编译器的bug,而是计算机的做法永远是死脑筋的。 还有,实际项目中千万不要出现这样的表达式,宁可多写几行,也不要堆在一行,我们一定要遵循《代码规范》。这也是为了提高代码的可阅读性和可维护性。 最后,给大家出个题目: int i = 0; int j = (i--) + ((i++) + (++i)); 计算结果i 和 j 分别是多少?
|
请发表评论