你真的了解 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++ 吗?

相关文章
相关标签/搜索