Was ist der Unterschied zwischen i ++ und ++ i?


204

Ich habe sie gesehen , sowohl in zahlreichen Stücken von C # -Code verwendet wird, und ich würde gerne wissen , wann zu verwenden , i++oder ++i( ieine Zahl Variable wie int, float, doubleusw.). Wer weiß das?


13
Außer wenn es überhaupt keinen Unterschied macht, sollten Sie keines von beiden verwenden, da dies nur dazu führt, dass die Leute dieselbe Frage stellen, die Sie jetzt stellen. "Lass mich nicht nachdenken" gilt sowohl für Code als auch für Design.
Instance Hunter


2
@Dlaor: Hast du überhaupt die Links in meinem Kommentar gelesen? Die erste handelt von C # und die zweite ist sprachunabhängig mit einer akzeptierten Antwort, die sich auf C # konzentriert.
Gnovice

7
@gnovice, der erste fragt nach dem Leistungsunterschied, während ich nach dem tatsächlichen Code-Unterschied gefragt habe, der zweite nach dem Unterschied in einer Schleife, während ich nach dem Unterschied im Allgemeinen gefragt habe, und der dritte nach C ++.
Dlaor

1
Ich glaube, dies ist kein Duplikat des Unterschieds zwischen i ++ und ++ i in einer Schleife? - Wie Dloar in seinem obigen Kommentar sagt , fragt die andere Frage speziell nach der Verwendung innerhalb einer Schleife.
Chue x

Antworten:


201

Seltsamerweise sehen die beiden anderen Antworten nicht so aus, und es lohnt sich auf jeden Fall zu sagen:


i++bedeutet "Sag mir den Wert von i, dann inkrementiere"

++ibedeutet "inkrementieren i, dann sag mir den Wert"


Sie sind Pre-Inkrement- und Post-Inkrement-Operatoren. In beiden Fällen wird die Variable inkrementiert . Wenn Sie jedoch den Wert beider Ausdrücke in genau denselben Fällen verwenden, unterscheidet sich das Ergebnis.


11
Dies scheint im Widerspruch zu dem zu stehen, was Eric sagt
Evan Carroll

65
Keine der Aussagen ist richtig. Betrachten Sie Ihre erste Aussage. i ++ bedeutet eigentlich "Speichern Sie den Wert, erhöhen Sie ihn, speichern Sie ihn in i und teilen Sie mir dann den ursprünglich gespeicherten Wert mit". Das heißt, das Erzählen erfolgt nach dem Inkrementieren, nicht vorher, wie Sie es angegeben haben. Betrachten Sie die zweite Aussage. i ++ bedeutet eigentlich "Speichern Sie den Wert, erhöhen Sie ihn, speichern Sie ihn in i und teilen Sie mir den inkrementierten Wert mit". Die Art und Weise, wie Sie sagten, macht es unklar, ob der Wert der Wert von i oder der Wert ist, der i zugewiesen wurde; Sie könnten anders sein .
Eric Lippert

8
@ Eric, es scheint mir sogar mit Ihrer Antwort, die zweite Aussage ist kategorisch korrekt. Obwohl er ein paar Schritte ausschließt.
Evan Carroll

20
Sicher, meistens liefern die schlampigen, falschen Arten der Beschreibung der Betriebssemantik die gleichen Ergebnisse wie die genaue und korrekte Beschreibung. Erstens sehe ich keinen zwingenden Wert darin, durch falsche Argumentation die richtigen Antworten zu erhalten, und zweitens habe ich Produktionscode gesehen, der genau so etwas falsch macht. Ich bekomme wahrscheinlich ein halbes Dutzend Fragen pro Jahr von echten Programmierern darüber, warum ein bestimmter Ausdruck voller Inkremente und Dekremente und Array-Dereferenzen nicht so funktioniert, wie sie angenommen haben.
Eric Lippert

12
@ Supercat: Die Diskussion ist über C #, nicht C.
Thanatos

428

