我正在准备 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++
后,a
变成5,但它被覆盖掉了,因为赋值操作a=
会覆盖它)
您的陈述:
a += (a++) * (a++);
等价于下列任何一个:
a = a*a + 2*a
a = a*(a+2)
a += a*(a+1)
可以使用其中任意一个代替。
3 * 3
,然后赋值给a
,然后a
会增加两次--> 11
。或者计算3 * 4
,结果增加一次--> 13
。虽然这没有多少意义,但是如果你看一些其他的前缀/后缀递增问题,你会感到“任何事情都有可能”... - Tim Pietzckera++
的意思是 '取得a的数值, 然后将a增加1'。因此,当你运行以下代码时:
(a++) * (a++)
首先执行第一个a++
,得到值3。然后a
加1。接着执行第二个a++
,a
产生值4,然后再次加1(但现在这没关系了)。
因此,最终结果为
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,第二个a++的值为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)
然后可以使用这些概括来重写复合运算符(为了简单起见,请忽略返回类型):
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))
这种写法确实更冗长一些,但很快就会明显地意识到,你永远不想在单行上执行超过两个操作。
在以下情况下:
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
这里需要注意的主要点是,编译器在这种情况下是从左往右解析的,对于后置递增运算符,计算时使用递增前的值,随着从左到右的移动,使用的是递增后的值。
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
2 iload_1 [a]
3 iinc 1 1 [a]
现在,“a”等于“4”,堆栈等于{3}。
然后它再次加载“a”(4),将其推入堆栈并递增“a”。
6 iload_1 [a]
7 iinc 1 1 [a]
10 imul
11 istore_1 [a]
太棒了!
(a++)
表示返回 a
并且增加,因此 (a++) * (a++)
的意思是 3 * 4。
a = 3 (->4) * 4 (->5) // = 3*4 = 12
。 - Smamatti