Почему это входит в бесконечную петлю?
у меня есть следующий код:
public class Tests {
public static void main(String[] args) throws Exception {
int x = 0;
while(x<3) {
x = x++;
System.out.println(x);
}
}
}
мы знаем, что он должен был написать просто x++
или x=x+1
, но на x = x++
он должен сначала атрибут x
к себе, а позже приращение его. Почему x
и далее с 0
как ценность?
--update
вот код:
public class Tests extends java.lang.Object{
public Tests();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_3
4: if_icmpge 22
7: iload_1
8: iinc 1, 1
11: istore_1
12: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
15: iload_1
16: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
19: goto 2
22: return
}
Я прочитаю о - инструкции попытаться понять...
27 ответов
Примечание: Первоначально я разместил код C# в этом ответе для иллюстрации, так как C# позволяет вам пройти int
параметры по ссылке с ref
ключевое слово. Я решил обновить его с помощью фактического юридического кода Java, используя первый MutableInt
класс я нашла на Google для сортировки приближенных, что ref
в C#. Я не могу сказать, помогает или вредит ответ. Я скажу, что я лично не сделал все, что много разработки Java; так что насколько я знаю, могут быть гораздо более идиоматические способы проиллюстрировать этот момент.
возможно, если мы напишем метод, чтобы сделать то же самое, что x++
это сделает это более ясным.
public MutableInt postIncrement(MutableInt x) {
int valueBeforeIncrement = x.intValue();
x.add(1);
return new MutableInt(valueBeforeIncrement);
}
верно? Приращение значения прошел и вернуть исходное значение: это определение оператора постинкремент.
теперь давайте посмотрим, как это поведение играет в вашем примере кода:
MutableInt x = new MutableInt();
x = postIncrement(x);
postIncrement(x)
что делает? Шагом x
да. А потом ... --27-->возвращает x
был до инкремента. Затем это возвращаемое значение присваивается x
.
Итак, приказ значениями x
0, ТО 1, ТО 0.
это может быть еще яснее, если мы перепишем выше:
MutableInt x = new MutableInt(); // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp; // Now x is 0 again.
ваша фиксация на том, что при замене x
в левой части приведенного выше задания с y
"вы видите, что это сначала увеличивает x, а затем приписывает его y" поражает меня как запутанный. Это не x
, который присваивается y
; это значение ранее назначенное на x
. Действительно, инъекции y
делает вещи не отличается от сценария выше; мы просто получили:
MutableInt x = new MutableInt(); // x is 0.
MutableInt y = new MutableInt(); // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp; // y is still 0.
так что ясно:x = x++
эффективно не изменяет значение x. Это всегда заставляет x иметь значения x0, то x0 + 1, а затем x0 снова.
обновление: кстати, дабы вы не сомневались в этом x
когда-либо присваивается 1 "между" операцией приращения и назначением в приведенном выше примере, я собрал быструю демонстрацию, чтобы проиллюстрировать, что это промежуточное значение действительно "существует", хотя оно никогда не будет "видно" в исполняющем потоке.
демо-звонки x = x++;
в цикле пока отдельный поток непрерывно печатает значение x
в консоли.
public class Main {
public static volatile int x = 0;
public static void main(String[] args) {
LoopingThread t = new LoopingThread();
System.out.println("Starting background thread...");
t.start();
while (true) {
x = x++;
}
}
}
class LoopingThread extends Thread {
public @Override void run() {
while (true) {
System.out.println(Main.x);
}
}
}
Ниже приведена выдержка из приведенного выше вывода программы. Обратите внимание на нерегулярное появление как 1s, так и 0s.
Starting background thread... 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 1
x = x++
работает следующим образом:
- сначала вычисляется выражение
x++
. Оценка этого выражения дает значение выражения (которое является значениемx
перед инкрементом) и инкрементыx
. - позже он присваивает значение выражения
x
, заменив приращение стоимости.
таким образом, последовательность событий выглядит следующим образом (это фактический декомпилированный байт-код, созданный javap -c
, С моим комментарии):
8: iload_1 // Remember current value of x in the stack 9: iinc 1, 1 // Increment x (doesn't change the stack) 12: istore_1 // Write remebered value from the stack to x
Для сравнения:x = ++x
:
8: iinc 1, 1 // Increment x 11: iload_1 // Push value of x onto stack 12: istore_1 // Pop value from the stack to x
это происходит потому, что значение x
не увеличивается вообще.
x = x++;
эквивалентно
int temp = x;
x++;
x = temp;
объяснение:
давайте посмотрим на байт-код для этой операции. Рассмотрим пример класса:
class test {
public static void main(String[] args) {
int i=0;
i=i++;
}
}
теперь, запустив дизассемблер класса, мы получаем:
$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iinc 1, 1
6: istore_1
7: return
}
теперь Java VM основанный стог который середины для каждой деятельности, данные будут нажаты на стог и из стека появятся данные для выполнения операции. Существует также другая структура данных, обычно массив для хранения локальных переменных. Локальные переменные задаются идентификаторами, которые являются только индексами массива.
давайте посмотрим на мнемоника на main()
способ:
-
iconst_0
: значение константы0
нажимается на стопку. -
istore_1
: верхний элемент стека извлекается и хранится в этот локальная переменная с индексом1
что isx
. -
iload_1
: значение в местоположение1
это значениеx
которая составляет0
, проталкивается в стек. -
iinc 1, 1
: значение в расположение памяти1
увеличивается на1
. Так чтоx
теперь становится1
. -
istore_1
: значение в верхней части стек хранится в памяти1
. Это0
присваивается кx
перезапись ее увеличенное значение.
отсюда значение x
не изменяется, что приводит к бесконечному циклу.
- Префиксная нотация увеличит переменную перед вычислением выражения.
- Постфиксная нотация будет увеличиваться после оценки выражения.
=
" имеет более низкий приоритет операторов, чем "++
".
так x=x++;
должны оценить следующим образом
-
x
подготовлено для назначения (оценка) -
x
incremented - Предыдущее значение
x
назначенные кx
.
ни один из ответов, где совсем пятно, так что здесь идет:
когда вы пишите int x = x++
, вы не назначаете x
для себя новое значение, вы присваиваете x
быть возвращаемым значением x++
выражение. Что является исходным значением x
, как намекнул в ответ Колина Кокрейна .
для удовольствия проверьте следующий код:
public class Autoincrement {
public static void main(String[] args) {
int x = 0;
System.out.println(x++);
System.out.println(x);
}
}
результат будет
0
1
возвращение значением выражения является исходное значение x
, который равен нулю. Но позже, при чтении значения x
, мы получаем обновленное значение, то есть один.
Это уже было хорошо объяснено другими. Я просто включаю ссылки на соответствующие разделы спецификации Java.
x = x++ - это выражение. Java будет следовать порядок оценки. Сначала он будет оценивать выражение x++, которое увеличит x и установит значение результата на Предыдущее значение x. Тогда это будет присвоить результат выражения к переменной x. В конце x возвращается к своему предыдущему значению.
такое заявление:
x = x++;
оценивает такой:
- пуш
x
в стек; - инкремент
x
; - поп
x
из стека.
таким образом, значение не изменяется. Сравните это с:
x = ++x;
, которая оценивается как:
- инкремент
x
; - пуш
x
в стек; - поп
x
от стек.
ты хочешь:
while (x < 3) {
x++;
System.out.println(x);
}
ответ довольно прост. Это связано с порядком, в котором вещи оцениваются. x++
возвращает значение x
затем с шагом x
.
следовательно, значение выражения x++
is 0
. Таким образом, вы назначаете x=0
каждый раз в цикле. Конечно x++
инкрементирует это значение, но это происходит до назначения.
от http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
операторы инкремента/декремента могут применяется до (префикс) или после (постфикс) операнд. Код результат++; и результат++; оба закончатся в результате увеличивается на единицу. Разница лишь в том, что префикс версия (результат++) оценивает увеличенное значение,а версия postfix (result++) оценивает к исходное значение. Если ты ... просто выполнение простого инкремент / декремент, на самом деле это не так важно, какую версию вы выберете. Но при использовании этого оператора в части большее выражение, то, которое вы выберите может сделать значительно разница.
чтобы проиллюстрировать, попробуйте следующее:
int x = 0;
int y = 0;
y = x++;
System.out.println(x);
System.out.println(y);
который будет печатать 1 и 0.
вы эффективно получаете следующее поведение.
- возьмите значение x (которое равно 0) как "результат" правой стороны
- инкремент значение x (так что x теперь 1)
- назначьте результат правой стороны (который был сохранен как 0) x (x теперь 0)
идея заключается в том, что оператор post-increment (x++) увеличивает эту переменную после возврата ее значения для использования в уравнении, которое она используется в.
Edit: добавление небольшого бита из-за комментария. Рассмотрим это следующим образом.
x = 1; // x == 1
x = x++ * 5;
// First, the right hand side of the equation is evaluated.
==> x = 1 * 5;
// x == 2 at this point, as it "gave" the equation its value of 1
// and then gets incremented by 1 to 2.
==> x = 5;
// And then that RightHandSide value is assigned to
// the LeftHandSide variable, leaving x with the value of 5.
вам действительно не нужен машинный код, чтобы понять, что происходит.
по определениям:
-
оператор присваивания вычисляет правое выражение и сохраняет его во временной переменной.
1.1. Текущее значение x копируется в эту временную переменную
1.2. x теперь увеличивается.
временная переменная затем копируется в левую часть выражение, которое случайно x! Так вот почему старое значение x снова копируется в себя.
Это довольно просто.
Это потому, что он никогда не увеличивается в этом случае. x++
сначала будет использовать его значение, прежде чем увеличивать, как в этом случае, это будет похоже:
x = 0;
но если вы это сделаете ++x;
это позволит увеличить.
значение остается на 0, потому что значение x++
равен 0. В этом случае не имеет значения, если значение x
увеличивается или нет, задание x=0
выполняется. Это перезапишет временное увеличенное значение x
(что было 1 за "очень короткое время").
это работает так, как вы ожидаете от другого. Это разница между префиксом и постфиксом.
int x = 0;
while (x < 3) x = (++x);
подумайте о x++ как вызове функции, который "возвращает" то, что X было до инкремент (вот почему он называется пост-инкрементом).
таким образом, порядок операции:
1: кэшировать значение x перед увеличением
2: приращение x
3. возвращает кэшированное значение (х, прежде чем он был увеличен)
4: возвращаемое значение присваивается x
когда ++ находится на rhs, результат возвращается до увеличения числа. Изменение ++X и все было бы хорошо. Java оптимизировал бы это для выполнения одной операции (назначение x к x), а не приращения.
насколько я вижу, ошибка возникает из-за того, что присвоение переопределяет увеличенное значение со значением до увеличения, т. е. оно отменяет приращение.
в частности, выражение" x++ "имеет значение" x "до приращения, в отличие от "++x", которое имеет значение " x " после приращения.
Если вы заинтересованы в исследовании байт-кода, мы рассмотрим три строки, о которых идет речь:
7: iload_1
8: iinc 1, 1
11: istore_1
7: iload_1 # поместит значение 2-й локальной переменной в стек
8: iinc 1,1 # увеличит 2-ю локальную переменную с 1, Обратите внимание, что она оставляет стек нетронутым!
9: istore_1 # появится в верхней части стека и сохранит значение этого элемента во 2-й локальной переменной
(Вы можете прочитать эффекты каждой инструкции JVM здесь)
вот почему приведенный выше код будет цикл бесконечно, в то время как версия с ++X не будет. Байткод для ++Х должно выглядеть совсем иначе, насколько я помню из компилятора 1.3 Java, который я написал чуть больше года назад, байт-код должен идти примерно так:
iinc 1,1
iload_1
istore_1
таким образом, просто меняя местами две первые строки, изменяет семантику так, чтобы значение, оставленное в верхней части стека, после приращения (т. е. "значение" выражения), было значением после приращения.
x++
=: (x = x + 1) - 1
так:
x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!
, тогда как
++x
=: x = x + 1
так:
x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x
конечно, конечный результат такой же, как просто x++;
или ++x;
на линии сама по себе.
интересно, есть ли что-нибудь в спецификации Java, которое точно определяет поведение этого. (Очевидно, что это утверждение подразумевает, что я слишком ленив, чтобы проверить.)
Примечание из байт-кода тома, ключевые строки 7, 8 и 11. Строка 7 загружает x в стек вычислений. Строка 8 с шагом x. В строке 11 хранится значение из стека обратно в x. В нормальных случаях, когда вы не присваиваете значения себе, я не думаю, что будет какая-либо причина, по которой вы не могли бы загрузите, сохраните,затем увеличьте. Вы получите тот же результат.
как, предположим, у вас был более нормальный случай, когда вы написали что-то вроде: z=(x++)+(y++);
ли он сказал (псевдокод, чтобы пропустить технические детали)
load x
increment x
add y
increment y
store x+y to z
или
load x
add y
store x+y to z
increment x
increment y
не имеет значения. Я думаю, что любая реализация должна быть действительной.
Я был бы очень осторожен в написании кода, который зависит от этого поведения. Это очень выглядит реализация зависит от меня, между трещинами в спецификациях. Единственное время, когда это имело бы значение, - это если бы вы сделали что-то сумасшедшее, как пример здесь, или если бы у вас было два потока и зависели от порядка оценки в выражении.
Я думаю, потому что в Java ++ имеет более высокий приоритет, чем = (цессии)...Не так ли? Посмотри на http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html...
то же самое, если вы пишете x=x+1...+ имеет более высокий приоритет, чем = (цессии)
на x++
выражение x
. The ++
часть влияет на значение после оценка, а не после сообщении. так что x = x++
эффективно переводится в
int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment
это происходит, потому что это сообщение увеличивается. Это означает, что переменная увеличивается после вычисления выражения.
int x = 9;
int y = x++;
x теперь 10, но y-9, значение x до его увеличения.
более подробно см. определение приращения поста.
проверьте код ниже,
int x=0;
int temp=x++;
System.out.println("temp = "+temp);
x = temp;
System.out.println("x = "+x);
выход будет,
temp = 0
x = 0
post increment
означает увеличьте значение и верните значение перед увеличением. Вот почему значение temp
is 0
. Ну и что, если ... --5--> и это в цикле (за исключением первой строки кода). прямо как в вопросе !!!!
оператор инкремента применяется к той же переменной, которой вы назначаете. Это напрашивается на неприятности. Я уверен, что вы можете увидеть значение вашей переменной x при запуске этой программы.... это должно прояснить, почему петля никогда не заканчивается.