Mam następujący kod:
public class Tests {
public static void main(String[] args) throws Exception {
int x = 0;
while(x<3) {
x = x++;
System.out.println(x);
}
}
}
Wiemy, że powinien był napisać sprawiedliwie x++
lub x=x+1
, ale na x = x++
nim powinien najpierw przypisać x
sobie, a później zwiększyć. Dlaczego x
kontynuuje się 0
jako wartość?
--aktualizacja
Oto kod bajtowy:
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
}
Przeczytam o instrukcjach, aby spróbować zrozumieć ...
x++
jest przyrostowy;x=
to przypisanie wyniku ; wynik ox++
to oryginalnax
(i nie jest efektem ubocznym przyrostu, ale to nie zmienia wynik), więc to może być interpretowane jakovar tmp = x; x++; x = tmp;
Odpowiedzi:
Uwaga : pierwotnie opublikowałem kod C # w tej odpowiedzi w celach ilustracyjnych, ponieważ C # umożliwia przekazywanie
int
parametrów przez odniesienie doref
słowa kluczowego. Postanowiłem zaktualizować go o rzeczywisty legalny kod Java, używając pierwszejMutableInt
klasy, którą znalazłem w Google, aby w przybliżeniu to, coref
robi w C #. Naprawdę nie wiem, czy to pomaga, czy boli odpowiedź. Powiem, że osobiście nie zrobiłem tyle rozwoju Java; więc o ile wiem, można zilustrować ten idiomatyczny sposób.Być może, jeśli napiszemy metodę równoważną temu, co
x++
to uczyni, będzie to jaśniejsze.Dobrze? Zwiększ przekazaną wartość i zwróć pierwotną wartość: taka jest definicja operatora poinkrementacji.
Zobaczmy teraz, jak wygląda to zachowanie w przykładowym kodzie:
postIncrement(x)
robi co? Przyrostyx
, tak. A potem zwraca to, cox
było przed przyrostem . Ta zwracana wartość jest następnie przypisywana dox
.Tak więc kolejność przypisanych wartości
x
to 0, następnie 1, a następnie 0.To może być jeszcze jaśniejsze, jeśli przepiszemy powyższe:
Twoje przypuszczenie, że po zastąpieniu
x
po lewej stronie powyższego zadania słowemy
„widać, że najpierw zwiększa x, a później przypisuje je y”, wydaje mi się mylące. Niex
przypisuje się tegoy
; jest to wartość poprzednio przypisanax
. Naprawdę, wstrzykiwaniey
nie różni się niczym od powyższego scenariusza; po prostu mamy:Jest więc jasne:
x = x++
skutecznie nie zmienia wartości x. Zawsze powoduje, że x ma wartości x 0 , następnie x 0 + 1, a następnie x 0 ponownie.Aktualizacja : Nawiasem mówiąc, aby nie wątpić, że
x
kiedykolwiek zostanie przypisane do 1 „pomiędzy” operacją przyrostową a przypisaniem w powyższym przykładzie, zrzuciłem szybkie demo, aby zilustrować, że ta wartość pośrednia rzeczywiście „istnieje”, chociaż będzie nigdy nie będzie „widoczny” w wątku wykonującym.Demo wywołuje
x = x++;
pętlę, podczas gdy oddzielny wątek ciągle drukuje wartośćx
na konsoli.Poniżej znajduje się fragment wyników powyższego programu. Zwróć uwagę na nieregularne występowanie zarówno 1, jak i 0.
źródło
Integer
klasy, która jest częścią standardowej biblioteki, a nawet ma tę zaletę, że jest automatycznie zapakowana do i zint
niemal przezroczystego.x
w ostatnim przykładzie należy zadeklarowaćvolatile
, w przeciwnym razie jest to niezdefiniowane zachowanie, a widzenie1
s jest specyficzne dla implementacji.++
można to zrobić przed lub po przypisaniu. Praktycznie rzecz biorąc, może istnieć kompilator, który robi to samo co Java, ale nie chciałbyś się na to stawiać.x = x++
działa w następujący sposób:x++
. Ocena tego wyrażenia tworzy wartość wyrażenia (która jest wartościąx
przed przyrostem) i przyrostyx
.x
, zastępując wartość przyrostową.Tak więc sekwencja zdarzeń wygląda następująco (jest to faktycznie zdekompilowany kod bajtowy wytworzony przez
javap -c
moje komentarze):Dla porównania
x = ++x
:źródło
iinc
inkrementuje zmienną, nie zwiększa wartości stosu ani nie pozostawia wartości na stosie (w przeciwieństwie do prawie każdej innej operacji arytmetycznej). Możesz dodać kod wygenerowany przez++x
dla porównania.Dzieje się tak, ponieważ wartość parametru
x
wcale się nie zwiększa.jest równa
Wyjaśnienie:
Spójrzmy na kod bajtu dla tej operacji. Rozważ przykładową klasę:
Teraz uruchamiając dezasembler klas otrzymujemy:
Teraz Java VM jest oparta na stosie, co oznacza, że dla każdej operacji dane zostaną wypchnięte na stos, a ze stosu dane wyskoczą, aby wykonać operację. Istnieje również inna struktura danych, zazwyczaj tablica do przechowywania zmiennych lokalnych. Zmienne lokalne otrzymują identyfikatory, które są tylko indeksami tablicy.
Spójrzmy na mnemoniki w
main()
metodzie:iconst_0
: Stała wartość0
jest przekazywana na stos.istore_1
: Górny element stosu jest wyskakujący i zapisywany w zmiennej lokalnej o indeksie,1
który jest
x
.iload_1
: Wartość w lokalizacji1
, której wartośćx
jest0
, jest wypychana do stosu.iinc 1, 1
: Wartość w miejscu pamięci1
jest zwiększana o1
. Tak sięx
teraz staje1
.istore_1
: Wartość na górze stosu jest zapisywana w lokalizacji pamięci1
. Jest to0
przypisane dox
zastąpienia jego przyrostowej wartości.Dlatego wartość
x
nie zmienia się, co powoduje powstanie nieskończonej pętli.źródło
++
), ale zmienna zostaje później nadpisana.int temp = x; x = x + 1; x = temp;
lepiej nie używać tautologii w twoim przykładzie.Jednak „
=
” ma niższy priorytet operatora niż „++
”.Więc
x=x++;
powinien oceniać w następujący sposóbx
przygotowane do zadania (ocenione)x
zwiększonyx
przypisana dox
.źródło
++
ma wyższy priorytet niż=
w C i C ++, ale instrukcja jest niezdefiniowana w tych językach.Żadna z odpowiedzi nie była na miejscu, więc oto:
Kiedy piszesz
int x = x++
, nie przypisujeszx
siebie do nowej wartości, przypisujesz,x
że jest to wartość zwracanax++
wyrażenia. Która okazuje się być oryginalną wartościąx
, jak wskazano w odpowiedzi Colina Cochrane'a .Dla zabawy przetestuj następujący kod:
Wynik będzie
Zwracana wartość wyrażenia jest wartością początkową
x
, która wynosi zero. Ale później, czytając wartośćx
, otrzymujemy zaktualizowaną wartość, to jest jedną.źródło
Zostało to już dobrze wyjaśnione przez innych. Podaję tylko linki do odpowiednich sekcji specyfikacji Java.
x = x ++ jest wyrażeniem. Java zastosuje się do kolejności oceny . Najpierw oceni wyrażenie x ++, które zwiększy x i ustawi wartość wynikową na poprzednią wartość x . Następnie przypisze wynik wyrażenia do zmiennej x. Na koniec x powraca do poprzedniej wartości.
źródło
To oświadczenie:
ocenia tak:
x
na stos;x
;x
ze stosu.Zatem wartość pozostaje niezmieniona. Porównaj to z:
który ocenia jako:
x
;x
na stos;x
ze stosu.Co chcesz to:
źródło
x++
fragment kodu.x++;
rozwiązaniex=x; x++;
i robisz to, co twierdzisz, że robi oryginalny kod.Odpowiedź jest dość prosta. Ma to związek z kolejnością oceniania rzeczy.
x++
zwraca wartość,x
a następnie zwiększax
.W związku z tym wartość wyrażenia
x++
wynosi0
. Więc przypisujesz zax=0
każdym razem w pętli. Na pewnox++
zwiększa tę wartość, ale dzieje się to przed przypisaniem.źródło
From http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
Aby to zilustrować, spróbuj wykonać następujące czynności:
Które wypisze 1 i 0.
źródło
Skutecznie uzyskujesz następujące zachowanie.
Chodzi o to, że operator post-inkrementacji (x ++) zwiększa tę zmienną PO zwróceniu jej wartości do wykorzystania w równaniu, w którym jest używana.
Edycja: Trochę dodaje z powodu komentarza. Rozważ to w następujący sposób.
źródło
Tak naprawdę nie potrzebujesz kodu maszynowego, aby zrozumieć, co się dzieje.
Zgodnie z definicjami:
Operator przypisania ocenia wyrażenie po prawej stronie i przechowuje je w zmiennej tymczasowej.
1.1 Bieżąca wartość x jest kopiowana do tej zmiennej tymczasowej
1.2 x jest teraz zwiększane.
Zmienna tymczasowa jest następnie kopiowana do lewej strony wyrażenia, czyli przypadkowo x! Dlatego właśnie stara wartość x jest ponownie kopiowana do siebie.
To jest dość proste.
źródło
Jest tak, ponieważ w tym przypadku nigdy się nie zwiększa.
x++
użyje jego wartości najpierw przed inkrementacją, jak w tym przypadku będzie to wyglądało tak:Ale jeśli to zrobisz
++x;
, wzrośnie.źródło
Wartość pozostaje na 0, ponieważ wartość
x++
wynosi 0. W tym przypadku nie ma znaczenia, czy wartość parametrux
jest zwiększona, czy nie, przypisaniex=0
jest wykonywane. Spowoduje to zastąpienie tymczasowej wartości przyrostowejx
(która wynosiła 1 przez „bardzo krótki czas”).źródło
x++
, nie dla całego zadaniax=x++;
Działa to tak, jak oczekujesz tego drugiego. Jest to różnica między przedrostkiem i postfiksem.
źródło
Pomyśl o x ++ jako wywołaniu funkcji, która „zwraca” to, co X był wcześniej inkrementem (dlatego nazywa się to inkrementacją post-inkrement).
Zatem kolejność operacji jest następująca:
1: buforuj wartość x przed inkrementacją
2: inkrement x
3: zwróć buforowaną wartość (x przed jej inkrementacją)
4: zwracana wartość jest przypisywana do x
źródło
Gdy ++ jest na rhs, wynik jest zwracany przed zwiększeniem liczby. Zmień na ++ x i byłoby dobrze. Java zoptymalizowałoby to, aby wykonać pojedynczą operację (przypisanie x do x) zamiast przyrostu.
źródło
O ile widzę, błąd występuje z powodu przypisania zastępującego wartość przyrostową wartością przed przyrostem, tzn. Cofa przyrost.
W szczególności wyrażenie „x ++” ma wartość „x” przed inkrementem, w przeciwieństwie do „++ x”, który ma wartość „x” po inkrementacji.
Jeśli jesteś zainteresowany badaniem kodu bajtowego, przyjrzymy się trzem pytaniom:
7:
iload_1 # Umieści wartość 2. zmiennej lokalnej na stosie 8: iinc 1,1 # zwiększy 2. zmienną lokalną o 1, zauważ, że pozostawia stos nietknięty!
9: istore_1 # Wyskoczy na górze stosu i zapisze wartość tego elementu w drugiej zmiennej lokalnej
(Możesz przeczytać efekty każdej instrukcji JVM tutaj )
Dlatego powyższy kod będzie się zapętlał w nieskończoność, podczas gdy wersja z ++ x nie. Kod bajtowy dla ++ x powinien wyglądać zupełnie inaczej, o ile pamiętam z kompilatora Java 1.3 napisanego nieco ponad rok temu, kod bajtowy powinien wyglądać mniej więcej tak:
Zatem zamiana dwóch pierwszych wierszy zmienia semantykę, tak że wartość pozostawiona na górze stosu po inkrementie (tj. „Wartość” wyrażenia) jest wartością po inkrementie.
źródło
Więc:
Natomiast
Więc:
Oczywiście wynik końcowy jest taki sam jak sam
x++;
lub++x;
na linii.źródło
z powodu powyższego wyrażenia x nigdy nie osiąga 3;
źródło
Zastanawiam się, czy jest coś w specyfikacji Java, która precyzyjnie określa zachowanie tego. (Oczywiście implikacją tego stwierdzenia jest to, że jestem zbyt leniwy, by to sprawdzić).
Uwaga z kodu bajtowego Toma kluczowe linie to 7, 8 i 11. Linia 7 ładuje x do stosu obliczeniowego. Przyrosty linii 8 x. Wiersz 11 przechowuje wartość ze stosu z powrotem do x. W normalnych przypadkach, gdy nie przypisujesz sobie wartości z powrotem, nie sądzę, aby istniał jakiś powód, dla którego nie można załadować, zapisać, a następnie zwiększyć. Otrzymasz ten sam wynik.
Załóżmy, że miałeś bardziej normalny przypadek, w którym napisałeś coś takiego: z = (x ++) + (y ++);
Czy to powiedział (pseudokod, aby pominąć dane techniczne)
lub
powinno być nieistotne. Wydaje mi się, że każde wdrożenie powinno być ważne.
Byłbym bardzo ostrożny przy pisaniu kodu, który zależy od tego zachowania. Wygląda mi na bardzo zależne od implementacji, między pęknięciami w specyfikacjach. Jedyne, co zrobiłoby różnicę to to, że zrobiłeś coś szalonego, na przykład tutaj, lub jeśli działały dwa wątki i były zależne od kolejności oceny w wyrażeniu.
źródło
Myślę, że ponieważ w Javie ++ ma wyższy priorytet niż = (przypisanie) ... Czy tak? Spójrz na http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ...
Ten sam sposób, jeśli piszesz x = x + 1 ... + ma wyższy priorytet niż = (przypisanie)
źródło
++
ma wyższy priorytet niż=
w C i C ++, ale instrukcja jest niezdefiniowana.x++
Wyrażenie tox
.++
Część wpływu na wartość po ocenie , nie po oświadczeniu . więcx = x++
jest skutecznie przetłumaczone naźródło
Przed zwiększeniem wartości o jeden wartość jest przypisywana do zmiennej.
źródło
Dzieje się tak, ponieważ jest zwiększany. Oznacza to, że zmienna jest zwiększana po ocenie wyrażenia.
x wynosi teraz 10, ale y wynosi 9, wartość x przed zwiększeniem.
Zobacz więcej w Definicja przyrostu postu .
źródło
x
/y
przykład różni się od prawdziwego kodu, a różnica jest istotna. Twój link nawet nie wspomina o Javie. W przypadku dwóch języków, o których wspomina, stwierdzenie w pytaniu jest niezdefiniowane.Sprawdź poniższy kod,
wyjście będzie,
post increment
oznacza zwiększenie wartości i zwrócenie wartości przed przyrostem . Właśnie dlatego wartośćtemp
jest0
. Co jeślitemp = i
i to jest w pętli (z wyjątkiem pierwszego wiersza kodu). tak jak w pytaniu !!!!źródło
Operator przyrostu jest stosowany do tej samej zmiennej, do której przypisujesz. To prosi o kłopoty. Jestem pewien, że możesz zobaczyć wartość swojej zmiennej x podczas uruchamiania tego programu ... to powinno wyjaśnić, dlaczego pętla nigdy się nie kończy.
źródło