文章目录

  • 问题描述
  • 模拟++的机制
  • 源码解析
  • 指令图解
    • 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。

i++和++i的真正区别相关推荐

  1. RPC 笔记(01)— RPC概念、调用流程、RPC 与 Restful API 区别

    1. 基本概念 PRC 远程过程调用 Remote Procedure Call,其就是一个节点请求另外一个节点提供的服务.当两个物理分离的子系统需要建立逻辑上的关联时,RPC 是牵线搭桥的常见技术手 ...

  2. C++ 笔记(28)— C++ 中 NULL和 nullptr 的区别

    最近看公司代码的时候发现在判断指针是否为空的时候,有的时候用的是 NULL, 有的时候用的是 nullptr 感觉很奇怪,好奇心驱使我查了下两者的区别,发现还是有很多细节需要学习的. 1. NULL ...

  3. gcc 和 g++ 的联系和区别,使用 gcc 编译 c++

    GCC 编译器已经为我们提供了调用它的接口,对于 C 语言或者 C++ 程序,可以通过执行 gcc 或者 g++ 指令来调用 GCC 编译器. 实际使用中我们更习惯使用 gcc 指令编译 C 语言程序 ...

  4. Python2 与 Python3 区别

    Python2.x 与 Python3.x 区别 1. print 函数 Python2 中 print 是语句(statement),Python3 中 print 则变成了函数.在 Python3 ...

  5. Docker 入门系列(1)- 初识容器,镜像、容器、仓库的区别

    Docker 简介 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发 ...

  6. HTTP 协议入门 — (TCP/IP协议族、通信传输流、URI 与 URL 的区别、Cookie 状态管理、HTTP 支持的方法、状态码类别、HTTP 首部字段)

    TCP/IP协议族 在介绍 HTTP 协议之前,我们先对 TCP/IP 协议族有个大概的了解,TCP/IP 协议从上到下主要分为应用层.传输层.网络层和数据链路层,各层的主要功能如下表所示: 协议层 ...

  7. python二进制打开(rb)和文本格式打开(r)什么区别?

    使用 open() 函数以文本格式打开文件和以二进制格式打开文件,唯一的区别是对文件中换行符的处理不同. 在 Windows 系统中,文件中用 "\r\n" 作为行末标识符(即换行 ...

  8. python中__dict__与dir()区别

    前言 Python下一切皆对象,每个对象都有多个属性(attribute),Python对属性有一套统一的管理方案. __dict__与dir()的区别: dir()是一个函数,返回的是list: _ ...

  9. java和C#面向对象的区别

    问题:C#中的构造函数和java的构造函数一样吗? 答: .net的项目中,写实体Entity属性时,经常会为每一个属性写一对get和set方法,在用到这个实体或是一个类时,通过new实例化一个对象, ...

  10. 判别模型和生成模型的区别

    20210703 https://www.zhihu.com/question/20446337 机器学习"判定模型"和"生成模型"有什么区别? 重点 http ...

最新文章

  1. 12月北京CISA认证考试考前辅导会成功举办
  2. 早在公元前五百年,孙子就参透了数据库分区的真谛
  3. C# 四舍五入round函数使用的代码
  4. 别再说“我已经努力了”,你的“努力”一文不值!
  5. html 手机分辨率,移动端各种分辨率手机屏幕----适配方法集锦
  6. So Easy!(HDU - 4565)
  7. Python if语句Demo
  8. 2018-2019-1 20165204 实验三 实时系统
  9. Spring8中lambda表达式的学习(Function接口、BiFunction接口、Consumer接口)
  10. nv21转jpg c语言,Yuv420转Jpeg(C语言实现)
  11. Jenkins把GitHub项目做成Docker镜像
  12. 第012课 内存控制器与SDRAM
  13. 科目三考试技巧全总结
  14. Android蓝牙4.0单车锁应用实例开发
  15. C++实现龙贝格求积分算法
  16. 2020年9月-上海-bilibli(B站总部)面试题
  17. C语言在坐标轴上输出曲线,C语言打印正弦曲线、直线、圆等等
  18. 维恩贝特面试JAVA后台开发
  19. EXCEL链接Kylin
  20. SDNU 1221

热门文章

  1. 【软件安装教程】【360清理大师】(在windows系统里的应用商店有自带的)
  2. Java的语言修饰符
  3. centos安装htop
  4. OpenGl(1) VAO、VBO、EBO的理解
  5. 中国食用油市场供需调研与投资战略分析报告2022年版
  6. CSS中的 “点“,“空格“,“>“ ,“逗号”,“+ ”
  7. 在线压缩视频的方法,一键压缩视频文件
  8. 面试题 如果要画一只鸟和一个人,你会如何构图
  9. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java钟点工管理系统ve5km
  10. QTableWidget 自动分行