关于前置 ++ 与后置++的差别,这似乎再熟悉不过了。前置 ++ 是先将变量的值加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;】等价。
①前置++是将自身加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)前,先将变量的值压入栈,执行增值指令后,使用的是之前压入栈的值。