最近我遇到了这个问题:Assignment operator chain understanding。
在回答这个问题时,我开始怀疑自己对添加赋值运算符+=
或任何其他operator=
(&=
,*=
,{{1}的行为的理解等等。)。
我的问题是,下面的表达式中的变量/=
何时更新到位,以便在评估期间其更改的值反映在表达式的其他位置,以及它背后的逻辑是什么?请看下面两个表达式:
表达式1
a
表达式2
a = 1
b = (a += (a += a))
//b = 3 is the result, but if a were updated in place then it should've been 4
在第一个表达式中,当评估最里面的表达式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
的值已更新,因此结果为6.
我们何时应该假设a
的值会反映在表达式的其他位置,何时不应该?
答案 0 :(得分:85)
请记住,a += x
确实意味着a = a + x
。要理解的关键点是添加从左到右进行评估 - 即a
中的a + x
在x
之前进行评估。
让我们弄清楚b = (a += (a += a))
的作用。首先,我们使用规则a += x
表示a = a + x
,然后我们开始以正确的顺序仔细评估表达式:
b = (a = a + (a = a + a))
因为a += x
表示a = a + x
b = (a = 1 + (a = a + a))
因为a
目前是1
。请记住,我们会在正确的术语a
(a = a + a)
b = (a = 1 + (a = 1 + a))
因为a
仍然是1
b = (a = 1 + (a = 1 + 1))
因为a
仍然是1
b = (a = 1 + (a = 2))
因为1 + 1
是2
b = (a = 1 + 2)
因为a
现在是2
b = (a = 3)
因为1 + 2
是3
b = 3
因为a
现在是3
这使我们得到a = 3
和b = 3
,如上所述。
让我们用另一个表达式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)
和a
现在是2.开始评估正确的术语b = 2 + (a = 2 + 2)
b = 2 + (a = 4)
b = 2 + 4
和a
现在是4
b = 6
这为我们留下了a = 4
和b = 6
。这可以通过在Java / JavaScript中打印出a
和b
来验证(两者在这里都有相同的行为)。
将这些表达式视为解析树也可能有所帮助。当我们评估a + (b + c)
时,LHS a
会在RHS (b + c)
之前进行评估。这是在树结构中编码的:
+
/ \
a +
/ \
b c
请注意,我们不再有任何括号 - 操作顺序被编码到树结构中。当我们评估树中的节点时,我们以固定顺序(即从+
从左到右)处理节点的子节点。例如,当我们处理根节点+
时,我们在右子树a
之前评估左子树(b + c)
,无论右子树是否括在括号中(因为括号中的括号甚至不存在。)
正因为如此,Java / JavaScript 不总是首先评估“最嵌套的括号”,而不是你可能已经教过算术的规则。
请参阅Java Language Specification:
15.7。评估订单
Java编程语言保证操作符的操作数似乎在特定的评估顺序中评估,即从左到右。
...15.7.1。首先评估左手操作数
在评估右侧操作数的任何部分之前,二元运算符的左侧操作数似乎已完全评估。
如果运算符是复合赋值运算符(第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。
答案 1 :(得分:7)
以下是需要注意的规则
表达评估
表达式1
a = 1
b = (a += (a += a))
b = (1 += (a += a)) // a = 1
b = (1 += (1 += a)) // a = 1
b = (1 += (1 += 1)) // a = 1
b = (1 += (2)) // a = 2 (here assignment is -> a = 1 + 1)
b = (3) // a = 3 (here assignment is -> a = 1 + 2)
表达式2
a = 1
b = (a += a) + (a += a)
b = (1 += a) + (a += a) // a = 1
b = (1 += 1) + (a += a) // a = 1
b = (2) + (a += a) // a = 2 (here assignment is -> a = 1 + 1)
b = (2) + (2 += a) // a = 2 (here here a = 2)
b = (2) + (2 += 2) // a = 2
b = (2) + (4) // a = 4 (here assignment is -> a = 2 + 2)
b = 6 // a = 4
表达3
a = 1
b = a += a += a += a += a
b = 1 += 1 += 1 += 1 += 1 // a = 1
b = 1 += 1 += 1 += 2 // a = 2 (here assignment is -> a = 1 + 1)
b = 1 += 1 += 3 // a = 3 (here assignment is -> a = 1 + 2)
b = 1 += 4 // a = 4 (here assignment is -> a = 1 + 3)
b = 5 // a = 5 (here assignment is -> a = 1 + 4)
答案 2 :(得分:1)
它只使用了操作顺序的变体。
如果您需要提醒操作顺序:
PEMDAS:
P =括号
E =指数
MD =乘法/除法
AS =加法/减法
其余的从左到右。
这种变化只是从左到右阅读,但是如果你看到括号内的所有内容,并用常量替换它,那么继续前进。
第一个例子:
var b = (a+=(a+=a))
var b = (1+=(1+=1))
var b = (1+=2)
var b = 3
第二个例子:
var b = (a+=a)+(a+=a)
var b = (1+=1)+(a+=a)
var b = 2 + (2+=2)
var b = 2 + 4
var b = 6
var a = 1
var b = (a += (a += a))
console.log(b);
a = 1
b = (a += a) + (a += a)
console.log(b);
a = 1
b = a += a += a;
console.log(b);
最后一个b = a += a += a
,因为没有括号,它会自动变为b = 1 += 1 += 1
b = 3