Java: предварительные, постфиксные приоритеты операторов
У меня есть два похожих вопроса о приоритетах операторов в Java.
Первый:
int X = 10;
System.out.println(X++ * ++X * X++); //it prints 1440
Согласно учебник Oracle:
Операторы postfix (expr ++, expr--) имеют более высокий приоритет, чем префикс (++ expr, --expr)
Итак, я полагаю, что порядок оценки:
1) first postfix operator: X++
1.a) X++ "replaced" by 10
1.b) X incremented by one: 10+1=11
At this step it should look like: System.out.println(10 * ++X * X++), X = 11;
2) second POSTfix operator: X++
2.a) X++ "replaced" by 11
2.b) X incremented by one: 11+1=12
At this step it should look like: System.out.println(10 * ++X * 11), X = 12;
3) prefix operator: ++X
3.a) X incremented by one: 12+1=13
3.b) ++X "replaced" by 13
At this step it should look like: System.out.println(10 * 13 * 11), X = 13;
4) evaluating 10*13 = 130, 130*11 = 1430.
Но Java, похоже, игнорирует порядок PRE/POST и помещает их на один уровень. Итак, реальный порядок:
X++ -> ++X -> X++
что заставляет отвечать (10 * 12 * 12) = 1440.
Второй:
Пример из этого вопроса:
int a=1, b=2;
a = b + a++;
Часть принятого ответа:
"К моменту назначения ++
уже увеличил значение a
до 2
(из-за приоритета), поэтому =
перезаписывает это увеличенное значение."
ОК, посмотрим шаг за шагом:
1) replacing "b" with 2
2) replacing "a++" with 1
3) incrementing "a" by 1 -> at this point a==2
4) evaluating 2+1 = 3
5) overwriting incremented value of "a" with 3
Кажется, все в порядке.
Но пусть немного изменится в этом коде (замените "=" на "+ =" )
a += b + a++;
шаги 1-4 должны быть такими же, как указано выше.
поэтому после шага 4 мы имеем что-то вроде этого:
a += 3;
где a==2
И тогда я думаю: ОК, a = 2+3
, поэтому a
должен быть 5
. НО ответ только 4
Я действительно смущен. Я уже провел пару часов, но до сих пор не могу понять, где я ошибаюсь.
P.S. Я знаю, что я не должен использовать этот "стиль" в реальных приложениях. Я просто хочу понять, что не так в моих мыслях.
Ответы
Ответ 1
Путаница проистекает из того факта, что операнды оцениваются слева направо. Это делается сначала, прежде чем будет обращено внимание на приоритет/порядок работы оператора.
Это поведение указано в JLS 15.7.2. Оценка операндов до операции
Итак, X++ * ++X * X++
сначала оценивается как 10 * 12 * 12
, что дает, как вы видели, 1440.
Чтобы убедиться в этом, рассмотрите следующее:
X = 10; System.out.println(X++ * ++X);
X = 10; System.out.println(++X * X++);
Если сначала были выполнены X++
, тогда ++X
second, затем умножение, оба должны печатать одинаковый номер.
Но они этого не делают:
X = 10; System.out.println(X++ * ++X); // 120
X = 10; System.out.println(++X * X++); // 121
Итак, как это имеет смысл? Хорошо, если мы понимаем, что операнды оцениваются слева направо, тогда это имеет смысл.
X = 10; System.out.println(X++ * ++X); // 120 (10 * 12)
X = 10; System.out.println(++X * X++); // 121 (11 * 11)
Первая строка выглядит как
X++ * ++X
10 (X=11) * (X=12) 12
10 * 12 = 120
и второй
++X * X++
(X=11) 11 * 11 (X=12)
11 * 11 = 121
Итак, почему в таблице используются префиксы и постфиксные приращения/декременты?
Верно, что приращение и декремент должны выполняться перед умножением. Но это то, что:
Y = A * B++
// Should be interpreted as
Y = A * (B++)
// and not
Y = (A * B)++
Так же, как
Y = A + B * C
// Should be interpreted as
Y = A + (B * C)
// and not
Y = (A + B) * C
Остается, что порядок оценки операндов происходит слева направо.
Если вы все еще не уверены:
Рассмотрим следующую программу:
class Test
{
public static int a(){ System.out.println("a"); return 2; }
public static int b(){ System.out.println("b"); return 3; }
public static int c(){ System.out.println("c"); return 4; }
public static void main(String[] args)
{
System.out.println(a() + b() * c());
// Lets make it even more explicit
System.out.println(a() + (b() * c()));
}
}
Если аргументы были оценены в то время, когда они были необходимы, на первый пришел b
или c
, а другой следующий и, наконец, a
. Однако выходы программы:
a
b
c
14
a
b
c
14
Потому что, независимо от порядка, который они необходимы и используются в уравнении, они все еще оцениваются слева направо.
Полезное чтение:
Ответ 2
Причина, по которой ее 1440 состоит в том, что
-
x устанавливается в 10 i.e 1-й член FIXED (общее уравнение 10 *)
-
x увеличивается на 1, x = 11 сейчас
-
x предварительно увеличивается на 1 x = 12, а второй член FIXED теперь (общее уравнение 10 * 12 *)
-
теперь x устанавливается в 12 и третий член FIXED (общее уравнение 10 * 12 * 12)
-
x теперь увеличивается, но в этом случае не используется для оценки,
Короче говоря, термин FIXED, когда возникает переменная, в этом случае X
Второй случай:
Я не уверен, но думаю, можно сломать как
который, я думаю, является тем, что происходит.
Ответ 3
Короче говоря,
Приоритет подобен подготовке выражения, которое должно быть рассчитано путем помещения скобок. Затем оценка идет слева направо, рассматривая каждую пару круглых скобок как отдельную операцию.
Например, если i=2
, то i+i++
становится i+(i++)
после приоритета и оценивается как 2+2 = 4
.
Однако i+++i
становится (i++)+i
и оценивается как 2+3 = 5
.
То же самое для i+(i=5)
, которое оценивается как 2+5 = 7
.
Фактически операторы постфикса имеют более высокий приоритет, чем префиксные операторы. Например, i+++++i
становится ((i++)++)+i
после приоритета, который дает ошибку компиляции (второму оператору постфикса нужна переменная для работы, но вместо этого найдено значение!). Если оба оператора постфикса и префикса имели одинаковый приоритет, тогда выражение стало бы (i++)+(++i)
и оценивалось до 2+4 = 6
.
Если вам нужно больше объяснений, вы можете скомпилировать и запустить следующий код и проверить примеры, напечатанные на выходе.
public class TestPrecedence {
public static void main(String[] str) {
int i = 0;
System.out.println("\n");
i = 2; System.out.println("i = " + i + "\n");
i = 2; System.out.println("i++ = " + i++ + "\n");
i = 2; System.out.println("++i = " + ++i + "\n");
i = 2; System.out.println("i++i = (i++)i TestPrecedence.java:8: error: ')' expected\n"+
" i++i\n"+
" ^\n");
i = 2; System.out.println("i+-i = i+(-i) = " + (i+-i) + "\n");
i = 2; System.out.println("++i++ = ++(i++) TestPrecedence.java:12: error: unexpected type\n"+
" ++i++ \n"+
" ^\n"+
" required: variable\n"+
" found: value\n");
i = 2; System.out.println("i+++++i = ((i++)++)+i TestPrecedence.java:17: error: unexpected type\n"+
" i+++++i\n"+
" ^\n"+
" required: variable\n"+
" found: value\n");
i = 2; System.out.println("i++ + ++i = " + (i++ + ++i) + "\n");
i = 2; System.out.println("i+(i=3) = " + (i+(i=3)) + " evaluates left to right\n");
i = 2; System.out.println("i+i++ precedence yields i+(i++) evaluates to 2+2 = " + (i+i++) + "\n");
i = 2; System.out.println("i+++i precedence yields (i++)+i evaluates to 2+3 = " + (i+++i) + "\n");
System.out.println("\n");
}
}
Ответ 4
Первый шаг
1) first postfix operator: X++
1.a) X++ "replaced" by 10
1.b) X incremented by one: 10+1=11
At this step it should look like: System.out.println(10 * ++X * X++), X = 11;
2) prefix operator: ++X
2.a) X "replaced" by 11
2.b) X incremented by one: 11+1=12
At this step it should look like: System.out.println(10 * 12 * X++), X = 12;
3) second POSTfix operator: X++
3.a) X "replaced" by 12
3.b) X incremented by one: 12+1=13
At this step it should look like: System.out.println(10 * 12 * 12), X = 13;
Отпечатает 10 * 12 * 12 = 1440
Это байт-код для первого шага
1. bipush 10
2. istore_0
3. getstatic java/lang/System/out Ljava/io/PrintStream;
4. iload_0
5. iinc 0 1
6. iinc 0 1
7. iload_0
8. imul
9. iload_0
10. iinc 0 1
11. imul
12. invokevirtual java/io/PrintStream/println(I)V
13. return
Это сделает следующее:
1. Push 10 to the stack
2. Pop 10 from the stack to X variable
3. Push X variable value (10) to the stack
5. Increment local variable X (11)
6. Increment local variable X (12)
7. Push X variable value (12) to the stack
8. Multiply top and subtop (10*12) Now Top = 120
9. Push X variable value (12) to the stack
10. Increment local variable X (13)
11. Multiply top and subtop (120*12) Now Top = 1440
Обратите внимание, что последнее приращение (10.) выполняется после нажатия X в стек
Ответ 5
Для второго →
int a=1, b=2;
a += b + a++;
Компилятор преобразует его в
a = a + b + a++;
Затем примените свою логику, и вы найдете причину, по которой это 4.