Addition assignment += поведение в выражении

недавно я наткнулся на этот вопрос: понимание цепочки операторов назначения.

отвечая на этот вопрос, я начал сомневаться в своем понимании поведения оператора сложения задание += или любое другое operator= (&=, *=, /=, etc.).

мой вопрос в том, когда переменная 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значение будет отражаться в других местах в выражении, и когда мы не должны?

3 ответов


помните, что 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)), потому что и 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 is 2
  • b = (a = 1 + 2), потому что a теперь 2
  • b = (a = 3), потому что 1 + 2 is 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. Это можно проверить, распечатав оба a и b в Java / JavaScript (оба имеют одинаковое поведение здесь).


это также может помочь думать об этих выражениях как о деревьях разбора. Когда мы оцениваем a + (b + c), LHS a оценивается перед RHS (b + c). Это закодировано в древовидной структуре:

   +
  / \
 a   +
    / \
   b   c

обратите внимание, что у нас больше нет круглых скобок-порядок операций закодирован в древовидную структуру. Когда мы оцениваем узлы в дереве, мы обрабатываем дочерние элементы узла в установленном порядке (т. е. слева направо на +). Например, когда мы обрабатываем корневой узел +, мы оцениваем левое поддерево a перед правым поддеревом (b + c), независимо от того, правое поддерево заключено в скобки или нет (поскольку скобки даже не присутствуют в дереве синтаксического анализа).

из-за этого Java/JavaScript do не всегда оценивайте "самые вложенные круглые скобки" сначала, в отличие от правил, которым вас могли бы научить для арифметики.

посмотреть Спецификация Языка 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.


Ниже приведены правила, о которых необходимо позаботиться

  • приоритет операторов
  • переменной задание
  • выражения

    выражение 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)
    

он просто использует изменение порядка операций.

Если вам нужно напоминание о порядке ops:

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