你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

我想大部分都知道 i++ 和 ++i的區別,i++ 就是先拿i來使用,以後再自增長1,而++i則是先自增長1,在拿i來使用,例如對於下面這兩個語句,我敢保證大部分人都會作:java

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。假如你對這個答案的由來了如指掌,那麼你大不可必往下看,假如你不大理解或者想從底層的彙編指令的來了解這個操做,那麼你能夠看看個人解釋。ide

首先咱們先來看看 i++ 的題,主要是爲了後面好解釋點。.net

int i = 1;
System.out.println(i++);

這兩行代碼的部分彙編指令以下,注意,我只列出了幾個重點的彙編語句:code

ICONST_1 //把常量 1 加載到棧頂
ISTORE 1 //把棧頂的元素彈出,並賦值給局部變量表中位置爲「1」的變量,此時指變量i。這兩句就至關於 int i = 1;blog

//接下來執行第二行代碼
ILOAD 1  //把局部變量表中位置爲「1」的變量加載到棧頂,即把i的值加載到棧頂
IINC 1 1  //直接把局部變量表中位置爲「1」的變量加1,即把 i 加1。注意,這條指令並無修改操做數棧就把 i 加1了。
INVOKEVIRTUAL java/io/PrintStream.println (I)V  //把棧頂的元素打印出來,此時棧頂的元素是 1。因此打印的是 1

注:能夠左右拉動

因此,此時打印的是1。get

有些人可能沒弄過彙編會有點矇蔽,沒事,我花個時間畫個圖來模擬(注:省略不少細節)。it

剛開始時的局部變量表和操做數棧如圖所示:
你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?io

一、執行 ICONST_1,常量 1 進棧

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

二、執行 ISTORE 1,棧頂元素出棧存到位置「1」

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

三、執行 ILOAD 1,把位置「1」的變量值存到棧頂

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

四、執行 IINC 1 1 ,直接把局部變量表中位置爲「1」的變量加 1

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

五、執行 INVOKEVIRTUAL java/io/PrintStream.println (I)V ,把棧頂的元素打印出來,此時棧頂的元素是 1.

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

因此雖然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

再畫下圖演示一下:

一、執行了ICONST_1 和ISTORE 1這兩句事後的局部變量和棧的狀況以下

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

二、執行 IINC 1 1。注意,執行這條指令,操做數棧不會發生變化。

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

三、執行 ILOAD 1,把位置「1」的變量值壓入棧頂

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

四、執行 INVOKEVIRTUAL java/io/PrintStream.println (I)V ,把棧頂的元素打印出來,此時棧頂的元素是 2

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

因此,對於 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你看懂了,那麼上面那個彙編應該也差很少能看懂。我用圖來逐條分析一下吧。

一、執行了 ICONST_1 和ISTORE 1以後的狀態以下

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

二、執行 ILOAD 1

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

三、執行 IINC 1 1

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

四、執行 ILOAD 1

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

五、執行 IINC 1 1。

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

此時實際上 i 的值已是 3 了,只是棧頂放的都是 i 的舊值。

六、執行 IADD ,把棧頂兩個元素出棧相加後再把結果入棧

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

七、執行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此時棧頂元素爲3,因此打印的是3

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

八、執行 ILOAD 1,把局部變量表加載到棧頂

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

九、執行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此時棧頂元素爲3,因此打印的是3
你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

完畢

如今知道了把,對於 i+++++i 的題也知道怎麼作以及怎麼回事了吧。

這篇文章重點讓你理解 i++ 與 ++ i的實現機制,對於上面的彙編指令以及進棧入棧的過程爲了更好着說明要解決的問題,因此隱藏了不少細節,並且也刪除了部分代碼。若有錯誤的地方,還請見諒。

若是你想了解更多的彙編指令,我這裏看到一篇總結的還挺全的:https://blog.csdn.net/hudashi/article/details/7062675

有收穫?點擊底部卡片加個雞腿犒勞一下?

  • End -

推薦閱讀:
【算法實戰】生成窗口最大值數組
談談NAT:什麼?全球IP和私有IP是什麼鬼?

你真的瞭解 i++, ++i 和 i+++++i 以及 i+++i++ 嗎?

相關文章
相關標籤/搜索