透析Java之i++和++i;

关于前置 ++ 与后置++的差别,这似乎再熟悉不过了。前置 ++ 是先将变量的值加1,然后使用加1后的值参与运算;而后置 ++ 是先使用该值参与运算,然后再将该值加1。
没错,关于二者之间的区别,的确可以这样认为,并且按照上面操作,运算中也基本不会有什么错误。但是,如果我告诉你,后置 ++ 其实与前置 ++ 一样,在参与运算之前都会将变量的值加1,你信吗?恩,应该是不信,不过,这是真的……

(1)如果只是看i++和++i,这两个是等价的,都等同于i=i+1,都是变量自身加1。
(2)在一般情况下,它们都是跟赋值联系在一起。
比如:
int a;
a=i++;//将i的值赋值给a,即a=i;然后再执行i=i+1;
也就是【a=i++;】与【a=i; i=i+1;】等价。
a=++i;//将i+1的值赋给a,即a=i+1;然后再执行i=i+1;
也就是【a=++i;】与【a=i+1;i=i+1;】等价。

(3)【总结一下】
①前置++是将自身加1的值赋值给新变量,同时自身也加1;
②后置++是将自身的值赋给新变量,然后才自身加1.
public class MyClass {

    public static void main(String[] args) {
        int i = 15;
        prePlusJ(i);
        prePlusI(i);
        postPlusJ(i);
        postPlusI(i);
    }

    private static void prePlusJ(int i) {
        int j = ++i;
        System.out.println("++i对j的赋值:");
        System.out.println("i = " + i);
        System.out.println("j = " + j);
    }

    private static void prePlusI(int i) {
        i = ++i;
        System.out.println("++i对i的赋值:");
        System.out.println("i = " + i);
    }

    private static void postPlusJ(int i) {
        int j = i++;//  标记1
        System.out.println("i++对j的赋值:");
        System.out.println("i = " + i);
        System.out.println("j = " + j);
    }

    private static void postPlusI(int i) {
        i = i++;//   标记2
        System.out.println("i++对i的赋值:");
        System.out.println("i = " + i);
    }

打印结果如下:

++i对j的赋值:
i = 16
j = 16
++i对i的赋值:
i = 16
i++对j的赋值:
i = 16
j = 15
i++对i的赋值:
i = 15
前3个方法的输出应该没什么好说的,只是最后一个方法postPlusI的输出有点离奇:
i=15

为什么会是15呢?按照通俗的见解,虽然后置 ++ 是先参与运算,然后再将值加1,但是执行对自身的赋值运算后(标记2),值也该加1,变成16才是啊。况且,当后置 ++ 对其他变量(j)赋值后(标记1),i自身也加1了,并且运行结果也打印出16,为什么对自己赋值后,结果就不一样了呢?

实际上,不管是前置 ++,还是后置 ++,都是先将变量的值加1,然后才继续计算的。二者之间真正的区别是:前置 ++ 是将变量的值加1后,使用增值后的变量进行运算的,而后置++ 是首先将变量赋值给一个临时变量,接下来对变量的值加1,然后使用那个临时变量进行运算。从效果上来说,假设有如下的程序片段:

        int i = 2;
        int j = ++i * 30;//a
        //第a行等同于
        i += 1;//讲i加1,值为3;
        j = i * 30;//3 * 30 = 90
        //而如果程序片段为:
        int i = 2;
        int j = i++ * 30;//b
        //第b行等同于
        int temp = i;  //将i赋值给一个临时变量,temp值为2。
        i += 1;        //将i加1,值为3。
        j = temp * 30; //使用临时变量temp进行运算,结果j为60。
好了,原理已经介绍清楚了,现在就让我们回过头来看上述【例MyClass】。其中i的初始值为15:
i = ++i;
这就相当于:
i += 1;    //将i加1,值为16。
i = i;     //使用i对自身赋值,值还是16。
而程序第标记2行:

i = i++;

这就相当于:
int temp = i;  //将i赋值给一个临时变量,temp值为15。
i += 1;        //将i加1,值为16。
i = temp;      //使用临时变量temp对i赋值,结果i值为15。

现在清楚了吧,这就是整个过程。其实后置 ++ 也是先将i的值加1,只是后来参与运算的不再是i,而是那个临时变量。

进一步的探索 实现扩展

让我们来更进一步地研究下前置 ++ 与后置 ++ 的区别。为了说明问题,列举出一个简单的程序。

package com.example;

/**
 * Company: 北京****科技有限公司,010-62538800,[email protected]
 * @author Created by ylwang on 2018/2/28
 */

public class DeepPlus {
    void post() {
        int i = 0;
        int j = i++;
    }

