菜鸟笔记
提升您的技术认知

i 和 i的真正区别-ag真人游戏

问题描述

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

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

      @test
      public void test(){
          int i = 0;
          i=i  ;
          system.out.println(i);
      }
    

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

模拟 的机制

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

    int i;
 
    @test
    public 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_0
       1: invokespecial #1                  // method java/lang/object."":()v
       4: return
  public void iplusplus();
    code:
		0 iconst_0
		1 istore_1
		2 iinc 1 by 1
		5 return
  public void plusplusi();
    code:
		0 iconst_0
		1 istore_1
		2 iinc 1 by 1
		5 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_0
       1: invokespecial #1                  // method java/lang/object."":()v
       4: return
  public void iplusplus();
    code:
		0 iconst_0
		1 istore_1
		2 iload_1
		3 iinc 1 by 1
		6 istore_1
		7 return
  public void plusplusi();
    code:
		0 iconst_0
		1 istore_1
		2 iinc 1 by 1
		5 iload_1
		6 istore_1
		7 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  ); //输出0
        i = 0;
        system.out.println(i     i  );//输出 1
        
        i = 0;
        system.out.println(i       i);  //输出2
        
        i = 0;
        system.out.println(i     i     i  ); //输出3
        
        i = 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。
网站地图