i++和++i的真正区别
文章目录
- 问题描述
- 模拟++的机制
- 源码解析
- 指令图解
- 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_1
和iinc 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的真正区别相关推荐
- RPC 笔记(01)— RPC概念、调用流程、RPC 与 Restful API 区别
1. 基本概念 PRC 远程过程调用 Remote Procedure Call,其就是一个节点请求另外一个节点提供的服务.当两个物理分离的子系统需要建立逻辑上的关联时,RPC 是牵线搭桥的常见技术手 ...
- C++ 笔记(28)— C++ 中 NULL和 nullptr 的区别
最近看公司代码的时候发现在判断指针是否为空的时候,有的时候用的是 NULL, 有的时候用的是 nullptr 感觉很奇怪,好奇心驱使我查了下两者的区别,发现还是有很多细节需要学习的. 1. NULL ...
- gcc 和 g++ 的联系和区别,使用 gcc 编译 c++
GCC 编译器已经为我们提供了调用它的接口,对于 C 语言或者 C++ 程序,可以通过执行 gcc 或者 g++ 指令来调用 GCC 编译器. 实际使用中我们更习惯使用 gcc 指令编译 C 语言程序 ...
- Python2 与 Python3 区别
Python2.x 与 Python3.x 区别 1. print 函数 Python2 中 print 是语句(statement),Python3 中 print 则变成了函数.在 Python3 ...
- Docker 入门系列(1)- 初识容器,镜像、容器、仓库的区别
Docker 简介 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发 ...
- HTTP 协议入门 — (TCP/IP协议族、通信传输流、URI 与 URL 的区别、Cookie 状态管理、HTTP 支持的方法、状态码类别、HTTP 首部字段)
TCP/IP协议族 在介绍 HTTP 协议之前,我们先对 TCP/IP 协议族有个大概的了解,TCP/IP 协议从上到下主要分为应用层.传输层.网络层和数据链路层,各层的主要功能如下表所示: 协议层 ...
- python二进制打开(rb)和文本格式打开(r)什么区别?
使用 open() 函数以文本格式打开文件和以二进制格式打开文件,唯一的区别是对文件中换行符的处理不同. 在 Windows 系统中,文件中用 "\r\n" 作为行末标识符(即换行 ...
- python中__dict__与dir()区别
前言 Python下一切皆对象,每个对象都有多个属性(attribute),Python对属性有一套统一的管理方案. __dict__与dir()的区别: dir()是一个函数,返回的是list: _ ...
- java和C#面向对象的区别
问题:C#中的构造函数和java的构造函数一样吗? 答: .net的项目中,写实体Entity属性时,经常会为每一个属性写一对get和set方法,在用到这个实体或是一个类时,通过new实例化一个对象, ...
- 判别模型和生成模型的区别
20210703 https://www.zhihu.com/question/20446337 机器学习"判定模型"和"生成模型"有什么区别? 重点 http ...
最新文章
- 12月北京CISA认证考试考前辅导会成功举办
- 早在公元前五百年,孙子就参透了数据库分区的真谛
- C# 四舍五入round函数使用的代码
- 别再说“我已经努力了”,你的“努力”一文不值!
- html 手机分辨率,移动端各种分辨率手机屏幕----适配方法集锦
- So Easy!(HDU - 4565)
- Python if语句Demo
- 2018-2019-1 20165204 实验三 实时系统
- Spring8中lambda表达式的学习(Function接口、BiFunction接口、Consumer接口)
- nv21转jpg c语言,Yuv420转Jpeg(C语言实现)
- Jenkins把GitHub项目做成Docker镜像
- 第012课 内存控制器与SDRAM
- 科目三考试技巧全总结
- Android蓝牙4.0单车锁应用实例开发
- C++实现龙贝格求积分算法
- 2020年9月-上海-bilibli(B站总部)面试题
- C语言在坐标轴上输出曲线,C语言打印正弦曲线、直线、圆等等
- 维恩贝特面试JAVA后台开发
- EXCEL链接Kylin
- SDNU 1221
热门文章
- 【软件安装教程】【360清理大师】(在windows系统里的应用商店有自带的)
- Java的语言修饰符
- centos安装htop
- OpenGl(1) VAO、VBO、EBO的理解
- 中国食用油市场供需调研与投资战略分析报告2022年版
- CSS中的 “点“,“空格“,“>“ ,“逗号”,“+ ”
- 在线压缩视频的方法,一键压缩视频文件
- 面试题 如果要画一只鸟和一个人,你会如何构图
- 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java钟点工管理系统ve5km
- QTableWidget 自动分行