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

Addition assignment += behavior in expression

最近遇到这个问题:

在回答这个问题时,我开始怀疑自己对加法赋值运算符 += 或任何其他 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) 时,它似乎没有更新 a 的值,因此结果是 3 4.

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

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

它只是使用了操作顺序的变体。

如果您需要提醒操作顺序:

PEMDAS:

P = Parenthesis

E = Exponents

MD = Multiplication/Division

AS = Addition/Subtraction

The rest left to right.

这个变体只是从左到右阅读,但如果您看到括号中的所有内容,并用常量替换它,然后继续。

第一个例子:

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

请记住 a += x 的真正意思是 a = a + x。要理解的关键点是加法是从左到右求值的——也就是说,a + x中的ax之前求值。

那么让我们弄清楚 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 + 12
  • b = (a = 1 + 2) 因为 a 现在是 2
  • b = (a = 3) 因为 1 + 23
  • b = 3 因为 a 现在是 3

如上所述,我们得到 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)a 现在是 2。开始评估正确的术语
  • b = 2 + (a = 2 + 2)
  • b = 2 + (a = 4)
  • b = 2 + 4a 现在是 4
  • b = 6

剩下 a = 4b = 6。这可以通过在 Java/JavaScript 中打印出 ab 来验证(两者在此处具有相同的行为)。


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

   +
  / \
 a   +
    / \
   b   c

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

正因为如此,Java/JavaScript do not 总是首先计算 "most nested parentheses",这与你可能学过的算术规则相反。

Java Language Specification

15.7. Evaluation Order

The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.
...

15.7.1. Evaluate Left-Hand Operand First

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

If the operator is a compound-assignment operator (§15.26.2), then evaluation of the left-hand operand includes both remembering the variable that the left-hand operand denotes and fetching and saving that variable's value for use in the implied binary operation.

在JLS的链接部分可以找到更多与您的问题类似的示例,例如:

Example 15.7.1-1. Left-Hand Operand Is Evaluated First

In the following program, the * operator has a left-hand operand that contains an assignment to a variable and a right-hand operand that contains a reference to the same variable. The value produced by the reference will reflect the fact that the assignment occurred first.

class Test1 {
    public static void main(String[] args) {
        int i = 2;
        int j = (i=3) * i;
        System.out.println(j);
    }
}

This program produces the output:

9

It is not permitted for evaluation of the * operator to produce 6 instead of 9.

以下是需要注意的规则

  • Operator precedence
  • 变量赋值
  • 表达式求值

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