Назначение добавления + = поведение в выражении

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

Отвечая на этот вопрос, я начал сомневаться в собственном понимании поведения оператора присваивания += или любого другого 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

Ответ 1

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


Это может также помочь думать об этих выражениях как деревья разбора. Когда мы оцениваем a + (b + c), LHS a оценивается до RHS (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)
    

Ответ 3

Он просто использует вариацию порядка 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);