Добавление присваивания += поведение в выражении

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

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

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)) так как 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) ЛХС 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.

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

  • Приоритет оператора
  • Переменная присваивание
  • оценка выражения

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

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

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

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

Другие вопросы по тегам