Если скобка имеет более высокий приоритет, то почему сначала решается оператор приращения?

У меня есть однострочный код,

int a = 10;
a = ++a * ( ++a + 5);

Мой ожидаемый результат был 12 * (11 + 5) = 192, но я получил 187. Столько, сколько я знал оператор приращения внутри () должно быть решено в первую очередь, тогда почему первый решается снаружи?

5 ответов

Решение

Выражения оцениваются слева направо. Скобки (и приоритет) просто выражают группировку, они не выражают порядок оценки.

Так

 11 * (12 + 5)
++a   ++a

равняется

187

Цитата из блога Эрика Липперта:

Оценка арифметического выражения контролируется тремя наборами правил: правилами приоритета, правилами ассоциативности и правилами порядка.

Правила приоритета описывают, как выражение в скобках должно быть заключено в скобки, когда выражение смешивает различные виды операторов.

Правила ассоциативности описывают, как выражение, заключенное в скобки, должно заключаться в скобки, когда выражение имеет группу операторов одного типа.

Порядок правил оценки описывает порядок, в котором оценивается каждый операнд в выражении.

Более высокий приоритет приводит к группированию операндов с оператором и не означает оценку операндов. Это порядок оценки, который определяет последовательность вычисления подвыражений в выражении.


Обновить:

Как я вижу, многие программисты считают это утверждение

a = ++a * ( ++a + 5);  

вызовет неопределенное поведение. Да, он вызовет UB, если нет гарантии порядка вычисления операндов оператора.

Но это не так в контексте языка программирования Java. Он имеет четко определенное поведение в Java (а также в C#). Спецификация языка Java гласит, что:

15.7. Порядок оценки

Язык программирования Java гарантирует, что операнды операторов, по-видимому, будут оцениваться в определенном порядке вычисления, а именно слева направо.

Пример 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.

Но, тем не менее, в спецификации Java четко указано, что нельзя писать такие коды:

Рекомендуется, чтобы код не зависел от этой спецификации. Код обычно более понятен, когда каждое выражение содержит не более одного побочного эффекта в качестве самой внешней операции, и когда код не зависит от того, какое именно исключение возникает в результате вычисления выражений слева направо.

Из этого фрагмента

int a = 10;
a = ++a * ( ++a + 5);

Иногда наиболее простым решением является использование javap для понимания порядка оценки:

 0: bipush 10 // push 10 onto the stack (stack={10})
 2: istore_1  // store 10 into variable 1 (a = 10, stack={})
 3: iinc 1, 1 // increment local variable 1 by 1 (a = 11, stack={})
 6: iload_1   // push integer in local variable 1 onto the stack (a = 11, stack = {11})
 7: iinc 1, 1 // increment local variable 1 by 1 (a = 12, stack={11})
 10: iload_1  // push integer in local variable 1 onto the stack (a = 12, stack = {12, 11})
 11: iconst_5 // load the int value 5 onto the stack (a = 12, stack = {5, 12, 11})
 12: iadd     // add 5 and 12 (a = 12, stack = {17, 11})
 13: imul     // multiply 17 and 11 (a = 12, stack = {})
  1. a увеличивается на 1. (строка 3) // a = 11
  2. a увеличивается на 1. (строка 7) // a = 12
  3. добавлять 5 в a (строка 12) // a = 17
  4. умножать 11 в 17 (строка 13) // a = 187

(10 + 1 + 1 + 5) * 11 = 187

Влияние скобок заключается в контроле того, какие вычисленные значения используются в качестве операндов для последующих операций. Они контролируют последовательность, в которой операции выполняются только в той степени, в которой операция не может быть оценена до тех пор, пока не будут выполнены ее операнды. Рассмотрим выражения:

(a()+b()) * (c()+d())
a() + (b()*c()) + d()

Скобки не должны (и в Java не могут) влиять на порядок вызова a (), b (), c () и d(). Они могут влиять на то, выполняется ли умножение до или после вызова d (), но только в очень редких случаях (например, d () вызывает собственный интерфейс Java, который изменяет режим числового округления, используемый при умножении, способом, которого Java не знает about) будет ли код иметь какой-либо способ узнать или заботиться о том, было ли произведено умножение до или после d().

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

  int a = 10;
  a = ++a * ( ++a + 5);

Вышеуказанные выражения всегда оцениваются слева направо, будь то C или JAVA

в этом случае это решение, подобное этому 11*(12+5), что приводит к 11*17 = 187 // по отношению к Java

но если это мы решаем то же самое выражение относительно языка C

тогда ответ меняется по мере изменения способа оценки

в c происходит первое предварительное увеличение / предварительное уменьшение, поэтому, если в выражении присутствует "N" ни разу, то pre inc / dec присутствует, тогда inc / dec произойдет сначала "N" не раз

затем в каждом вхождении переменной в выражении будет подставляться одно и то же значение, и вычисляется значение выражения, а после этого происходит пост-увеличение / уменьшение

то есть a увеличивается до 11, затем снова 12, поскольку в выражении есть два приращения для a, а затем выражение оценивается как

12*(12+5)=12*17=204 // на языке C

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