深入理解 i++ 与 ++i 的区别

1 篇文章 0 订阅
1 篇文章 0 订阅

深入理解 i++ 与 ++i 的区别

浅层理解

i++ 是先进行赋值,再对变量 i 进行加1操作

++i 是先对变量 i 进行加1操作,在将 i 的值赋值给其他变量

int i = 0;
int j, k;
System.out.println("k = " + (k = i++)); // k = 0
System.out.println("j = " + (j = ++i)); // j = 2

问题

int i = 0;
i = i++;
System.out.println("i = " + i); // ?





深入理解

运行时内存区

知道内存划分的可以跳过这部分

先简单介绍 java程序在运行时所用到的内存的划分

由于jdk版本的不同,jvm(运行java程序的平台)也不同,对内存的划分也不同,详情请系统学习JVM

  • 简单分类及理解(理解本节必须掌握的内容)
    • 堆:主要存放一些new出来的对象
    • 方法区:jvm会将字节码文件(.class)中的信息保存的方法区Klass中,其中包含了类的所有信息
    • 栈:采用栈的数据结构来管理方法的调用。
      • 每个被调用的方法的数据信息(的引用)存放在栈帧中(,可根据引用在堆,方法区找到真正的数据内容)
      • 方法执行流程
      • 栈帧中的内容主要掌握
        • 局部变量表:已数组的形式存放一个方法中出现的变量
        • 操作数栈
        • 帧数据(略)
  • 详细分类(暂时不用深入理解,但早晚得掌握)
    • 每个线程都拥有的独立的内存区域
      • 程序计数器:用来记录指令执行到的为止
      • 方法栈(与简单分类中的对应)
        • java虚拟机栈
        • 本地方法栈
    • 所有线程共享的内存区域
        • 逻辑空间划分(可能听说过,所以列举出来)
          • 永久代(jdk8移除)
          • 年轻代(又可以分为伊甸园区、生存者区)
          • 老年代(又分为普通对象老年代、大对象区)
        • 存储内容划分
          • new出来的对象
          • Class对象(供反射使用)
          • 字符串常量池(面试热点)
          • 静态变量
          • 。。。。
      • 方法区(jdk8版本前后变动较频繁的区域):略




方法的执行

public static void main(String[] args) {
    method1();
}

pulic static void method1() {
    method2();
}

public static void method2() {
    // ...
}

请添加图片描述

main 方法先入站

main方法中执行到 method1() 时,method1() 方法入栈

method1()方法中method2()时,method2()方法入栈

随后,method2方法执行完,出栈;接着method1随m2方法执行完,也执行完,出栈

。。。


局部变量表与操作数栈

这两个部分都存放在栈帧中,每个方法对应一个栈帧,所有每个方法对应一个局部变量表和操作数栈

局部变量表
  • 局部变量以数组的方式记录

  • 举例

    public static void oneMethod(int i, int j) {
        int k = i+j;
    }
    

    在这个案例中,局部变量有 i,j,k ;可列下表

    数组索引012
    数组内容i 变量信息jk
操作数栈
  • 同样也采用栈这种数据结构

  • 存放的是计算过程中的临时值

  • 举例

    • 还是以上面代码为例

      public static void oneMethod(int i, int j) {
          int k = i+j;
      }
      
    • 步骤

      • 当调用这个方法时(如 oneMethod(1, 2)),会为局部变量表中的 ij 赋值,此时初始的局部变量表

        数组索引0(i对应的索引)1(j对应的索引)2(k对应的索引)
        对应的值12
      • 先读取局部变量表中 i 的值(即从索引0处读取)并放入操作数栈中,此时栈:

        1(栈顶)
      • 再读取局部变量表中 j 的值(即从索引1处读取)并放入操作数栈中,此时栈:

        2(栈顶)
        1
      • 执行相加操作(从栈顶取出两个数,相加得到一个数,再放入栈中),此时操作数栈:

        3(栈顶)
      • 从栈顶取出一个数,将该数放到局部变量表k所对应的位置,此时局部变量表

        数组索引0(i对应的索引)1(j对应的索引)2(k对应的索引)
        对应的值123
      • 该方法结束,对应的栈帧出 方法栈(注意与操作数栈区分)

  • 举例2

    int m = i + j + k

    先从局部变量表中分别读取到 i 和 j 的值到操作数栈

    执行相加操作得到临时值( i 与 j 相加的真实值)

    再从局部变量表中读取到k的值到操作数栈

    再与刚才的临时值相加得到临时值(i 与 j 与 k 相加的真实值)

    从栈中取出该值存放到表中m的位置

