加法赋值 += 表达式中的行为

最近我遇到了这个问题:赋值运算符链理解

在回答这个问题时,我开始怀疑自己对加法赋值运算符或任何其他(,,,等)行为的理解。+=operator=&=*=/=

我的问题是,下面表达式中的变量何时更新到位,以便其更改的值在计算期间反映在表达式中的其他地方,其背后的逻辑是什么?请看以下两个表达式: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)a34

但是,在第二个表达式中,更新的值,因此结果为 6。a

我们什么时候应该假设 的值将反映在表达式中的其他地方,什么时候不应该?a


答案 1

请记住,这确实意味着.要理解的关键点是,加法是从左到右评估的 - 也就是说,在 之前评估了 in。a += xa = a + xaa + xx

因此,让我们弄清楚什么是可以做到的。首先,我们使用规则均值 ,然后开始以正确的顺序仔细计算表达式:b = (a += (a += a))a += xa = a + x

  • b = (a = a + (a = a + a))因为意味着a += xa = a + x
  • b = (a = 1 + (a = a + a))因为目前是.请记住,我们在正确的术语之前评估左术语a1a(a = a + a)
  • b = (a = 1 + (a = 1 + a))因为仍然是a1
  • b = (a = 1 + (a = 1 + 1))因为仍然是a1
  • b = (a = 1 + (a = 2))因为是1 + 12
  • b = (a = 1 + 2)因为现在是a2
  • b = (a = 3)因为是1 + 23
  • b = 3因为现在是a3

这给我们留下了上面的推理。a = 3b = 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)现在是2。开始评估正确的术语a
  • b = 2 + (a = 2 + 2)
  • b = 2 + (a = 4)
  • b = 2 + 4现在是a4
  • b = 6

这给我们留下了 和 。这可以通过在Java / JavaScript中打印出来来验证(两者在这里具有相同的行为)。a = 4b = 6ab


将这些表达式视为解析树也可能有所帮助。当我们评估时,LHS在RHS之前被评估。这是在树结构中编码的:a + (b + c)a(b + c)

   +
  / \
 a   +
    / \
   b   c

请注意,我们不再有任何括号 - 操作顺序被编码到树结构中。当我们评估树中的节点时,我们以固定的顺序处理节点的子节点(即,从左到右)。例如,当我们处理根节点时,我们在右子树之前评估左子树,而不管右子树是否用括号括起来(因为括号甚至不存在于解析树中)。++a(b + c)

正因为如此,Java/JavaScript 并不总是首先计算“嵌套最多的括号”,这与你可能被教导的算术规则相反。

请参阅 Java 语言规范

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。


答案 2

以下是需要注意的规则

  • 运算符优先级
  • 变量赋值
  • 表达式计算

    表达式 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)