我想大部分都知道 i++ 和 ++i的區別,i++ 就是先拿i來使用,之后再自增加1,而++i則是先自增加1,在拿i來使用,例如對於下面這兩個語句,我敢保證大部分人都會做:
int i = 1; System.out.println(i++)
int i = 1; System.out.println(++1)
答案分別為 1,2。對於這個答案我猜大多數人都能答出來。不過 i++ 和 ++i 這兩個操作,在內部是如何實現的呢?
我們先來看另外一個問題:
public static void main(String[] args) { int i = 1; System.out.println(i+++i++); System.out.println(i); }
這個比剛才那個難了點,答案分別是3,3。假如你對這個答案的由來了如指掌,那么你大不可必往下看,假如你不大理解或者想從底層的匯編指令的來了解這個操作,那么你可以看看我的解釋。
首先我們先來看看 i++ 的題,主要是為了后面好解釋點。
int i = 1; System.out.println(i++);
這兩行代碼的部分匯編指令如下,注意,我只列出了幾個重點的匯編語句:
ICONST_1 //把常量 1 加載到棧頂 ISTORE 1 //把棧頂的元素彈出,並賦值給局部變量表中位置為“1”的變量,此時指變量i。這兩句就相當於 int i = 1; //接下來執行第二行代碼 ILOAD 1 //把局部變量表中位置為“1”的變量加載到棧頂,即把i的值加載到棧頂 IINC 1 1 //直接把局部變量表中位置為“1”的變量加1,即把 i 加1。注意,這條指令並沒有修改操作數棧就把 i 加1了。 INVOKEVIRTUAL java/io/PrintStream.println (I)V //把棧頂的元素打印出來,此時棧頂的元素是 1。所以打印的是 1
注:可以左右拉動
所以,此時打印的是1。
有些人可能沒弄過匯編會有點蒙蔽,沒事,我花個時間畫個圖來模擬(注:省略很多細節)。
剛開始時的局部變量表和操作數棧如圖所示:
1、執行 ICONST_1,常量 1 進棧
2、執行 ISTORE 1,棧頂元素出棧存到位置“1”
3、執行 ILOAD 1,把位置“1”的變量值存到棧頂
4、執行 IINC 1 1 ,直接把局部變量表中位置為“1”的變量加 1
5、執行 INVOKEVIRTUAL java/io/PrintStream.println (I)V ,把棧頂的元素打印出來,此時棧頂的元素是 1.
所以雖然i已經等於2了,但此時棧頂的元素卻是i之前的值 1 ,所以打印的是1。
這下關於 i ++ 的懂了吧?
那我們來看看 ++ i 與 i ++ 的匯編指令有什么不同。
int i = 1; System.out.println(++i);
對應的部分重點匯編指令如下:
//和上面i++差不多,不過IINC 1 1 和ILOAD 1這兩句的順序調換了。 ICONST_1 ISTORE 1 IINC 1 1 //直接把局部變量表中位置為“1”的變量加1 ILOAD 1 //把位置“1”的變量壓到棧頂,此時棧頂的元素是 2 INVOKEVIRTUAL java/io/PrintStream.println (I)V //所以打印的是2
再畫下圖演示一下:
1、執行了ICONST_1 和ISTORE 1這兩句過后的局部變量和棧的情況如下
2、執行 IINC 1 1。注意,執行這條指令,操作數棧不會發生變化。
3、執行 ILOAD 1,把位置“1”的變量值壓入棧頂
4、執行 INVOKEVIRTUAL java/io/PrintStream.println (I)V ,把棧頂的元素打印出來,此時棧頂的元素是 2
所以,對於 i++ 和 ++i的區別徹底懂了吧。
接下來我們來分析這個程序
int i = 1; System.out.println(i+++i++); System.out.println(i);
這里先說一下,按照運算符號的優先順序,i+++i++等價於 (i++) + (i++)。
對應的部分匯編指令如下:
//第一行 ICONST_1 ISTORE 1 //第二行 ILOAD 1 IINC 1 1 ILOAD 1 IINC 1 1 IADD //把棧頂的兩個元素彈出相加之后在把結果放回棧頂 INVOKEVIRTUAL java/io/PrintStream.println (I)V //第三行 ILOAD 1 INVOKEVIRTUAL java/io/PrintStream.println (I)V
如果上面的那兩個 i++ 和 ++i你看懂了,那么上面那個匯編應該也差不多能看懂。我用圖來逐條分析一下吧。
1、執行了 ICONST_1 和ISTORE 1之后的狀態如下
2、執行 ILOAD 1
3、執行 IINC 1 1
4、執行 ILOAD 1
5、執行 IINC 1 1。
此時實際上 i 的值已經是 3 了,只是棧頂放的都是 i 的舊值。
6、執行 IADD ,把棧頂兩個元素出棧相加后再把結果入棧
7、執行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此時棧頂元素為3,所以打印的是3
8、執行 ILOAD 1,把局部變量表加載到棧頂
9、執行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此時棧頂元素為3,所以打印的是3
完畢
現在知道了把,對於 i+++++i 的題也知道怎么做以及怎么回事了吧。
這篇文章重點讓你理解 i++ 與 ++ i的實現機制,對於上面的匯編指令以及進棧入棧的過程為了更好着說明要解決的問題,所以隱藏了很多細節,而且也刪除了部分代碼。如有錯誤的地方,還請見諒。
如果你想了解更多的匯編指令,我這里看到一篇總結的還挺全的:https://blog.csdn.net/hudashi/article/details/7062675
更多精彩文章可以關注我的公眾號:苦逼的碼農(ID:di201805),該公眾號每周還會以專題的模式更新算法題,期待你的關注
推薦閱讀: