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

11t*_*ion 58 javascript java language-lawyer compound-assignment

最近我遇到了这个问题:分配运营商链理解.

在回答这个问题,我开始怀疑我自己的加法赋值运算符的行为的理解+=或任何其他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
Run Code Online (Sandbox Code Playgroud)

表达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
Run Code Online (Sandbox Code Playgroud)

在第一个表达式中,当(a += a)评估最里面的表达式时,似乎它不会更新值a,因此结果3不会出现4.

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

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

pkp*_*pnd 86

记住这a += x真的意味着a = a + x.要理解的关键点是从左到右评估添加 - 也就是说,之前评估了ain .a + xx

那么让我们弄清楚是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.这可以通过打印两者abJava/JavaScript 来验证(这两者都具有相同的行为).


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

   +
  / \
 a   +
    / \
   b   c
Run Code Online (Sandbox Code Playgroud)

请注意,我们不再有任何括号 - 操作顺序被编码到树结构中.当我们评估树中的节点时,我们以固定的顺序(即,从左到右+)处理节点的子节点.例如,当我们处理根节点时+,我们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);
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序产生输出:

9
Run Code Online (Sandbox Code Playgroud)

*运算符的评估不允许生成6而不是9.

  • @ 11thdimension这就是语言的运作方式.假设我们想要评估'a +(b + c)`.无论RHS是否括在括号中,首先评估LHS`a`.然后评估RHS`(b + c)`.这可能违背算术中的"规则",我们首先评估"最里面"的括号.相反,将这些计算视为一个解析树是有帮助的,它将操作顺序中的所有括号和烘焙消除到树结构中. (2认同)

Nik*_*wal 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)
    
    Run Code Online (Sandbox Code Playgroud)

    表达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
    
    Run Code Online (Sandbox Code Playgroud)

    表达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)
    
    Run Code Online (Sandbox Code Playgroud)

  • 对我而言,这使得你的答案不易理解,因为你所说的"1 + = 2"并不明显(因为那不是有效的代码). (6认同)
  • 写"1 + = 2"或类似的东西真的没有意义,因为"1"不是可以赋值的变量. (2认同)