先上結論:
- x++和x+1不是一回事!
- 這個玩意沒有優先級!!!
- 這種寫法,是C標準嚴格禁止的。和伸手摸電門一樣,寫這種代碼屬于做死。
- 關于這種寫法的結果的一切討論,都是無意義的。
一段代碼引出的糾結
先看如下一段代碼,猜猜看,輸出的是什么?
int a;
a = 0;
a = a++;
System.out.println("a = " + a);
最終輸出的是:a = 0
為什么是0而不是1?
查看一下字節碼,會發現++操作與+1不一樣。
我們平時總是說前綴表達式優先級高,后綴表達式優先級低,其實并不是,這里根本不存在優先級問題,而一定要說的話,++運算符的優先級高于賦值運算符!
在程序運行a++時,a的值首先是賦值給一個拷貝或者說臨時變量(按值傳遞,底層實現),即temp = a(即temp = a = 0),然后a執行自增運算(運算后a的值為1),最后將這個拷貝(此時拷貝的值為0)作為(a++)整體的值賦值給a(賦值后a的值有重新從1變為0),所以最終的a的值輸出為0。
即a = a++;語句等價于:
a=(temp=a,a+=1,temp);
或者我們干脆這么理解:
int b = a++;
a = b;
那么x = x++;算什么?
這在c標準里,這種操作稱為未定義行為。
C99中:
J.2 Unde?ned behavior
Between two sequence an object is modi?ed more than once, or is modi?ed and the prior value is read other than to determine the value to be stored (6.5).
大致意為在兩個序列點(;或,)中,同一變量被修改超過一次的做法是未定義行為。
C99還規定,未定義行為由編譯器自行處理,輸出什么結果都可以!輸出一個Hello World都是符合標準的。
x++確切的解釋
i = i + i++之類問題,根本不是優先級的問題。
簡單來說,a = b,和數學課本上的等式完全是兩個意思。在計算機領域,它的意思是:先計算出表達式b的值,然后把這個值賦給a。
表達式的定義為:一個單獨的字面值,或者一個單獨的變量,或者通過算術/邏輯運算甚至函數調用連接起來的表達式——注意,賦值操作不是什么算術/邏輯運算,也不是函數調用。
顯然,對于表達式b來說,它的運算符優先級有多復雜都不是問題。
但,因為太重要所以需要再說一遍:請注意,表達式里面不允許出現賦值操作,因為這個操作并不是算術/邏輯運算。
顯然,i++的問題在于,雖然i++看起來只有操作符和操作數組合、而且通常作為表達式使用,但其實它的含義是i=i+1——這根本不是一個表達式,而是“計算表達式i+1的值,并將其賦予變量i”:換句話說,這里面額外有一個賦值操作。
事實上,i++本身作為一個c/c++語句,是不可刪除的;而 2+3、a&&b、!a之類真正的表達式構成的單獨語句則可以在編譯時直接刪除。原因就是i++另外還隱含了一個賦值操作,從而多了個會影響程序狀態的“副作用”。
c/c++里面,類似這個賦值操作的、執行后會影響程序狀態的行為,被稱為“副作用(side effect)”。
進一步的,c/c++標準里面對這類有表達式外表、但卻另有額外語義的“假”表達式叫做“有side effect的表達式”(關于何謂side effect,c/c++標準有專門定義,請盡量參考這個定義,因為我的轉述很可能會有某些瑕疵之處,不可輕信),實質上也是強調了它和原始意義上的表達式的不同之處。
但是呢,為了寫代碼的便利,c/c++系語言提供了一個語法糖,允許程序員將i++用到表達式里面,同時規定其含義為:首先取i的值,用這個值代入表達式,供以后求值用;之后,執行i=i+1(執行i=i+1的確切時機不限,在表達式求值之前還是之后都行,只要執行了就對)。
如此一來,忽略副作用不提的話,i++看起來就像是一個真正的表達式。
但,必須注意,i++畢竟不是一個表達式,它畢竟還有個副作用藏在里面。粗暴的用某種規定允許它摻乎進去,就必然帶來很多棘手的問題。
比如說,i=i++,這個語句如何解釋?
首先,這顯然是一個賦值語句,所以最終i應該存的是等號右側表達式的值;雖然i++不是表達式,但按照規定,它可以解釋為“語句執行前i的取值”;所以,這其實是把語句執行前,i的取值賦給i的一個賦值語句——也就是說,執行后,i的值應該不變。
但,注意i++還有一個賦值動作。即:把語句執行前的i值加一,然后賦值給變量i——所以,執行后,i的值應該增加了1。
顯然,兩個賦值動作的執行結果出現了矛盾。究竟哪個對呢?
進一步的,i=(i++)+(i++)呢?這里面可有三個針對i的賦值操作啊。
不僅如此,對于函數調用,如max(i++, i++),這又是什么意義呢?
很顯然,不是表達式的i++,絕對不能和表達式混淆。
雖然,為了表達簡潔,c/c++系列語言允許它在特定場合代替表達式,但這并不等于說,c/c++就認為它和表達式沒有差別。相反,c/c++自始至終都認為它是一個賦值操作,只是可以在嚴格限定的場景替代表達式而已——這個“嚴格限定”,就是“不允許一個變量在一對序列點之間兩次改變其值”(不太嚴謹的說法)。
只有滿足了這個“嚴格限定”,程序才不會出現“二義”。
換句話說,i++本身是一個有著特定內涵(對i賦值)的指令,并不是單純的數學表達式。把它當基本數學表達式濫用,得到的復合表達式是沒有數學意義(因而也沒有現實意義)的。
把它用對,是程序員的責任。
最后給個面試題的例子,看看就好,畢竟真有人考這玩意……
public class Test {
public static void main(String[] args) {
test1();
test2();
test3();
test4();
test5();
test6();
test7();
}
private static void test1() {
int a;
a = 10;
a = a++;
System.out.println("test1 a = " + a);//10
}
private static void test2() {
int a, b;
a = 10;
a = a + a++;
System.out.println("test2 a = " + a);//20
}
private static void test3() {
int a;
a = 10;
a = a++ + a;
System.out.println("test3 a = " + a);//21
}
private static void test4() {
int a;
a = 10;
a = a++ + ++a;
System.out.println("test4 a = " + a); //22
}
private static void test5() {
int a, b;
a = 10;
b = a + a++;
System.out.println("test5 a = " + a);//11
System.out.println("test5 b = " + b);//20
}
private static void test6() {
int a, b;
a = 10;
b = a++ + a;
System.out.println("test6 a = " + a);//11
System.out.println("test6 b = " + b);//21
}
private static void test7() {
int a, b;
a = 10;
b = a++ + ++a;
System.out.println("test7 a = " + a);//12
System.out.println("test7 b = " + b);//22
}
}
參考了作者:invalid s在https://www.zhihu.com/question/23180989/answer/23874381中的描述。