再议(i=-i++)的真面目~~
看到大家拍了那么多的砖头,我只有重新说明一下,没有人在实际的项目中写这样的代码,由于我的C语言学的真的不是很好(只能用来参加考试),所以就想重新读一下C语言的书,算是重新的学习一下吧。在看谭浩强的《C程序设计(第二版)》的第57页最后一段时,看到谭浩强老师用-i++这个例子来讲解++和--的结合方向是“自右向左”时,偶然间想到如果改为i=-i++会是什么情况哪?于是我就在C#中试了一下,结果得到了-3,觉得很怪,于是在TurboC中试了一下,是-2,所以我就开始研究这个问题了。
在这里我还想问一下那些拍砖头的人,你们或许一辈子都不会写着这样的代码,但是你们真的就明白这个语句的执行过程麽?至少在这个过程中我学到了很多的东西,例如以前忽略的优先级,没有想到它和C语言有很大的不同,特别是对待++和--时,居然分清楚了前缀和后缀的优先级差别;还有也清楚的明白了,实际上在内存中计算机是如何处理++的?如果你们真的很清楚的话,哪真的希望你们以后多帮帮我,看来我还有很多的东西要学习。
上一篇《(i=-i++)你真的了解这个表达式么?》 我讲述我对i=-i++的迷惑,得到了很多朋友的讲解,逐渐开始明白了,但是说实在的上篇文章解释的还是不很清楚的,而且很多是错误的。在这里我就重新讲解一下。
下面还是那段代码:
哈哈,看到了吧,是最先执行了,也就是说语句“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.
最后我总结了一句话,不知道对不对:千万不要在“=”的左边出现某一变量时,右边又存在其++(后缀)的操作,否则++操作将会被覆盖掉。
在这里我还想问一下那些拍砖头的人,你们或许一辈子都不会写着这样的代码,但是你们真的就明白这个语句的执行过程麽?至少在这个过程中我学到了很多的东西,例如以前忽略的优先级,没有想到它和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代码: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 }
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
然后我们再来看另外一段代码: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代码: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
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是什么时候执行的。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
哈哈,看到了吧,是最先执行了,也就是说语句“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.
最后我总结了一句话,不知道对不对:千万不要在“=”的左边出现某一变量时,右边又存在其++(后缀)的操作,否则++操作将会被覆盖掉。