最近我遇到了一个问题:赋值运算符链理解。
在回答这个问题,我开始怀疑我自己的加法赋值运算符的行为的理解+=或任何其他operator=(&=,*=,/=等)。
+=
operator=
&=
*=
/=
我的问题是,a下面的表达式中的变量何时更新到位,以便其更改的值在求值过程中反映在表达式的其他位置,其背后的逻辑是什么?请看以下两个表达式:
a
表达式1
a = 1 b = (a += (a += a)) //b = 3 is the result, but if a were updated in place then it should've been 4
表达式2
a = 1 b = (a += a) + (a += a) //b = 6 is the result, but if a is not updated in place then it should've been 4
在第一个表达式中,当计算最里面的表达式时(a += a),似乎不更新的值a,因此结果是3而不是4。
(a += a)
3
4
但是,在第二个表达式中,的值a被更新,因此结果为6。
我们何时应该假定a的值将反映在表达式的其他位置,而我们何时不应该?
记住那a += x真的意味着a = a + x。要理解的关键点是 加法是从左到右求值的, 也就是说,ain a + x是在之前求值的x。
a += x
a = a + x
a + x
x
因此,让我们弄清楚是什么b = (a += (a += a))。首先,我们使用规则a += xmean a = a + x,然后开始以正确的顺序仔细评估表达式:
b = (a += (a += a))
b = (a = a + (a = a + a))
b = (a = 1 + (a = a + a))
1
(a = a + a)
b = (a = 1 + (a = 1 + a))
b = (a = 1 + (a = 1 + 1))
b = (a = 1 + (a = 2))
1 + 1
2
b = (a = 1 + 2)
b = (a = 3)
1 + 2
b = 3
这使我们有了a = 3和b = 3上面所解释的。
a = 3
让我们用另一个表达式尝试b = (a += a) + (a += a):
b = (a += a) + (a += a)
b = (a = a + a) + (a = a + a)
b = (a = 1 + 1) + (a = a + a)
b = (a = 2) + (a = a + a)
b = 2 + (a = a + a)
b = 2 + (a = 2 + 2)
b = 2 + (a = 4)
b = 2 + 4
b = 6
这给我们留下了a = 4和b = 6。可以通过打印输出a以及b使用Java / JavaScript 进行验证(两者在此处具有相同的行为)。
a = 4
b
将这些表达式视为解析树也可能会有所帮助。当我们评估时a + (b + c),LHS a先于RHS评估(b + c)。这是在树结构中编码的:
a + (b + c)
(b + c)
+ / \ a + / \ b c
请注意,我们再也没有括号了-操作的顺序被编码到树形结构中。当我们评估树中的节点时,我们以 固定的顺序 处理该节点的子节点(即,从到左到右+)。例如,当我们处理根节点时+,我们先评估左子树,a然后评估右子树(b + c),而不管右子树是否用括号括起来(因为括号甚至不存在于解析树中)。
+
因此,与您可能被教过算术规则的规则相反,Java / JavaScript 并不 总是首先评估“最嵌套的括号”。
请参阅Java语言规范:
15.7。评估单 Java编程语言保证运算符的操作数似乎按特定的 评估顺序 (即从左到右)进行 评估 。 … 15.7.1。首先评估左手操作数 在评估右侧操作数的任何部分之前,似乎已对二进制运算符的左侧操作数进行了完全评估。 如果该运算符是复合赋值运算符(第15.26.2节),则对左操作数的求值包括记住左操作数表示的变量以及获取并保存该变量的值以用于隐式二进制操作。
Java编程语言保证运算符的操作数似乎按特定的 评估顺序 (即从左到右)进行 评估 。 …
在评估右侧操作数的任何部分之前,似乎已对二进制运算符的左侧操作数进行了完全评估。
如果该运算符是复合赋值运算符(第15.26.2节),则对左操作数的求值包括记住左操作数表示的变量以及获取并保存该变量的值以用于隐式二进制操作。
可以在JLS的链接部分找到与您的问题类似的更多示例,例如:
示例15.7.1-1 首先评估左操作数 在下面的程序中,*运算符具有一个左操作数和一个右操作数,左操作数包含对变量的赋值,右操作数包含对相同变量的引用。由引用产生的值将反映分配首先发生的事实。 class Test1 { public static void main(String[] args) { int i = 2; int j = (i=3) * i; System.out.println(j); } } 该程序产生输出: 9 不允许评估*运算符产生6而不是9。
示例15.7.1-1 首先评估左操作数
在下面的程序中,*运算符具有一个左操作数和一个右操作数,左操作数包含对变量的赋值,右操作数包含对相同变量的引用。由引用产生的值将反映分配首先发生的事实。
class Test1 { public static void main(String[] args) { int i = 2; int j = (i=3) * i; System.out.println(j); } }
该程序产生输出:
9
不允许评估*运算符产生6而不是9。