Die typische Antwort auf diese Frage, die leider bereits hier veröffentlicht wurde, lautet, dass einer das Inkrement "vor" verbleibenden Operationen und der andere das Inkrement "nach" verbleibenden Operationen ausführt. Obwohl dies die Idee intuitiv vermittelt, ist diese Aussage auf den ersten Blick völlig falsch . Die zeitliche Abfolge von Ereignissen ist in C # sehr genau definiert, und es ist ausdrücklich nicht der Fall, dass die Präfix- (++ var) und Postfix- (var ++) Versionen von ++ die Dinge in einer anderen Reihenfolge als andere Operationen ausführen.

Es ist nicht überraschend, dass Sie viele falsche Antworten auf diese Frage sehen. Sehr viele "Teach yourself C #" - Bücher verstehen es auch falsch. Auch die Art und Weise, wie C # es macht, unterscheidet sich von der Art und Weise, wie C es macht. Viele Leute argumentieren, dass C # und C dieselbe Sprache sind; Sie sind nicht. Das Design der Inkrement- und Dekrementoperatoren in C # vermeidet meiner Meinung nach die Designfehler dieser Operatoren in C.

Es gibt zwei Fragen, die beantwortet werden müssen, um festzustellen, wie genau Präfix und Postfix ++ in C # funktionieren. Die erste Frage ist was ist das Ergebnis? und die zweite Frage ist, wann die Nebenwirkung des Inkrements auftritt?

Es ist nicht offensichtlich, wie die Antwort auf eine der beiden Fragen lautet, aber es ist tatsächlich recht einfach, sobald Sie sie sehen. Lassen Sie mich genau erläutern, was x ++ und ++ x für eine Variable x tun.

Für das Präfixformular (++ x):

  1. x wird ausgewertet, um die Variable zu erzeugen
  2. Der Wert der Variablen wird an einen temporären Speicherort kopiert
  3. Der temporäre Wert wird erhöht, um einen neuen Wert zu erzeugen (der temporäre Wert wird nicht überschrieben!).
  4. Der neue Wert wird in der Variablen gespeichert
  5. Das Ergebnis der Operation ist der neue Wert (dh der inkrementierte Wert des temporären Werts).

Für das Postfix-Formular (x ++):

  1. x wird ausgewertet, um die Variable zu erzeugen
  2. Der Wert der Variablen wird an einen temporären Speicherort kopiert
  3. Der temporäre Wert wird erhöht, um einen neuen Wert zu erzeugen (der temporäre Wert wird nicht überschrieben!).
  4. Der neue Wert wird in der Variablen gespeichert
  5. Das Ergebnis der Operation ist der Wert des temporären

Einige Dinge zu beachten:

Erstens ist die zeitliche Reihenfolge der Ereignisse in beiden Fällen genau gleich . Auch hier ist es absolut nicht der Fall, dass sich die zeitliche Reihenfolge der Ereignisse zwischen Präfix und Postfix ändert. Es ist völlig falsch zu sagen, dass die Bewertung vor anderen Bewertungen oder nach anderen Bewertungen erfolgt. Die Auswertungen erfolgen in beiden Fällen in genau derselben Reihenfolge, wie Sie anhand der Schritte 1 bis 4 sehen können, die identisch sind. Der einzige Unterschied ist der letzte Schritt - ob das Ergebnis der Wert des temporären oder des neuen inkrementierten Werts ist.

Sie können dies einfach mit einer einfachen C # -Konsolen-App demonstrieren:

public class Application
{
    public static int currentValue = 0;

    public static void Main()
    {
        Console.WriteLine("Test 1: ++x");
        (++currentValue).TestMethod();

        Console.WriteLine("\nTest 2: x++");
        (currentValue++).TestMethod();

        Console.WriteLine("\nTest 3: ++x");
        (++currentValue).TestMethod();

        Console.ReadKey();
    }
}

public static class ExtensionMethods 
{
    public static void TestMethod(this int passedInValue) 
    {
        Console.WriteLine("Current:{0} Passed-in:{1}",
            Application.currentValue,
            passedInValue);
    }
}

Hier sind die Ergebnisse...

Test 1: ++x
Current:1 Passed-in:1

