Ich habe folgenden Code:
public class Tests {
public static void main(String[] args) throws Exception {
int x = 0;
while(x<3) {
x = x++;
System.out.println(x);
}
}
}
Wir wissen, dass er nur x++
oder hätte schreiben sollen x=x+1
, aber x = x++
darauf sollte er sich zuerst selbst zuschreiben x
und später erhöhen. Warum geht es x
weiter 0
als Wert?
--aktualisieren
Hier ist der Bytecode:
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
}
Ich werde über die Anweisungen lesen, um zu versuchen zu verstehen ...
x++
ist nach dem Inkrementieren;x=
ist die Zuordnung des Ergebnisses ; Das Ergebnis vonx++
ist das Originalx
(und es gibt einen Nebeneffekt des Inkrements, aber das ändert nichts am Ergebnis). Dies kann also interpretiert werden alsvar tmp = x; x++; x = tmp;
Antworten:
Hinweis : Ursprünglich habe ich in dieser Antwort zur Veranschaulichung C # -Code angegeben, da Sie mit C #
int
Parameter als Referenz mit demref
Schlüsselwort übergeben können. Ich habe beschlossen, es mit tatsächlichem legalem Java-Code zu aktualisieren, indemMutableInt
ich die erste Klasse verwende, die ich bei Google gefunden habe, um ungefähr zu wissen, wasref
in C # funktioniert. Ich kann nicht wirklich sagen, ob das hilft oder die Antwort verletzt. Ich werde sagen, dass ich persönlich nicht so viel Java-Entwicklung gemacht habe; Soweit ich weiß, könnte es viel idiomatischere Möglichkeiten geben, diesen Punkt zu veranschaulichen.Wenn wir eine Methode aufschreiben, die das Äquivalent zu dem
x++
tut, was sie tut, wird dies möglicherweise klarer.Recht? Inkrementieren Sie den übergebenen Wert und geben Sie den ursprünglichen Wert zurück: Dies ist die Definition des Nachinkrementierungsoperators.
Lassen Sie uns nun sehen, wie sich dieses Verhalten in Ihrem Beispielcode auswirkt:
postIncrement(x)
macht was? Inkrementex
, ja. Und dann gibt , wasx
war vor dem Zuwachs . Dieser Rückgabewert wird dann zugewiesenx
.Die Reihenfolge der zugewiesenen Werte
x
ist also 0, dann 1, dann 0.Dies könnte noch deutlicher werden, wenn wir das Obige neu schreiben:
Ihre Fixierung auf die Tatsache, dass, wenn Sie
x
auf der linken Seite der obigen Zuordnung durchy
"Sie können sehen, dass es zuerst x erhöht und später y zuschreibt", mich verwirrt erscheinen. Es ist nichtx
das, was zugewiesen wirdy
; Dies ist der Wert, der zuvor zugewiesen wurdex
. Das Injizieren unterscheidety
die Dinge nicht von dem obigen Szenario. Wir haben einfach:Es ist also klar:
x = x++
Ändert den Wert von x effektiv nicht. Es bewirkt immer, dass x die Werte x 0 , dann x 0 + 1 und dann wieder x 0 hat.Update : Damit Sie nicht bezweifeln, dass
x
zwischen dem Inkrementierungsvorgang und der Zuweisung im obigen Beispiel jemals 1 "zugewiesen" wird, habe ich eine kurze Demo zusammengestellt, um zu veranschaulichen, dass dieser Zwischenwert tatsächlich "existiert", obwohl dies der Fall ist Niemals auf dem ausführenden Thread "gesehen" werden.Die Demo ruft
x = x++;
in einer Schleife auf, während ein separater Thread den Wert von kontinuierlichx
an die Konsole druckt .Unten finden Sie einen Auszug aus der Ausgabe des obigen Programms. Beachten Sie das unregelmäßige Auftreten von Einsen und Nullen.
quelle
Integer
Klasse verwenden, die Teil der Standardbibliothek ist, und sie hat sogar den Vorteil, dass sieint
fast transparent automatisch von und zu verpackt wird .x
in Ihrem letzten Beispiel deklariert werdenvolatile
, sonst ist es ein undefiniertes Verhalten und das Sehen von1
s ist implementierungsspezifisch.++
könnte vor oder nach der Zuweisung erfolgen. In der Praxis gibt es möglicherweise einen Compiler, der das Gleiche wie Java tut, aber Sie möchten nicht darauf wetten.x = x++
funktioniert folgendermaßen:x++
. Die Auswertung dieses Ausdrucks erzeugt einen Ausdruckswert (der der Wertx
vor dem Inkrement ist) und Inkrementex
.x
und überschreibt den inkrementierten Wert.Die Reihenfolge der Ereignisse sieht also wie folgt aus (es handelt sich um einen tatsächlich dekompilierten Bytecode, wie er von
javap -c
mit meinen Kommentaren erstellt wurde):Zum Vergleich
x = ++x
:quelle
iinc
eine Variable inkrementiert wird, kein Stapelwert inkrementiert wird und kein Wert auf dem Stapel verbleibt (im Gegensatz zu fast jeder anderen arithmetischen Operation). Möglicherweise möchten Sie den von generierten Code++x
zum Vergleich hinzufügen .Dies geschieht, weil der Wert von
x
überhaupt nicht erhöht wird.ist äquivalent zu
Erläuterung:
Schauen wir uns den Bytecode für diese Operation an. Betrachten Sie eine Beispielklasse:
Wenn wir nun den Klassen-Disassembler ausführen, erhalten wir:
Jetzt ist die Java-VM stapelbasiert, was bedeutet, dass für jede Operation die Daten auf den Stapel übertragen werden und aus dem Stapel die Daten herausspringen, um die Operation auszuführen. Es gibt auch eine andere Datenstruktur, normalerweise ein Array zum Speichern der lokalen Variablen. Die lokalen Variablen erhalten IDs, die nur die Indizes für das Array sind.
Schauen wir uns die Mnemonik in
main()
Methode an:iconst_0
: Der konstante Wert0
wird auf den Stapel übertragen.istore_1
: Das oberste Element des Stapels ausgeklappt ist und in dem lokalen Variable mit Index gespeichert ist,1
der ist
x
.iload_1
: Der Wert an der Stelle1
, deren Wertx
ist0
, wird in den Stapel verschoben.iinc 1, 1
: Der Wert am Speicherort1
wird um erhöht1
. So wirdx
jetzt1
.istore_1
: Der Wert oben im Stapel wird im Speicherort gespeichert1
. Das ist0
demx
Überschreiben seines inkrementierten Wertes zugeordnet.Daher
x
ändert sich der Wert von nicht, was zur Endlosschleife führt.quelle
++
), aber die Variable wird später überschrieben.int temp = x; x = x + 1; x = temp;
Es ist besser, in Ihrem Beispiel keine Tautologie zu verwenden."
=
" Hat jedoch eine niedrigere Operatorrangfolge als "++
".Also
x=x++;
sollte wie folgt bewertet werdenx
für den Einsatz vorbereitet (ausgewertet)x
erhöhtx
zugewiesen anx
.quelle
++
hat eine höhere Priorität als=
in C und C ++, aber die Anweisung ist in diesen Sprachen undefiniert.Keine der Antworten war genau richtig, also geht es weiter:
Wenn Sie schreiben
int x = x++
, weisen Sie nichtx
zu, dass es sich um den neuen Wert handelt,x
sondern um den Rückgabewert desx++
Ausdrucks. Was zufällig der ursprüngliche Wert von istx
, wie in Colin Cochranes Antwort angedeutet .Testen Sie zum Spaß den folgenden Code:
Das Ergebnis wird sein
Der Rückgabewert des Ausdrucks ist der Anfangswert von
x
, der Null ist. Aber später, wennx
wir den Wert von lesen , erhalten wir den aktualisierten Wert, das heißt einen.quelle
Es wurde bereits von anderen gut erklärt. Ich füge nur die Links zu den relevanten Java-Spezifikationsabschnitten hinzu.
x = x ++ ist ein Ausdruck. Java folgt der Auswertungsreihenfolge . Zuerst wird der Ausdruck x ++ ausgewertet, der x erhöht und den Ergebniswert auf den vorherigen Wert von x setzt . Dann wird das Ausdrucksergebnis der Variablen x zugewiesen . Am Ende ist x wieder auf dem vorherigen Wert.
quelle
Diese Aussage:
bewertet wie folgt:
x
auf den Stapel;x
;x
vom Stapel.Der Wert bleibt also unverändert. Vergleichen Sie das mit:
welches bewertet als:
x
;x
auf den Stapel;x
vom Stapel.Was Sie wollen ist:
quelle
x++
Code-Snippet.x++;
Sie einfach Ihre Lösung inx=x; x++;
und Sie tun, was Sie behaupten, dass der ursprüngliche Code tut.Die Antwort ist ziemlich einfach. Es hat mit der Reihenfolge zu tun, in der die Dinge ausgewertet werden.
x++
gibt den Wert zurückx
und erhöht sich dannx
.Folglich ist der Wert des Ausdrucks
x++
ist0
. Sie weisen alsox=0
jedes Mal in der Schleife zu. Erhöhtx++
diesen Wert zwar, aber das passiert vor der Zuweisung.quelle
Von http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
Versuchen Sie zur Veranschaulichung Folgendes:
Welches wird 1 und 0 drucken.
quelle
Sie erhalten effektiv das folgende Verhalten.
Die Idee ist, dass der Post-Inkrement-Operator (x ++) diese fragliche Variable inkrementiert, nachdem sie ihren Wert zur Verwendung in der Gleichung zurückgegeben hat, in der sie verwendet wird.
Bearbeiten: Wegen des Kommentars etwas hinzufügen. Betrachten Sie es wie folgt.
quelle
Sie brauchen den Maschinencode nicht wirklich, um zu verstehen, was passiert.
Nach den Definitionen:
Der Zuweisungsoperator wertet den Ausdruck auf der rechten Seite aus und speichert ihn in einer temporären Variablen.
1.1. Der aktuelle Wert von x wird in diese temporäre Variable kopiert
1.2. x wird jetzt erhöht.
Die temporäre Variable wird dann in die linke Seite des Ausdrucks kopiert, die zufällig x ist! Deshalb wird der alte Wert von x wieder in sich selbst kopiert.
Es ist ziemlich einfach.
quelle
Dies liegt daran, dass es in diesem Fall nie erhöht wird.
x++
verwendet zuerst den Wert, bevor er wie in diesem Fall inkrementiert wird:Aber wenn Sie dies tun, wird
++x;
dies zunehmen.quelle
Der Wert bleibt bei 0, da der Wert 0
x++
ist. In diesem Fall spielt es keine Rolle, ob der Wert vonx
erhöht wird oder nicht, die Zuweisungx=0
wird ausgeführt. Dadurch wird der temporär inkrementierte Wert vonx
(der für "sehr kurze Zeit" 1 war) überschrieben .quelle
x++
, nicht für die gesamte Aufgabex=x++;
Dies funktioniert so, wie Sie es von dem anderen erwarten. Es ist der Unterschied zwischen Präfix und Postfix.
quelle
Stellen Sie sich x ++ als einen Funktionsaufruf vor, der zurückgibt, was X vor dem Inkrement war (deshalb wird es als Nachinkrement bezeichnet).
Die Operationsreihenfolge lautet also:
1: Zwischenspeichern des Werts von x vor dem Inkrementieren
2: Inkrementieren von x
3: Zurückgeben des zwischengespeicherten Werts (x vor dem Inkrementieren)
4: Rückgabewert wird x zugewiesen
quelle
Wenn sich ++ auf der rechten Seite befindet, wird das Ergebnis zurückgegeben, bevor die Zahl erhöht wird. Wechseln Sie zu ++ x und es wäre in Ordnung gewesen. Java hätte dies optimiert, um eine einzelne Operation (die Zuweisung von x zu x) anstelle des Inkrements auszuführen.
quelle
Soweit ich sehen kann, tritt der Fehler auf, weil die Zuweisung den inkrementierten Wert mit dem Wert vor der Inkrementierung überschreibt, dh das Inkrement rückgängig macht.
Insbesondere hat der Ausdruck "x ++" vor dem Inkrementieren den Wert "x" im Gegensatz zu "++ x", das nach dem Inkrementieren den Wert "x" hat.
Wenn Sie den Bytecode untersuchen möchten, sehen wir uns die drei fraglichen Zeilen an:
7: iload_1 # Setzt den Wert der 2. lokalen Variablen auf den Stapel
8: iinc 1,1 # erhöht die 2. lokale Variable um 1, beachten Sie, dass der Stapel unberührt bleibt!
9: istore_1 # Legt den Anfang des Stapels ab und speichert den Wert dieses Elements in der 2. lokalen Variablen
(Sie können die Auswirkungen jeder JVM-Anweisung hier lesen ).
Aus diesem Grund wird der obige Code auf unbestimmte Zeit wiederholt, während dies bei der Version mit ++ x nicht der Fall ist. Der Bytecode für ++ x sollte ganz anders aussehen, soweit ich mich an den 1.3 Java-Compiler erinnere, den ich vor etwas mehr als einem Jahr geschrieben habe. Der Bytecode sollte ungefähr so aussehen:
Wenn Sie also nur die beiden ersten Zeilen vertauschen, wird die Semantik so geändert, dass der Wert, der nach dem Inkrement oben im Stapel verbleibt (dh der 'Wert' des Ausdrucks), der Wert nach dem Inkrement ist.
quelle
Damit:
Wohingegen
Damit:
Natürlich ist das Endergebnis das gleiche wie nur
x++;
oder++x;
in einer Zeile für sich.quelle
aufgrund der obigen Aussage erreicht x niemals 3;
quelle
Ich frage mich, ob die Java-Spezifikation irgendetwas enthält, das das Verhalten genau definiert. (Die offensichtliche Folge dieser Aussage ist, dass ich zu faul bin, um sie zu überprüfen.)
Beachten Sie aus Toms Bytecode, dass die Schlüsselzeilen 7, 8 und 11 sind. Zeile 7 lädt x in den Berechnungsstapel. Zeile 8 erhöht x. Zeile 11 speichert den Wert vom Stapel zurück auf x. In normalen Fällen, in denen Sie sich selbst keine Werte zuweisen, gibt es meines Erachtens keinen Grund, warum Sie nicht laden, speichern und dann erhöhen konnten. Sie würden das gleiche Ergebnis erhalten.
Angenommen, Sie hatten einen normaleren Fall, in dem Sie etwas geschrieben haben wie: z = (x ++) + (y ++);
Ob es hieß (Pseudocode zum Überspringen von technischen Details)
oder
sollte irrelevant sein. Jede Implementierung sollte gültig sein, würde ich denken.
Ich wäre äußerst vorsichtig beim Schreiben von Code, der von diesem Verhalten abhängt. Es sieht für mich sehr implementierungsabhängig aus, zwischen den Rissen in den Spezifikationen. Das einzige Mal, wenn es einen Unterschied macht, ist, wenn Sie etwas Verrücktes gemacht haben, wie im Beispiel hier, oder wenn zwei Threads ausgeführt wurden und von der Reihenfolge der Auswertung innerhalb des Ausdrucks abhängig waren.
quelle
Ich denke, weil in Java ++ eine höhere Priorität hat als = (Zuweisung) ... Tut es? Schauen Sie sich http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html an ...
Der gleiche Weg, wenn Sie x = x + 1 ... + schreiben, hat eine höhere Priorität als = (Zuweisung)
quelle
++
hat auch eine höhere Priorität als=
in C und C ++, aber die Anweisung ist undefiniert.Der
x++
Ausdruck ergibtx
. Der++
Teil beeinflusst den Wert nach der Auswertung , nicht nach der Anweisung . sox = x++
wird effektiv übersetzt inquelle
Vor dem Erhöhen des Werts um eins wird der Wert der Variablen zugewiesen.
quelle
Es passiert, weil es inkrementiert ist. Dies bedeutet, dass die Variable inkrementiert wird, nachdem der Ausdruck ausgewertet wurde.
x ist jetzt 10, aber y ist 9, der Wert von x, bevor er erhöht wurde.
Weitere Informationen finden Sie unter Definition des Post-Inkrements .
quelle
x
/y
Beispiel unterscheidet sich vom realen Code und der Unterschied ist relevant. Ihr Link erwähnt nicht einmal Java. Für zwei der Sprachen es nicht erwähnen, ist die Aussage in der Frage nicht definiert.Überprüfen Sie den folgenden Code,
die Ausgabe wird sein,
post increment
bedeutet, den Wert zu erhöhen und den Wert vor dem Inkrement zurückzugeben . Deshalb ist der Werttemp
ist0
. Was ist, wenntemp = i
und dies in einer Schleife ist (mit Ausnahme der ersten Codezeile). genau wie in der frage !!!!quelle
Der Inkrementoperator wird auf dieselbe Variable angewendet, der Sie zuweisen. Das bittet um Ärger. Ich bin sicher, dass Sie den Wert Ihrer x-Variablen sehen können, während Sie dieses Programm ausführen. Dies sollte klarstellen, warum die Schleife niemals endet.
quelle