今天遇到了一个问题:
int a=0,c=0;
c = (a++)+a;
你觉的c会是什么值?
我的第一反应就是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_0 | int型常量值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
基本都一样.