Test 2: x++
Current:2 Passed-in:1

Test 3: ++x
Current:3 Passed-in:3

Im ersten Test können Sie sehen, dass beide currentValueund was an die übergeben wurdeTestMethod() Erweiterung übergeben wurde, erwartungsgemäß denselben Wert aufweisen.

Doch im zweiten Fall werden versuchen , die Menschen zu sagen , dass der Zuwachs von currentValuegeschieht nach dem Aufruf TestMethod(), aber wie Sie aus den Ergebnissen sehen können, kommt es vor dem Aufruf , wie durch die angezeigte ‚Laufend: 2‘ Ergebnis.

In diesem Fall wird zunächst der Wert von currentValuein einem temporären gespeichert. Als nächstes wird eine inkrementierte Version dieses Werts wieder gespeichert currentValue, ohne jedoch die temporäre Version zu berühren, in der der ursprüngliche Wert noch gespeichert ist . Schließlich wird dieses temporäre an übergeben TestMethod(). Wenn das Inkrement nach dem Aufruf von erfolgt TestMethod(), wird derselbe, nicht inkrementierte Wert zweimal ausgeschrieben, dies ist jedoch nicht der Fall.

Es ist wichtig , dass der Wert von sowohl die zurück beachten currentValue++und ++currentValuewerden Operationen auf der Grundlage der temporären und nicht der in der Variablen gespeicherten Istwert zu der Zeit entweder Operation beendet.

In der obigen Reihenfolge der Operationen kopieren die ersten beiden Schritte den dann aktuellen Wert der Variablen in die temporäre. Damit wird der Rückgabewert berechnet. Bei der Präfixversion wird der temporäre Wert erhöht, bei der Suffix-Version wird dieser Wert direkt / nicht erhöht. Die Variable selbst wird nach dem ersten Speichern im Temporär nicht erneut gelesen.

Einfacher ausgedrückt gibt die Postfix-Version den Wert zurück, der aus der Variablen gelesen wurde (dh den Wert der temporären Variable), während die Präfix-Version den Wert zurückgibt, der in die Variable zurückgeschrieben wurde (dh den inkrementierten Wert der temporären Variable). Weder geben Sie den Wert der Variablen zurück.

Dies ist wichtig zu verstehen, da die Variable selbst möglicherweise flüchtig ist und sich in einem anderen Thread geändert hat. Dies bedeutet, dass der Rückgabewert dieser Operationen vom aktuellen Wert in der Variablen abweichen kann.

Es ist überraschend häufig, dass Menschen in Bezug auf Vorrang, Assoziativität und die Reihenfolge, in der Nebenwirkungen ausgeführt werden, sehr verwirrt sind. Ich vermute vor allem, dass es in C so verwirrend ist. C # wurde sorgfältig entworfen, um in all diesen Punkten weniger verwirrend zu sein. Für eine zusätzliche Analyse dieser Probleme, einschließlich meiner weiteren Demonstration der Falschheit der Idee, dass Präfix- und Postfix-Operationen "Dinge in der Zeit verschieben", siehe:

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

was zu dieser SO-Frage führte:

int [] arr = {0}; int value = arr [arr [0] ++]; Wert = 1?

Vielleicht interessieren Sie sich auch für meine vorherigen Artikel zu diesem Thema:

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

und

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

und ein interessanter Fall, in dem C es schwierig macht, über die Richtigkeit nachzudenken:

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

Außerdem stoßen wir auf ähnliche subtile Probleme, wenn wir andere Vorgänge mit Nebenwirkungen in Betracht ziehen, z. B. verkettete einfache Zuweisungen:

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

Und hier ist ein interessanter Beitrag darüber, warum die Inkrementoperatoren zu Werten in C # und nicht zu Variablen führen :

Warum kann ich ++ i ++ nicht in C-ähnlichen Sprachen ausführen?


4
+1: Könnten Sie für die wirklich Neugierigen einen Hinweis darauf geben, was Sie unter den "Konstruktionsfehlern dieser Operationen in C" verstehen?
Justin Ardini

