java前自增后自增_前自增和后自增运算符问题:

今天遇到了一个问题:int a=0,c=0;

c = (a++)+a;

你觉的c会是什么值?

c0dcd01d3ce13e9e5459dc883832ad90.png

我的第一反应就是c=0,而结果却是c=1;

之前一直错误地认为++a优先级最高,是最先进行的运算;a++优先级最低,是最后才进行的计算。虽然原来看到书上写的a++和++a同属于较高运算级,但毕竟与自己平时的感觉不太一样,没有特别注意。尤其是遇到 c= (a++)+b 和 c= (++a)+b这类问题时,他们的计算结果与自己预期一致,而这更是加深了这种错误认识。

直到遇到了开头提到的问题,我才开始正式审视这个问题。

多方查证得知a++和++a在单独使用时几乎没有差别。

但是在作为操作数时,a++是将加加之前的值作为操作数,而++a相反,它是将加加之后的值作为操作数。

上述c = (a++)+a,其实相当于tmp = a;a=a+1; c=tmp+a;

而另一个c = (a++)+a,相当于a=a+1; c=a+a;

引用一段在其他地方看到的解释:

假设存在a=1,那么“b=(a++)+a;”将如何计算结果呢?按照正文所述,顺序应该是,1)计算b,2)计算a++(假设值为c)(操作数A),3)计算a(操作数B),4)计算c+a,5)将c+a的结果赋值给b。按照“++”的定义,第2)步中a++的结果依然是1,即c为1,随后a立即增1,因此在执行第3)步时,a的值已经是2。所以b的结果为3。很多初学者会误认为a增1的操作是在表达式计算完毕后执行的。

经过实践,上述表诉是正确的,至少它在C和Java中是对的。

C中的验证

验证的方法很简单,我们知道C是一门高级语言,计算机无法理解,需要用编译器将它编译成汇编语言、机器语言,计算机才能理解。而汇编语言是符号化的机器语言,用一个符号(英文单词、数字)来代表一条机器指令,命令简单,可读性比机器语言好,而且一条指令只做一件事。

所以,我们可以看看编译后的汇编语言是什么样的。再复杂的结构在汇编语言里都会被分解得很小、很细,++a和a++自然也不是问题。

源代码:c = (a++)+a;

c = (++a)+a;

编译后://x86_64 gcc 9.1

mov eax, DWORD PTR [rbp-4]

lea edx, [rax+1]

mov DWORD PTR [rbp-4], edx

mov edx, DWORD PTR [rbp-4]

add eax, edx

mov DWORD PTR [rbp-8], eax

--- 分割线 ---

add DWORD PTR [rbp-4], 1

mov eax, DWORD PTR [rbp-4]

add eax, eax

mov DWORD PTR [rbp-8], eax

编译后的指令是x86_64指令集,具体是什么我们也不用去管它。当然,如果你能看懂的话还是可以自己仔细分析比较好,有助于加深理解。

a++: add eax, edx

++a: add eax, eax

二者的不同在这里可以看出来,a++作为操作数时它的值为加加之前的值,++a则是加加之后的值。

Java中的验证

不同于C, Java作为一种跨平台语言选择用字节码作为中间媒介。对于它,可以用JDK自带的javap.exe反编译工具进行处理。

源代码:public class A{

public static void main(String[] args) {

}

public void a() {

int a = 0;

int c = (a++) + a;

System.out.println(c);

}

public void b() {

int a = 0;

int c = (++a) + a;

System.out.println(c);

}

}

反编译后的字节码:public void a();

Code:

0: iconst_0

1: istore_1

2: iload_1

3: iinc 1, 1

6: iload_1

7: iadd

8: istore_2

9: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;

12: iload_2

13: invokevirtual #7 // Method java/io/PrintStream.println:(I)V

16: return

public void b();

Code:

0: iconst_0

1: istore_1

2: iinc 1, 1

5: iload_1

6: iload_1

7: iadd

8: istore_2

9: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;

12: iload_2

13: invokevirtual #7 // Method java/io/PrintStream.println:(I)V

16: return

上面是反编译后的字节码,没有学习过jvm指令也没有关系,我们只需要将关注点放到两个函数的不同点上。

这几个指令我们需要简单了解下:指令作用iconst_0int型常量值1进栈

istore_1将栈顶int型数值存入第二个局部变量,从0开始计数

iload_1第二个int型局部变量进栈,从0开始计数

iinc 1, 1指定int型变量增加指定值

iadd栈顶两int型数值相加,并且结果进栈

对于函数a中的a++,int c = (a++) + a;2: iload_1//将a的值入栈,此时a还是原来的值

3: iinc 1, 1//将a加1,并将结果写入a

6: iload_1//将a(第二个操作数)的值入栈,此时a已改变

7: iadd//将栈顶两值相加,并且结果进栈

8: istore_2//将栈顶存放的结果写入c

对于函数a中的++a,int c = (++a) + a;2: iinc 1, 1//将a加1,并将结果写入a

5: iload_1//将a的值入栈,此时a已改变

6: iload_1//再次将a(第二个操作数)的值入栈,此时a已改变

7: iadd//将栈顶两值相加,并且结果进栈

8: istore_2//将栈顶存放的结果写入c

a++和++a的区别是:

单独的a++;和++a;没有区别他们都被编译成 iinc 1, 1(可以理解为a=a+1);

当二者涉及到其他运算符时他们的区别才体现出来,a++是将加加之前的值作为操作数入栈,++a是将加加之后的值作为操作数入栈。

本文由 ukuq 创作,采用 知识共享署名4.0 国际许可协议进行许可

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

最后编辑时间为: Aug 6, 2019 at 08:23 pm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值