我正在为 OCPJP 考试而学习,所以我必须了解 Java 的每一个奇怪的细节。这包括前增量和后增量运算符应用于变量的顺序。以下代码给了我奇怪的结果:
int a = 3;
a = (a++) * (a++);
System.out.println(a); // 12
答案不应该是11吗?或者可能是 13 岁?但不是12!
跟进:
以下代码的结果是什么?
int a = 3;
a += (a++) * (a++);
System.out.println(a);
我正在为 OCPJP 考试而学习,所以我必须了解 Java 的每一个奇怪的细节。这包括前增量和后增量运算符应用于变量的顺序。以下代码给了我奇怪的结果:
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++
a
变成 4 之后。所以你有3 * 4 = 12
.
(a
在 2nd 之后变为 5 a++
,但被丢弃,因为赋值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++);
首先构建语法树:
+=
a
*
a++
a++
评估它从最外面的元素开始并递归下降。对于每个元素,请执行以下操作:
运算符很特殊:它被扩展为类似的+=
东西left = left + right
,但只对表达式求值left
一次。在右侧被评估为一个值之前,左侧仍然被评估为一个值(而不仅仅是一个变量)。
这将导致:
+=
a
。a
的值。3
*
a++
。这将返回 a 的当前值3
并设置a
为4
a++
。这将返回 a 的当前值4
并设置a
为5
+=
。左侧已3
在第三步中评估为 ,右侧为12
。所以它将 3+12=15 分配给a
.a
15。这里要注意的一件事是运算符优先级对评估顺序没有直接影响。它只影响树的形式,从而间接影响顺序。但是在树中的兄弟姐妹中,无论运算符优先级如何,评估始终是从左到右的。
(a++)
是一个后增量,所以表达式的值为 3。
(a++)
是后增量,所以表达式的值现在是 4。
表达式评估从左到右进行。
3 * 4 = 12
每次使用 a++ 时,您都在后递增 a。这意味着第一个 a++ 计算结果为 3,第二个计算结果为 4。3 * 4 = 12。
的情况下 :
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 i.e. 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 = 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)
然后可以使用这些概括重写复合运算符(为简单起见,请忽略返回类型):
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)));
可以使用多个变量拆分:
Operators.set(a, 3)
Operators.set(b, Operators.postIncrement(a))
Operators.set(c, Operators.postIncrement(a))
Operators.set(a, Operators.multiply(b, c))
这样肯定会更冗长,但很明显,您永远不想在一行上执行两个以上的操作。
这是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 压入堆栈,然后从堆栈中弹出 3 并将其存储在 a 中。现在 a = 3 并且堆栈为空。
0 iconst_3
1 istore_1 a
现在它将值从“a”(3) 推入堆栈,然后递增 a(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),将其相乘并将其存储回堆栈(12)。
10 imul
现在“a”等于 5,堆栈等于 12。
最后是从堆栈中弹出 12 并存储在 a 中。
11 istore_1 [a]
多田!
(a++)
表示返回a
和递增,所以
(a++) * (a++)
表示 3 * 4
是 12。表达式从左边开始计算。所以它:
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
只是为了确保在哪里放置++
表达式,该表达式会根据位置更改值。
第一个表达式大家已经解释清楚了,为什么a的值为12。
对于接下来的问题,对于不经意的观察者来说,答案是完全显而易见的:
17
前缀和后前缀增量的优先级高于乘法运算符。因此表达式被评估为 3*4。
如果您在下次使用 a 时使用 a++,则它会加一。所以你的所作所为
a = 3 * (3 + 1) = 3 * 4 = 12