    void pre() {
        int i = 0;
        int j = ++i;
    }
}
保存并编译文件,打开控制台,然后进入class文件的目录(包example的上级目录),输入:
javap -c example.DeepPlus

其中javap是反编译命令,-c为显示代码反编译后的伪指令。结果如下:


    void post();
    Code:
     //将int类型常量0压入栈,即当前栈顶值为int类型0。
     0: iconst_0
    //从栈顶弹出一个int类型值,然后将该值存储在局部变量1中。这里局部变量1就是
    //程序中的变量i,也就是将刚才压入栈的0弹出,赋值给变量i。这两条指令相当于执
    //行(int i = 0;)。
    1: istore_1
    //将局部变量1中存储的int类型值压入栈,即将i的值0压入栈。这在程序中就相当
    //于将i的值赋值给一个临时变量temp,此时,temp的值为0。
    2: iload_1
    //将局部变量1的值加1。也就是将i的值加1。在程序中,这就相当于(i += 1;)。
    //此时,i的值为1。
    3: iinc         1, 1
     //从栈顶弹出一个int类型值,然后将该值存储在局部变量2中。这里局部变量2就是
     //程序中的变量j,也就是将刚才压入栈的i值0弹出(i压栈时值还没有加1),赋值
     //给变量j。在程序中就相当于(j = temp;)。
     6: istore_2
    //返回。
    7: return

接下来,再看一下pre方法:

    void pre();
    Code:
     //将int类型常量0压入栈,即当前栈顶值为int类型0。
     0: iconst_0
    //从栈顶弹出一个int类型值,然后将该值存储在局部变量1中。这里局部变量1就是
    //程序中的变量i,也就是将刚才压入栈的0弹出,赋值给变量i。这两条指令相当于执
    //行(int i = 0;)。
    1: istore_1
    //将局部变量1的值加1。也就是将i的值加1。在程序中,这就相当于(i += 1;)。
    //此时,i的值为1。注意,前置 ++ 在执行iinc指令的时候并没有将i的值压入栈,
    //也就是并没有赋值给一个临时变量。
    2: iinc         1, 1
     //将局部变量1中存储的int类型值压入栈,即将i的值1压入栈。
     5: iload_1
    //从栈顶弹出一个int类型值,然后将该值存储在局部变量2中。这里的局部变量2就是
    //程序中的变量j,也就是将刚才压入栈的i值1弹出(i压栈时值已经加1),赋值
    //给变量j。在程序中就相当于(j = i;)。
    6: istore_2
    //返回。
    7: return
现在我们已经从指令的级别来分析二者之间的差异了。前置 ++ 直接将变量的值加1,然后使用这个变量的值。而后置 ++ 是先将变量的值压入栈(暂时保存起来),然后将变量加1,之后使用压栈时的值。
要点总结:
· 前置 ++ 与后置 ++ 都是先将变量的值加1,而不是前置 ++ 先加1然后运算,而后置++ 先运算后加1。
· 从程序上说,后置 ++ 先将变量赋值给一个临时变量,然后将变量的值加1,接下来使用那个临时变量参与运算。
· 从指令上说,后置 ++ 在执行增值指令(iinc)前,先将变量的值压入栈,执行增值指令后,使用的是之前压入栈的值。

猜你喜欢

转载自blog.csdn.net/iblade/article/details/79404674