i++和++i的真正区别

news/2024/9/23 4:19:22/

文章目录

      • 问题描述
      • 模拟++的机制
      • 源码解析
      • 指令图解
        • 1.方法执行前在内存中的的数据结构
        • 2.执行iconst_0
        • 3.执行istore_1
        • 4.执行iload_1
        • 5.执行iinc 1 by 1
        • 6.执行istore_1
      • 测试示例
      • 结论

问题描述

  • 记得刚开始学编程的时候还是从c语言开始的,还是看的谭浩强写的那本书,上面对介绍i++和++i的区别如下:
    i++是先赋值,然后再自增;++i是先自增,后赋值。
    用代码表示就是:
    若 a = i++; 则等价于 a=i;i=i+1;
    而 a = ++i; 则等价于 i=i+1;a=i;

  • 那么事实真是这样么,只是曾经我深信不疑,但是直到我看到下面这段代码:

      @Testpublic void test(){int i = 0;i=i++;System.out.println(i);}
    

    如果按原先定义,就应该是i=i;i=i+1; 那么结果就应该是1;但是很遗憾结果是0;所以得知原先定义有误,至少是不准确的。

模拟++的机制

那么真实的机制是怎么样的呢?我简单用代码模拟一下它的效果:

    int i;@Testpublic void testAddI() {i = 0;i = lastAdd();System.out.println(i);i = 0;i = firstAdd();System.out.println(i);}//模拟i++的机制public int lastAdd() {//操作i前对其值保留副本int temp = i;i = i + 1;//返回副本return temp;}//模拟++i的机制public int firstAdd() {i = i + 1;return i;}

输出结果为0和1,和i=i++以及i=++i的结果一致。

通过以上代码模拟,似乎在java的执行过程中,i++和++i都直接对i进行了i=i+1的操作,但是不同的是i++得到的是i未进行加法操作的前的值的副本,而++i直接得到计算后的值。那么,事实真的是这样吗,我们再去刨析一下源码,看看在汇编指令中,它到底是怎么做的。

源码解析

再写一个类,源码如下:

public class PlusI {public void iPlusPlus(){int i = 0;i++;}public void plusPlusI(){int i = 0;++i;}}

对其class文件进行反汇编后,代码如下:

public class com.aliencat.javabase.bit.PlusI {public com.aliencat.javabase.bit.PlusI();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic void iPlusPlus();Code:0 iconst_01 istore_12 iinc 1 by 15 returnpublic void plusPlusI();Code:0 iconst_01 istore_12 iinc 1 by 15 return}

先不谈这些汇编指令的意义,乍一看,两个方法的执行指令完全一样。
我们再把代码改下,看看为什么i=i++和i=++i会产生不一样的结果:

public class PlusI {public void iPlusPlus(){int i = 0;i = i++;}public void plusPlusI(){int i = 0;i = ++i;}
}

对其进行反汇编后,代码如下:

public class com.aliencat.javabase.bit.PlusI {public com.aliencat.javabase.bit.PlusI();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic void iPlusPlus();Code:0 iconst_01 istore_12 iload_13 iinc 1 by 16 istore_17 returnpublic void plusPlusI();Code:0 iconst_01 istore_12 iinc 1 by 15 iload_16 istore_17 return}

关于汇编指令的解析请参考:通过jvm指令手册看懂java反汇编源码

关于方法在JVM的内存模型请参考:一文看懂Java内存模型(JMM)

通过比较我们发现,除了iload_1iinc 1 by 1这两条指令顺序有区别外,其它都是一致的。
下面我们来分析一下每条汇编指令的意义:

  • iconst_0:将int类型的0值压入操作数栈

  • istore_1: 弹出操作数栈顶的值赋给局部变量表下标为1的变量

  • iload_1: 将局部变量表下标为1的位置存储的值压入操作数栈

  • iinc 1 by 1:取局部变量表下标为1的位置存储的值加上1

  • istore_1:弹出操作数栈顶的值赋给局部变量表下标为1的变量

指令图解

下面是关于i=i++;执行过程的图解:

1.方法执行前在内存中的的数据结构

因为实例方法的局部变量表中默认第一个是保存的this,所以i的下标位置为1
在这里插入图片描述

2.执行iconst_0

将int类型的0值压入操作数栈

在这里插入图片描述

3.执行istore_1

弹出操作数栈顶的值赋给局部变量表下标为1的变量

在这里插入图片描述

4.执行iload_1

将局部变量表下标为1的位置存储的值压入操作数栈

在这里插入图片描述

5.执行iinc 1 by 1

取局部变量表下标为1的位置存储的值加上1(指令中第一个1代表局部变量表的下标)

在这里插入图片描述

6.执行istore_1

弹出操作数栈顶的值赋给局部变量表下标为1的变量