21
@ Justin: Ich habe einige Links hinzugefügt. Aber im Grunde genommen: In C gibt es keinerlei Garantie dafür, in welcher Reihenfolge die Dinge rechtzeitig passieren. Ein konformer Compiler kann verdammt noch mal alles tun, was ihm gefällt, wenn sich zwei Mutationen im selben Sequenzpunkt befinden, und muss Ihnen niemals sagen, dass Sie etwas tun, das durch die Implementierung definiert ist. Dies führt dazu, dass Leute gefährlich nicht portierbaren Code schreiben, der auf einigen Compilern funktioniert und auf anderen etwas völlig anderes macht.
Eric Lippert

14
Ich muss sagen, dass dies für wirklich Neugierige gute Kenntnisse sind, aber für die durchschnittliche C # -Anwendung liegt der Unterschied zwischen dem Wortlaut in den anderen Antworten und den tatsächlichen Vorgängen so weit unter dem Abstraktionsniveau der Sprache, die sie wirklich macht kein Unterschied. C # nicht Assembler ist, und aus 99,9% der Zeit i++oder ++iin Code verwendet, die Dinge auf im Hintergrund gehen nur das; im Hintergrund . Ich schreibe C #, um zu Abstraktionsebenen zu gelangen, die über dem liegen, was auf dieser Ebene vor sich geht. Wenn dies also für Ihren C # -Code wirklich wichtig ist, sind Sie möglicherweise bereits in der falschen Sprache.
Tomas Aschan

24
@Tomas: Zunächst einmal mache ich mir Sorgen um alle C # -Anwendungen, nicht nur um die Masse der durchschnittlichen Anwendungen. Die Anzahl der nicht durchschnittlichen Anwendungen ist groß. Zweitens macht es keinen Unterschied, außer in den Fällen, in denen es einen Unterschied macht . Ich bekomme Fragen zu diesem Zeug, basierend auf echten Fehlern in echtem Code, wahrscheinlich ein halbes Dutzend Mal im Jahr. Drittens stimme ich Ihrem größeren Punkt zu; Die ganze Idee von ++ ist eine sehr einfache Idee, die im modernen Code sehr kurios aussieht. Es ist wirklich nur da, weil es für C-ähnliche Sprachen idiomatisch ist, einen solchen Operator zu haben.
Eric Lippert

11
+1, aber ich muss die Wahrheit sagen ... Es ist Jahre und Jahre her, dass die einzige Verwendung von Postfix-Operatoren, die ich mache, nicht allein in der Reihe ist (also nicht i++;) for (int i = 0; i < x; i++)... Und ich bin sehr sehr glücklich darüber! (und ich benutze niemals den Präfix-Operator). Wenn ich etwas schreiben muss, für dessen Entschlüsselung ein erfahrener Programmierer 2 Minuten benötigt ... Nun ... Es ist besser, eine weitere Codezeile zu schreiben oder eine temporäre Variable einzuführen :-) Ich denke, dass Ihr "Artikel" (ich habe gewonnen) nenne es nicht "Antwort") bestätigt meine Wahl :-)
xanatos

23

Wenn Sie haben:

int i = 10;
int x = ++i;

dann xwird es sein 11.

Aber wenn Sie haben:

int i = 10;
int x = i++;

dann xwird es sein 10.

Beachten Sie, wie Eric betont, dass das Inkrement in beiden Fällen zur gleichen Zeit erfolgt, aber es ist der Wert, der als Ergebnis angegeben wird, der sich unterscheidet (danke Eric!).

Im Allgemeinen verwende ich gerne, es ++isei denn, es gibt einen guten Grund, dies nicht zu tun. Wenn ich zum Beispiel eine Schleife schreibe, verwende ich gerne:

for (int i = 0; i < 10; ++i) {
}

Oder wenn ich nur eine Variable inkrementieren muss, verwende ich gerne:

++x;

Normalerweise hat die eine oder andere Art keine große Bedeutung und hängt vom Codierungsstil ab. Wenn Sie jedoch die Operatoren in anderen Zuordnungen verwenden (wie in meinen ursprünglichen Beispielen), ist es wichtig, sich möglicher Nebenwirkungen bewusst zu sein.


