再议(i=-i++)的真面目~~

看到大家拍了那么多的砖头,我只有重新说明一下,没有人在实际的项目中写这样的代码,由于我的C语言学的真的不是很好(只能用来参加考试),所以就想重新读一下C语言的书,算是重新的学习一下吧。在看谭浩强的《C程序设计(第二版)》的第57页最后一段时,看到谭浩强老师用-i++这个例子来讲解++和--的结合方向是“自右向左”时,偶然间想到如果改为i=-i++会是什么情况哪?于是我就在C#中试了一下,结果得到了-3,觉得很怪,于是在TurboC中试了一下,是-2,所以我就开始研究这个问题了。
在这里我还想问一下那些拍砖头的人,你们或许一辈子都不会写着这样的代码,但是你们真的就明白这个语句的执行过程麽?至少在这个过程中我学到了很多的东西,例如以前忽略的优先级,没有想到它和C语言有很大的不同,特别是对待++和--时,居然分清楚了前缀和后缀的优先级差别;还有也清楚的明白了,实际上在内存中计算机是如何处理++的?如果你们真的很清楚的话,哪真的希望你们以后多帮帮我,看来我还有很多的东西要学习。

上一篇《(i=-i++)你真的了解这个表达式么?》 我讲述我对i=-i++的迷惑,得到了很多朋友的讲解,逐渐开始明白了,但是说实在的上篇文章解释的还是不很清楚的,而且很多是错误的。在这里我就重新讲解一下。
下面还是那段代码:
 1 using System;
 2  
 3 namespace ConsoleApplication3
 4 {
 5     class Program
 6 {
 7         static void Main(string[] args)
 8         {
 9             int i = 3;
10             i = -i++;
11             Console.WriteLine(i);
12         }
13     }
14 }
当然下面还有他的IL代码:
 1 .method private hidebysig static void  Main(string[] args) cil managed
 
2 {
 
3   .entrypoint
 
4   // 代码大小       18 (0x12)
 5   .maxstack  3
 
6   .locals init ([0] int32 i)
 
7   IL_0000:  nop
 
8   IL_0001:  ldc.i4.3
 
9   IL_0002:  stloc.0
10   IL_0003:  ldloc.0
11   IL_0004:  dup
12   IL_0005:  ldc.i4.1
13   IL_0006:  add
14   IL_0007:  stloc.0
15   IL_0008:  neg
16   IL_0009:  stloc.0
17   IL_000a:  ldloc.0
18   IL_000b:  call       void [mscorlib]System.Console::WriteLine(int32)
19   IL_0010:  nop
20   IL_0011:  ret
21 } // end of method Program::Main
22 
然后我们再来看另外一段代码:
 1 using System;
 2 
 3 namespace ConsoleApplication2
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             int i = 3;
10             i++;
11             Console.WriteLine(i);
12         }
13     }
14 }
15 
再来看一下他的IL代码:
 1 .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint
 4   // 代码大小       15 (0xf)
 5   .maxstack  2
 6   .locals init ([0] int32 i)
 7   IL_0000:  nop
 8   IL_0001:  ldc.i4.3
 9   IL_0002:  stloc.0
10   IL_0003:  ldloc.0
11   IL_0004:  ldc.i4.1
12   IL_0005:  add
13   IL_0006:  stloc.0
14   IL_0007:  ldloc.0
15   IL_0008:  call       void [mscorlib]System.Console::WriteLine(int32)
16   IL_000d:  nop
17   IL_000e:  ret
18 // end of method Program::Main
19 
20 
呵呵,在这里我就不分析IL代码的意思了,要是不太明白的话,可以看我的第一篇文章《(i=-i++)你真的了解这个表达式么?》,在哪里有详细的IL代码解释,我们这里只是要注意一下,其中的两条指令“dup”,以及“add”,很是明显,这里的add是在执行++操作,但是我们可以看一下,add是什么时候执行的。
哈哈,看到了吧,是最先执行了,也就是说语句“i=-i++”是先计算出i++,然后再取负的,很是奇怪,因为我们总是再讲i++和++i的区别时就知道i++是先让i参与运算,然后再做++,但是IL代码明显表示出先是做的++,再进行其他运算的。
这里真的很是迷惑啊,其实事情是这样的。
我们可以查C#运算符的优先级次序,发现“(后缀)++”的优先级最高,下来是“++(前缀)”以及“-(一元)”。
显然这和C语言是有很大不同的,在C语言中,“++”的优先级是不分前后缀的,他们的优先级都是一样的,和“-”的优先级也是一样的。
但是C#却把前缀++和后缀++分开来处理,既是:前缀++和-的优先级一样,而后缀++比它们两个的优先级都要高。

因此在我们的i=-i++的式子中先计算add就是合理的了,但是C#是如何做到先计算后缀++,而又不影响其真实的计算过程哪?我们再来比较一下上面哪两段IL代码,可以发现第一个比第二个在add的前面多了一个dup指令,它是做什么的哪?它是复制站顶元素,即做了一个i的拷贝。那么后面一个为什么没有这种拷贝哪,我想是因为,在后一个代码中i不需要参加其他的运算,也就是说i++中的i一旦参与了其他的运算,就会通过dup指令产生一个拷贝。
在这里我们进一步分析它们的情况吧。i产生拷贝后,由于i++的优先级最高,所以必须先执行++操作,因此拷贝出来的i就++,得到结果是4,此时4出站,i就变成了4。接线来,站顶元素就是原来的那个i了,它还是i在没有参加++操作前的值(既是3),所以这个站顶的i就参与到其他的操作去了,最后预算的结果是-3,这时就需要做“=”的赋值操作了,如果这时在=号的左边是别的变量(如t),则站顶元素出站赋值给变量t,于是本条C#语句执行完毕,t得到了右边表达式的正确值,i也进行了++操作,一切都很合理。但是我们这里“=”的左端是i,不是其他的变量,所以站顶元素(-3)出站,参与“=”的赋值操作,而这时我们i是4,因此-3会覆盖掉4,也就是说++操作的结果(4)被覆盖掉了,变成了等号右边表达式的值-3。
因此我们迷惑的假想就出来了,i只取了负,并没有做++操作。因此最后打印的结果是-3.

最后我总结了一句话,不知道对不对:千万不要在“=”的左边出现某一变量时,右边又存在其++(后缀)的操作,否则++操作将会被覆盖掉。

posted on 2007-07-29 16:31  啊不才  阅读(3025)  评论(36编辑  收藏  举报

导航