1. 程式人生 > 實用技巧 >【C++和C#的區別雜談】後自增運運算元的結算時機

【C++和C#的區別雜談】後自增運運算元的結算時機

C++和C#的前自增++n和後自增n++,都是先自增後取值先取值後自增的含義,但在複雜一點的賦值語句中,我發現細節上有很大的差異。

發現這個問題主要是一個無聊的晚上,我想搞清楚後自增是什麼時候結算,自己搗鼓了一下之後我把我自己的測試結果告訴了朋友,結果學java的朋友和我爭論了半天,最後發現同樣的程式碼,大家輸出是不一樣的。之後我又用了C#寫了同樣的測試程式碼,發現輸出和他java是一樣的,這讓我豁然開朗,遂寫下這篇部落格希望分享我的結論,如果有錯誤的話,歡迎評論區指正。

前排提醒:C++和C#我都是用的VS2017,不同編譯環境的C++程式碼輸出結果可能不一樣

先說我的結論:

C++:i++遇到順序點(逗號,分號)之後i才自增,即我一直以來認為的一條語句結束後結算。但++i前自增的值會影響賦值語句所有i的值。

C#:C#不允許使用‘,’逗號運運算元,但也並非到';'分號才結算,賦值語句是從左到右,按序取值,i++在取完i的值之後馬上就自增了,但不影響之前取的i的值,隻影響後續i的取值。

心得:實際開發還是儘量自增單行寫,第一個是可讀性好,第二個是不容易出問題。(應該也就只有筆試會出現以下實驗的程式碼了)

結論看不明白的可以看看我無聊的小實驗:

最簡單常見的版本:

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = n++;//語句a
for (int num : arr)
{
cout << num << ' ';
}
//輸出是0 0 0,語句a結束後n自增到1

上面這段程式碼其實在C#也是一樣的輸出,但如果下面這樣寫,結果就不一樣了。

C++版:

int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n;//語句a
for (int num : arr)
{
cout << num << ' ';
}
//輸出是0 0 0,語句a結束後n自增到1,C++結果和上面的一樣

C#版:

int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n;//語句a
foreach (int num in arr)
{
Console.Write(num + " ");
}
//輸出是1 0 0,現在應該能理解結論了,從左到右,先取n作為索引值,然後自增之後影響到了等號右邊的n的值
//語句a則為arr[0] = 1;

就是這樣的差異開始引申出了問題,也是讓我迷惑的開始,接下來我會通過更復雜的例子和反彙編的指令來解釋和證明我的結論,先列舉C++的部分,之後再C#,感興趣的可以往下看。

C++部分:

//程式碼01
int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
for (int num : arr)
{
cout << num << ' ';
}
//輸出為0 3 0,具體操作流程為語句先結算了++n,使得n自增到1.而出現的兩個n++都在賦值語句結束後結算
//所以編譯結果為arr[1] = 1 + 1 + 1; 然後i自增兩次到3

以下為反彙編的結果:

一開始看到這個我有點懵逼,但是通過比對下面這段程式碼的反彙編指令,就能看出來了。

//程式碼02
int[] arr = { 0, 0, 0 };
int n = 0;
arr[++n] = n + (n++) + ++n;//把索引處的n++改為了++n
foreach (int num in arr)
{
Console.Write(num + " ");
}
//輸出為0 0 6,具體操作流程為語句先結算兩次++n,使得n自增到2.而n++在賦值語句結束後結算
//所以編譯結果為arr[2] = 2 + 2 + 2; 然後i自增到3
//這下應該發現n在這條語句中取值的時候都是同樣的值了

以下為上面程式碼02的反彙編結果:

通過和上面的指令比對可以看出來了,具體差異在00251F3A這段指令,arr[++n]這段程式碼02在累加前多進行了一次對n的自增,然後將自增後的值賦給了n,然後開始進行累加,而上面arr[n++]那段程式碼01是在累加結束之後,在01161F4A那條指令對n++進行自增的結算,所以就算看不懂全部的指令,也應該能通過比對這兩段程式碼看出他們的差異,從而證明瞭C++對於後自增的處理,是在語句結束之後結算

說到語句結束,我之前寫了一篇關於逗號運運算元的部落格,可以結合今天這個結論看看下面這段程式碼的輸出結果。

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = (n++, n + (n++) + ++n);//在逗號左邊添加了n++的語句
for (int num : arr)
{
cout << num << ' ';
}
//輸出為0 0 6
//原因為逗號也屬於一條語句結束的標誌,所以結算了n++,n=1,然後執行新的語句n + (n++) + ++n
//逗號右邊的語句先結算了++n,n=2,所以最後賦值語句為arr[2] = 2 + 2 + 2; 然後n++到3

至此C++的部分結束。

接下來C#的測試結果將顛覆上面的結論,如果不用java或者C#的話,下面的內容可能沒有用(朋友java的輸出結果和我C#一樣,不過也是希望大家自己試試,我自己沒有試過)

C#部分:

//程式碼01 C#版
int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
foreach (int num in arr)
{
Console.Write(num + " ");
}
//輸出為5 0 0 和VC++完全不一樣對吧
//具體原因為C#的賦值語句是從左到右,先取了n的值作為索引,然後馬上對n自增,n=1
//來到等號右邊,第一個n取值為1,第二個n取值為1,然後自增到2,第三個n先自增到3,然後取值為3
//所以最終編譯結果為arr[0] = 1 + 1 + 3; 即5 0 0的原因
//為了節省篇幅,我直接告訴你arr[++n] = n + (n++) + ++n的結果為0 5 0,如果你上面看懂了這個應該也懂

貼一下C#這段程式碼的反彙編結果:

可以看到第一行是取n的值,然後把n作為索引值,之後inc指令對n自增1。證明瞭上面的結論。

即:C#的賦值語句為從左到右,按序取值,n++在取完n的值之後馬上自增,但不影響之前取的值,隻影響後續n的取值

小結:

所以++n 先自增後取值n++ 先取值後自增是能直接套在C#上的,而對於VC++來說,後自增的結算是非常“緩慢”的,到語句結束才結算。

感謝您的觀看。