在这里插入图片描述

下面是关于i=++i;的图解,我就不一一解释了
在这里插入图片描述

测试示例

如果弄懂了上面的原理,很容易猜出下面的计算结果

    public static void main(String[] args) {int i = 0;System.out.println(i++); //输出0i = 0;System.out.println(i++ + i++);//输出 1i = 0;System.out.println(i++ + ++i);  //输出2i = 0;System.out.println(i++ + i++ + i++); //输出3i = 0;System.out.println(i++ + i++ + i++ + i++); //输出6//明明只有4个i+1为什么却得出6的结果你能从你原理解的定义推理出来吗?}

结论

  • 在使用i=i++的过程中,它会先把i的原始值0复制到操作数栈中,然后再对局部变量表中的0进行+1操作使得i变为了1,此时操作数栈顶的值为0,然后执行赋值操作时候使用的是弹出的操作数栈顶的值,所以最后i又被修改为了0;
  • 而i=++i的过程则是先对局部变量表中i的原始值进行加1的操作,即使得i由0变为1,然后将i的值复制到操作数栈,最后赋值即弹出操作数栈顶的值。
  • i++;和++i;的执行过程和结果是一样的。
  • 在使用i++和++i赋值的过程中,他们区别在于前者先复制当前数据,再进行原始值加1的操作,后者则先进行了原始值加1的操作,再对计算后的结果进行了复制,最后返回的其实都是放入操作数栈的拷贝。
  • 看懂了上面的原理,你应该能明白为什么int i = 0;i=i++ + ++i;等于2了吧。如果按原来的定义取理解,也许会得出结果为1。

http://www.ppmy.cn/news/445725.html

相关文章

Option3X 5G 全网部署(基于 IUV_5G 软件)

目录 步骤一&#xff1a;组网选型 步骤二&#xff1a;拓扑规划 步骤三&#xff1a;链路预算 步骤四&#xff1a;站点选址 步骤五&#xff1a;设备配置 步骤一&#xff1a;组网选型 以兴城市为例选择非独立组网架构 步骤二&#xff1a;拓扑规划 一般的非独立组网网元架构…

I3C协议Single Data Rate(SDR)模式研读(一):总线配置Bus Configuration

官方I3C协议规范技术文档中文版 从本篇开始将介绍I3C Protocol&#xff0c;其中主要包括两种模式&#xff1a;SDR和HDR&#xff0c;详细见下图 1. Single Data Rate (SDR) Mode SDR模式是I3C总线的默认模式&#xff0c;主要用于从当前主设备到从设备的私人消息传递。 SDR模式…

i++ 和 ++i 详解

18、i 和 i 详解 1、参考链接&#xff1a;https://blog.csdn.net/song854601134/article/details/111291272?ops_request_misc%257B%2522request%255Fid%2522%253A%2522164698445016780274194665%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&reques…

Intel(R) Ethernet Connection I218 V及Intel(R) Ethernet Connection I219 V不能安装windows Server解决方法

该网卡在安装windows Server系列系统&#xff08;如windows server2012 、windows server2016、windows server2019等&#xff09;时无法安装驱动。 经过查询资料和对比驱动程序&#xff0c;Intel Ethernet Connection I218-LM有windows server驱动。 也兼容多种网卡所以也应该…

DOI号详解

何为DOI号&#xff1f; doi的全称是“digital object identifier”&#xff0c;数字对象唯一标识&#xff0c;被喻为“互联网上的条形码”、“科技论文的身份证”&#xff0c;通过它可以方便、可靠地链接到论文全文。doi代码具有唯一性&#xff0c;这种特性保证了在网络环境下…

I2C协议---I2C时序图解析

一、I2C协议简介 I2C 通讯协议(Inter&#xff0d;Integrated Circuit)是由 Phiilps 公司开发的&#xff0c;由于它引脚少&#xff0c;硬件实现简单&#xff0c;可扩展性强&#xff0c;不需要 USART、CAN 等通讯协议的外部收发设备&#xff0c;现在被广泛地 使用在系统内多个集成…

GET /socket.io/?EIO=3transport=pollingt=MLaWYlC 404 10.986 ms - 1665报错解决方案

nodejs启动程序报&#xff1a; GET /socket.io/?EIO3&transportpolling&tMLaWYlC 404 10.986 ms - 1665 解决方案&#xff1a; 在bin 下的www中加入 var io require(socket.io).listen(server);

i2cdetect i2cdump i2cget i2cset用法

本博客转载自台湾朋友的文章&#xff1a;http://3sec.kilab.tw/?p260 在處理音訊相關的問題時&#xff0c;我通常會找個方法來讀寫codec中register的值。幸好linux上也有這樣的工具 – i2c tools。先到lm-sensors下載soure code&#xff0c;然後cross compile成arm的執行檔&a…