i++ 与 ++i 的区别

终于进入主题

  • 首先,我们写的 每个方法 会由 jvm翻译为一段段指令(暂且这么理解)

    请添加图片描述

    • 解释一些出现的指令(已第一段指令为例)
      • iconst_1:将常量 1 放入操作数栈中
      • istore_1:取出栈顶部数据,存到表中索引1的位置(索引0是args的位置,索引1即为变量a的位置)
      • iload_1:从表中索引 1 的位置读取数据到 操作数栈
      • iadd:取出栈顶部两个数据,相加,将和放入操作数栈中
      • invokestatic #2:调用静态方法#2(即method()方法,#2是符号引用)
  • 进入正题

    • 补充一些指令 更多指令

      • iinc 1 by 2 : 对局部变量表索引1号位的值加2(直接对局部变量表的操作)
      • getstatic #1 : #1处静态字段值入栈
      • putstatic #1 : 将栈顶部值存到#1静态字段处
      • dup :复制栈顶部数据
    • i++

      public static void main(String args) {
          int i = 0;
          i = i++;
          // System.out.println(i) // 0
      }
      
      // 先读取一份未计算的值到局部变量表
      // 虽然直接对局部变量表进行+1操作,但是又被操作数栈中的原始值覆盖
      iconst_0     // 将0存到操作数栈中           栈:->[0] 数组:[args, i]
      istore_1     // 从栈中取出一个存到数组1号位   栈:->[] 数组:[args, i=0]
      iload_1      // 将数组1号位的值加载入栈      栈:->[0] 数组:[args, i=0]
      iinc 1 by 1  // 对数组1号位的值加1          栈:->[0] 数组:[args, i=1]
      istore_1     // 从栈中取出一个存到数组1号位   栈:->[] 数组:[args, i=0]
      return
      
    • ++i

      public static void main(String args) {
          int i = 0;
          i = ++i;
          // System.out.println(i) // 1
      }
      
      // 先对局部变量表进行+1操作,再读取一份已计算的值到操作数栈,之后即使覆盖,也是用已计算的值覆盖
      iconst_0     // 将0存到操作数栈中           栈:->[0] 数组:[args, i]
      istore_1     // 从栈中取出一个存到数组1号位   栈:->[] 数组:[args, i=0]
      iinc 1 by 1  // 对数组1号位的值加1          栈:->[] 数组:[args, i=1]
      iload_1      // 将数组1号位的值加载入栈      栈:->[1] 数组:[args, i=1]
      istore_1     // 从栈中取出一个存到数组1号位   栈:->[] 数组:[args, i=1]
      return
      
    • 含静态字段的 i++

      private static int number = 3;
      public static void add1() {
          return number++;
      }
       // 先复制 dup 一份未计算的值,后计算,此时会剩下一个值未+1
      getstatic #1   // #1处静态字段值入栈      栈:->[3]       数组:[]
      dup            // 复制栈顶部数据          栈:->[3, 3]    数组:[]
      iconst_1       //                      栈:->[1, 3, 3] 数组:[]
      iadd           // 让栈顶部两个数相加      栈:->[4, 3]    数组:[]
      putstatic #1   // 将栈顶部值存到#1处      栈:->[3]       数组:[]
      ireturn        // 返回栈顶部值           栈:->[]        数组:[]
      
    • 含静态字段的++i

      private static int number = 3;
      public static void add1() {
          return ++number;
      } 
           
       // 先计算,后复制dup,此时,两个值都是已+1的值
      getstatic #1   // #1处静态字段值入栈      栈:->[3]       数组:[]
      iconst_1       //                      栈:->[1, 3]    数组:[]
      iadd           //                      栈:->[4]       数组:[]
      dup            // 复制栈顶部数据          栈:->[4, 4]    数组:[]
      putstatic #1   // 将栈顶部值存到#1处      栈:->[4]       数组:[]
      ireturn        // 返回栈顶部值           栈:->[]        数组:[]
      

总结

交给你自己了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值