在研究兩個整數互換的方法時(詳細看這里),發現了一個有趣的現象。
a ^= b ^= a ^= b; ≠ a ^= b;b ^= a;a ^= b;
有興趣的童鞋可以看看下面代碼的結果是什么:
int a = 10; int b = 5; a ^= b ^= a ^= b; Console.WriteLine("{0} {1}", a, b);
一直以為a=b=c就是按照先后順序執行b=c,a=b。照上面的執行結果來看,還不一定。
到底執行的順序怎么樣,要看看反編譯的代碼才知道。
先看看a=b=c=30都在做什么:
IL_0008: ldc.i4.s 30 //推送30到棧頂端 IL_000a: dup //復制一個30到棧頂端 IL_000b: stloc.2 //提取頂端的30賦值給索引為2的變量,也就是c IL_000c: dup //復制一個棧頂端的30 IL_000d: stloc.1 //提取頂端的30賦值給索引為1的變量,也就是b IL_000e: stloc.0 //提取頂端的30賦值給索引為0的變量,也就是a
這樣來看,a=b=c=30可以理解成c=30,b=30,a=30.
再看看a ^= b ^= a ^= b在做什么(初始化這里a=10,b=5):
IL_0006: ldloc.0 //推送索引為0的變量值到棧頂端,也就是10 IL_0007: ldloc.1 //推送5 IL_0008: ldloc.0 //推送10 IL_0009: ldloc.1 //推送5 IL_000a: xor //提取10和5,做異或運算,將結果15推送到棧頂端 IL_000b: dup //復制15 IL_000c: stloc.0 //提取15賦值給a IL_000d: xor //取頂端的兩個值15和5,做異或運算,將結果10推送到棧頂端 IL_000e: dup //復制10 IL_000f: stloc.1 //提取10賦值給b IL_0010: xor //取頂端的兩個值10和10做異或運算,結果0放到棧頂端 IL_0011: stloc.0 //提取0賦值給a
做圖解如下(作圖水平不高,但應該勉強能看懂):
整個過程,用代碼還原就是
a1=a0^b0=15;
然后 b1=b0^a1=10;
再然后 a2=a0^b1=10^10=0。
前面兩步的環節基本上是我們想要的,但是第三步a2=a0^b1卻脫離了我們的原意,這里采用了a0做異或而不是a1,所以對應的結果也就出現了偏差。
因此,這一行代碼執行下來,a=0,b=10,結果顯然並不是我們想要的。
接下來,再看看a ^= b;b ^= a;a ^= b;在干嘛:
IL_0006: ldloc.0 //推送10 IL_0007: ldloc.1 //推送5 IL_0008: xor //提取10和5做異或運算,得15,推送至棧頂 IL_0009: stloc.0 //將15賦值給a IL_000a: ldloc.1 //推送b的值5 IL_000b: ldloc.0 //推送a的新值15 IL_000c: xor //提取15和5做異或運算,得10,推送至棧頂 IL_000d: stloc.1 //將10賦值給b IL_000e: ldloc.0 //推送a的值15 IL_000f: ldloc.1 //推送b的新值10 IL_0010: xor //提取15和10做異或運算,得5,推送至棧頂 IL_0011: stloc.0 //將5賦值給a
整個過程比較清晰。中規中矩的異或計算然后賦值,再異或,再賦值,再異或賦值。最后a=5,b=10,結果和我們想的一樣。
C#里面可以寫連等句式,但是其中的邏輯一定要小心,尤其是連等過程中有變量賦值的,更要注意。平時使用的時候,建議不要為了省那兩行的代碼量而用連等語句拼湊,因為運算的結果可能和我們想要的不一樣,而導致程序bug,得不償失。