a =(a ++)*(a ++)在Java中给出奇怪的结果
我正在学习OCPJP考试,所以我必须了解Java的每一个奇怪的细节。 这包括前后增量操作符应用于variables的顺序。 下面的代码给了我奇怪的结果:
int a = 3; a = (a++) * (a++); System.out.println(a); // 12
答案不应该是11吗? 或者,也许13? 但不是12!
跟进:
以下代码的结果是什么?
int a = 3; a += (a++) * (a++); System.out.println(a);
第a++
之后变成4.所以你有3 * 4 = 12
。
( a
在第二个a++
之后变成5,但被丢弃,因为赋值a =
覆盖它)
您的声明:
a += (a++) * (a++);
相当于其中的任何一个:
a = a*a + 2*a a = a*(a+2) a += a*(a+1)
改用其中的任何一种。
a++
意味着“a的值,然后a增加1”。 所以当你跑步
(a++) * (a++)
首先对a++
进行评估,然后生成值3.然后将a++
加1,然后对第二个a++
进行评估。 a
产生4的值,然后再次递增(但是现在不重要)
所以这变成了
a = 3 * 4
等于12。
int a = 3; a += (a++) * (a++);
首先构build语法树:
+= a * a++ a++
评估它从recursion的最外层元素和下降开始。 对于每个元素做:
- 从左到右评估孩子
- 评估元素本身
+=
运算符是特殊的:它被扩展为left = left + right
,但只评估expression式left
一次。 在右边被评估为一个值之前,仍然左边被评估为一个值(而不仅仅是一个variables)。
这导致:
- 开始评估
+=
- 评估赋值给variables
a
左侧。 - 评估variables
a
为将在添加中使用的值3
。 - 开始评估
*
- 评估第一个
a++
。 这将返回3的当前值并将a
设置为4
- 评估第二
a++
。 这将返回4的当前值并将a
设置为5
- 计算产品:3 * 4 = 12
- 执行
+=
。 第三步评估左侧为3
,右侧为12
。 所以它将3 + 12 = 15分配给a
。 - a的最终值是15。
这里要注意的一点是,运算符优先级对评估顺序没有直接的影响。 它只影响树的forms,从而间接地影响秩序。 但是在树中的兄弟姐妹中,评估总是从左到右,而不pipe操作符的优先级如何。
(a++)
是后增量,所以expression式的值是3。
(a++)
是后增量,所以expression式的值现在是4。
expression评估从左到右发生。
3 * 4 = 12
每次你使用++时,你都是后增加一个。 这意味着第一个a ++评估为3,第二个评估为4.3 * 4 = 12。
对于运营商如何运作普遍缺乏了解。 老实说,每个操作员都是语法糖。
所有你需要做的就是了解每个操作员背后究竟发生了什么。 假设如下:
a = b -> Operators.set(a, b) //don't forget this returns b a + b -> Operators.add(a, b) a - b -> Operators.subtract(a, b) a * b -> Operators.multiply(a, b) a / b -> Operators.divide(a, b)
复合运算符可以使用这些泛化来重写(为简单起见,请忽略返回types):
Operators.addTo(a, b) { //a += b return Operators.set(a, Operators.add(a, b)); } Operators.preIncrement(a) { //++a return Operators.addTo(a, 1); } Operators.postIncrement(a) { //a++ Operators.set(b, a); Operators.addTo(a, 1); return b; }
你可以重写你的例子:
int a = 3; a = (a++) * (a++);
如
Operators.set(a, 3) Operators.set(a, Operators.multiply(Operators.postIncrement(a), Operators.postIncrement(a)));
哪些可以使用多个variables分割出来:
Operators.set(a, 3) Operators.set(b, Operators.postIncrement(a)) Operators.set(c, Operators.postIncrement(a)) Operators.set(a, Operators.multiply(b, c))
这当然更加冗长,但是显而易见,你永远不想在一行上执行两个以上的操作。
的情况下 :
int a = 3; a = (a++) * (a++); a = 3 * a++; now a is 4 because of post increment a = 3 * 4; now a is 5 because of second post increment a = 12; value of 5 is overwritten with 3*4 ie 12
因此我们得到12的输出。
的情况下 :
a += (a++) * (a++); a = a + (a++) * (a++); a = 3 + (a++) * (a++); // a is 3 a = 3 + 3 * (a++); //a is 4 a = 3 + 3 * 4; //a is 5 a = 15
这里需要注意的要点是,在这种情况下,编译器从左到右求解,在后递增的情况下,在计算中使用增量之前的值,并且从左向右移动递增值。
(a++)
意味着返回a
并增加,所以(a++) * (a++)
意味着3 * 4
这里是java代码:
int a = 3; a = (a++)*(a++);
这是字节码:
0 iconst_3 1 istore_1 [a] 2 iload_1 [a] 3 iinc 1 1 [a] 6 iload_1 [a] 7 iinc 1 1 [a] 10 imul 11 istore_1 [a]
这是发生了什么事情:
将3推入堆栈,然后从堆栈中popup3并将其存储在堆栈中。 现在a = 3,堆栈是空的。
0 iconst_3 1 istore_1 a
现在它将“a”(3)的值推入堆栈,然后递增(3 – > 4)。
2 iload_1 [a] 3 iinc 1 1 [a]
所以现在“a”等于“4”堆栈等于{3}。
然后再次加载“a”(4),推入堆栈并递增“a”。
6 iload_1 [a] 7 iinc 1 1 [a]
现在“a”等于5,堆栈等于{4,3}
所以它最终从堆栈(4和3)中popup两个值,并将其重新存储到堆栈(12)中。
10 imul
现在“a”等于5,堆栈等于12。
最后是从堆栈中popup12个,并存储在一个。
11 istore_1 [a]
TADA!
它是12.expression式开始从左边评估。 所以它确实:
a = (3++) * (4++);
一旦第一部分(3 ++)被评估,a就是4,所以在下一部分,它做a = 3 * 4 = 12。注意最后一个后增加(4 ++)被执行,但是没有任何影响因为在此之后a被赋值12。
例1
int a = 3; a = (++a) * (a++); System.out.println(a); // 16
例2
int a = 3; a = (++a) * (++a); System.out.println(a); // 20
只是为了确定在哪里把++
expression式改变基于位置的值。
每个人都清楚地解释了第一个expression式,为什么a的值是12。
对于随后的问题,对于偶然的观察者来说,答案是完全明显的:
17
前缀和后缀前缀增量比乘法运算符具有更高的优先级。 因此expression式被评估为3 * 4。
如果您在下次使用时使用++,则会加1。 所以你这样做
a = 3 * (3 + 1) = 3 * 4 = 12