2
Sie könnten Ihre Beispiele wahrscheinlich verdeutlichen, indem Sie die Codezeilen in die richtige Reihenfolge bringen - das heißt "var x = 10; var y = ++ x;" und "var x = 10; var y = x ++;" stattdessen.
Tomas Aschan

21
Diese Antwort ist gefährlich falsch. Wenn sich das Inkrement ereignet, ändert sich dies nicht in Bezug auf die verbleibenden Operationen. Daher ist es zutiefst irreführend , dass es in einem Fall vor den verbleibenden Operationen und in dem anderen Fall nach den verbleibenden Operationen ausgeführt wird. Das Inkrement wird in beiden Fällen gleichzeitig durchgeführt . Der Unterschied besteht darin, welcher Wert als Ergebnis angegeben wird und nicht, wenn das Inkrement abgeschlossen ist .
Eric Lippert

3
Ich habe dies so bearbeitet, dass es ifür den Variablennamen und nicht varals C # -Schlüsselwort verwendet wird.
Jeff Yates

2
Also ohne Variablen zuzuweisen und nur "i ++;" oder "++ i;" wird genau die gleichen Ergebnisse liefern oder verstehe ich es falsch?
Dlaor

1
@ Eric: Könnten Sie klarstellen, warum der ursprüngliche Wortlaut "gefährlich" ist? Dies führt zu einer korrekten Verwendung, auch wenn die Details falsch sind.
Justin Ardini

7
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.

Beantwortet das deine Frage ?


4
Man könnte sich vorstellen, dass das Postfix-Inkrement und das Präfix-Dekrement keinen Einfluss auf die Variable haben: P
Andreas

5

Der Operator arbeitet so, dass er gleichzeitig inkrementiert wird. Wenn er sich jedoch vor einer Variablen befindet, wird der Ausdruck mit der inkrementierten / dekrementierten Variablen ausgewertet:

int x = 0;   //x is 0
int y = ++x; //x is 1 and y is 1

Wenn es nach der Variablen liegt, wird die aktuelle Anweisung mit der ursprünglichen Variablen ausgeführt, als ob sie noch nicht inkrementiert / dekrementiert worden wäre:

int x = 0;   //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0

Ich stimme dcp bei der Verwendung von Pre-Inkrement / Decrement (++ x) zu, sofern dies nicht erforderlich ist. Wirklich das einzige Mal, dass ich das Post-Inkrementieren / Dekrementieren verwende, ist while-Schleifen oder Schleifen dieser Art. Diese Schleifen sind die gleichen:

while (x < 5)  //evaluates conditional statement
{
    //some code
    ++x;       //increments x
}

oder

while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
    //some code
}

Sie können dies auch beim Indizieren von Arrays und dergleichen tun:

int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678;   //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);

Usw.


4

Nur für den Datensatz ist in C ++ der Präfixoperator effizienter, wenn Sie entweder (dh) die Reihenfolge der Operationen nicht interessieren (Sie möchten sie nur inkrementieren oder dekrementieren und später verwenden), da dies nicht der Fall ist müssen eine temporäre Kopie des Objekts erstellen. Leider verwenden die meisten Leute Posfix (var ++) anstelle von Präfix (++ var), nur weil wir das anfangs gelernt haben. (Ich wurde in einem Interview danach gefragt). Ich bin mir nicht sicher, ob dies in C # zutrifft, aber ich gehe davon aus, dass dies der Fall ist.


2
Deshalb benutze ich ++ i in c #, wenn ich es alleine benutze (dh nicht in einem größeren Ausdruck). Immer wenn ich eine ac # -Schleife mit i ++ sehe, weine ich ein wenig, aber jetzt, wo ich weiß, dass dies in c # tatsächlich so ziemlich der gleiche Code ist, fühle ich mich besser. Persönlich werde ich immer noch ++ i verwenden, weil Gewohnheiten schwer sterben.
Mikle
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.