i++ 和 ++i

计算原则

i++ 和 ++i 都是先自增后返回,只不过返回值有不同。后文有代码指令解释。

对此有个简单的记忆方法,变量在 ++ 前返回的就是自增前的值,变量在 ++ 后返回的就是自增后的值。

另外,java 的表达式是从左到右执行的,即使只是一个变量,它也是一个表达式。

记住,用逻辑运算符(+、-、*、/)分割的就是一个表达式。

但表达式的执行也要遵循计算式的优先级。

 计算分析

比如以下代码及指令解释:
复制代码
// 4: iload_1         // 本地局部变量表索引为 1 的变量槽值 0 入栈( i = 0 中的 0 )到栈顶,此时操作数栈模型为:顶部 0 <- 尾部
// 5: iinc          1, 1    // 本地局部变量表索引为 1 的变量槽值 0 自增 1(i 自增 1),此时 i = 1
// 8: iinc          2, 1    // 本地局部变量表索引为 2 的变量槽值 0 自增 1(j 自增 1),此时 j = 1
// 11: iload_2              // 本地局部变量表索引为 2 的变量槽值 1 ( j = 1 中的值 1 )压入栈顶,此时操作数栈模型为:顶部 1 <- 0 尾部
// 12: iload_1              // 本地局部变量表索引为 1 的变量槽值 1  (i = 1 中的值 1 )压入栈顶,此时操作数栈模型为:顶部 1 <- 1 <- 0 尾部
// 13: imul                 // 操作数栈中栈顶的两个操作数 1,1 出栈运行乘法,计算结果压入栈顶,此时操作数栈模型为:顶部 1 <- 0 尾部
// 14: iadd                 // 操作数栈中栈顶的两个操作数 1,0 出栈运行加法,计算结果压入栈顶,此时操作数栈模型为:顶部 1 <- 尾部
// 15: istore_3             // 操作数栈中栈顶的操作数 1 出栈到本地变量 k,k = 1,此时操作数栈模型为:顶部 <- 尾部
int i = 0;
int j = 0;
int k = i++ + ++j * i;
复制代码

根据 i++ 和 ++i 的计算原则和计算式优先级

先算乘除、后算加减

此时表达式可以拆分为两块 i++ 表达式的值加上 ++j * i 的值

(i++) + (++j * i)

计算第一步:i++ 返回自增前的 i,表达式为 0 + (++j * 1)

为什么是这样呢?因为 i++ 执行后返回了自增前的 i,即 i++ 这个表达式返回值为 0

而实际上 i++ 这个表达式已经改变了 i 的值,执行 i++ 表达式之后,i 已经为 1 了

计算第二步:++j 表达式返回自增后的 j,即 j++ 这个表达式的返回值为 1,此时表达式为 0 + (1 * 1)

计算第三步:返回结果 1

 

图解分析

指令: 0: iconst_0

读取 “int i = 0” 的值 “0” 压入操作数栈栈顶

 

指令:1: istore_1

操作数栈栈顶的操作数 “0” 写入索引为 1 的本地局部变量变量槽 Sloat,

执行 “int i = 0” 赋值操作,把值 0 赋给变量 i

 

指令:2: iconst_0

读取 “int j = 0” 的值 “0” 压入操作数栈栈顶

 

指令:3: istore_2

操作数栈栈顶的操作数 “0” 写入索引为 2 的本地局部变量变量槽 Sloat,

执行 “int j = 0” 赋值操作,把值 0 赋给变量 j

 

 

指令:4: iload_1

从这条指令开始执行 “int k = i++ + ++j * i;”

本地局部变量索引为 1 的变量槽值 0 入栈,

即 “=” 右侧的表达式从左向右执行,这条指令就是把 “i++” 中的 i 的入栈。

 

指令:5: iinc 1, 1

本地局部变量索引为 1 的变量槽值 0 自增加 1,即执行 “i++” 中的 “++” 进行自增。

此时局部变量表索引为 1 的变量槽中的值为 1,即此时实际上是 “i = 1” ,

但是操作数栈中依旧保留自增前的值 0。此时表达式可以写成 “0 + (++j * 1)”

 

指令:8: iinc 2, 1

本地局部变量索引为 2 的变量槽值 0 自增加 1,即执行 “++j” 中的 “++” 进行自增。

此时局部变量表索引为 2 的变量槽中的值为 1,即此时实际上是 “j = 1” 。

此时表达式可以写成 “0 + (1 * 1)”

 

指令:11: iload_2

本地局部变量索引为 2 的变量槽值 1 入操作数栈栈顶,此时操作数栈有两个值 1,0

 

指令:12: iload_1

本地局部变量索引为 1 的变量槽值 1 入操作数栈栈顶,此时操作数栈有三个值 1,1,0

 

指令:13: imul

操作数栈栈顶的前两个操作数做乘积运算(执行 1 * 1)并出栈,计算结果 1 入操作数栈栈顶,此时操作数栈有两个值 1,0

 

指令:14: iadd

操作数栈栈顶的前两个操作数做累和运算(执行 0 + 1)并出栈,

计算结果 1 入操作数栈栈顶,此时操作数栈有一个值 1

 

指令:15: istore_3

操作数栈栈顶的操作数 1 写入本地局部变量表索引为 3 的变量槽,即 k = 1

 

参考指令

iload_1 本地局部变量表索引为 1 的槽位的 int 值入栈顶

iinc 1, 1 本地局部变量表索引为 1 的槽位的 int 值自增 1

istore_1 栈顶的值出栈写入本地局部变量表索引为 1 的槽位

imul 操作数栈前两个运行乘法,计算结果压入栈顶

iadd 操作数栈前两个运行加法,计算结果压入栈顶

 

口诀

变量在前返回前;

变量在后返回后;

从左到右算有序;

计算优先不可忘;

 

演示代码

复制代码
public static void main (String[] args) {
    // 0: iconst_0
    // 1: istore_1
    // 2: iconst_0
    // 3: istore_2

    // 4: iload_1               // 入栈 i = 0 中的 0 到栈顶
    // 5: iinc          1, 1    // 本地变量表中的 i 自增 1,此时 i = 1
    // 8: istore_1              // 栈顶的 0 出栈到本地变量表 i,这个操作导致 i 重新变成 0
                                // 这个 i++ 的操作结束,总结就是入栈、自增、回写(返回)

    // 9: iinc          2, 1    // 本地变量表中的 j 自增 1,此时 j = 1
    // 12: iload_2              // 入栈 j = 1 中的 1 到栈顶
    // 13: istore_2             // 栈顶的 1 出栈到本地变量表 j,这个操作导致 j 依旧为 1
                                // 这个 ++j 的操作结束,总结就是自增、入栈、回写(返回)
    // 14: return
                                // 所以说,无论是 i++ 还是 ++i,都是先自增最后返回
                                // 只不过二者入栈的时机不同,导致最终返回值不同
                                // i++ 是先入栈,最终返回的就是自增之前入栈的值 0
                                // ++i 是后入栈,最终返回的就是自增之后入栈的值 1
    int i = 0;
    int j = 0;
    i = i++;
    j = ++j;
}
复制代码

 

模型

Java 代码执行模型

 

参考文章:
https://blog.csdn.net/android_cai_niao/article/details/106027313
https://www.cnblogs.com/longjee/p/8675771.html

 

posted @   维维尼~  阅读(480)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示