加法赋值 += 表达式中的行为
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
中的a
在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
请注意,我们不再使用任何括号——运算顺序被编码到树结构中。当我们评估树中的节点时,我们以 固定顺序 处理节点的子节点(即,+
从左到右)。例如,当我们处理根节点+
时,我们在右子树(b + c)
之前评估左子树a
,而不管右子树是否被括在括号中(因为括号甚至不存在于解析树中。
正因为如此,Java/JavaScript do not 总是首先计算 "most nested parentheses",这与你可能学过的算术规则相反。
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)
最近遇到这个问题:
在回答这个问题时,我开始怀疑自己对加法赋值运算符 +=
或任何其他 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
中的a
在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)
之前评估左项 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
如上所述,我们得到 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
请注意,我们不再使用任何括号——运算顺序被编码到树结构中。当我们评估树中的节点时,我们以 固定顺序 处理节点的子节点(即,+
从左到右)。例如,当我们处理根节点+
时,我们在右子树(b + c)
之前评估左子树a
,而不管右子树是否被括在括号中(因为括号甚至不存在于解析树中。
正因为如此,Java/JavaScript do not 总是首先计算 "most nested parentheses",这与你可能学过的算术规则相反。
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)