Inkrementieren in C ++ - Wann wird x ++ oder ++ x verwendet?

91

Ich lerne gerade C ++ und habe vor einiger Zeit etwas über die Inkrementierung gelernt. Ich weiß, dass Sie "++ x" verwenden können, um die Inkrementierung vorher vorzunehmen, und "x ++", um sie danach durchzuführen.

Trotzdem weiß ich wirklich nicht, wann ich eines der beiden verwenden soll ... Ich habe "++ x" nie wirklich verwendet und die Dinge haben bisher immer gut funktioniert - also wann sollte ich es verwenden?

Beispiel: Wann ist es in einer for-Schleife vorzuziehen, "++ x" zu verwenden?

Könnte jemand auch genau erklären, wie die verschiedenen Inkrementierungen (oder Dekrementierungen) funktionieren? Ich würde dies sehr begrüssen.

Jesse Emond
quelle

Antworten:

112

Es geht nicht um Präferenz, sondern um Logik.

x++erhöht den Wert der Variablen x nach der Verarbeitung der aktuellen Anweisung.

++xErhöht den Wert der Variablen x, bevor die aktuelle Anweisung verarbeitet wird.

Entscheide dich also einfach für die Logik, die du schreibst.

x += ++ierhöht i und addiert i + 1 zu x. x += i++fügt i zu x hinzu und erhöht dann i.

Oliver Friedrich
quelle
27
und bitte beachten Sie, dass es in einer for-Schleife bei Primativen absolut keinen Unterschied gibt. In vielen Codierungsstilen wird empfohlen, niemals einen Inkrement-Operator zu verwenden, wenn dieser missverstanden werden könnte. dh x ++ oder ++ x sollte nur in einer eigenen Zeile existieren, niemals als y = x ++. Persönlich mag ich das nicht, aber es ist ungewöhnlich
Mikeage
2
Und wenn der generierte Code in einer eigenen Zeile verwendet wird, ist er mit ziemlicher Sicherheit derselbe.
Nosredna
14
Dies mag wie Pedanterie erscheinen (hauptsächlich, weil es :) ist), aber in C ++ x++ist es ein Wert mit dem Wert xvor dem Inkrement, x++ein Wert mit dem Wert xnach dem Inkrement. Keiner der Ausdrücke garantiert, wenn der tatsächlich inkrementierte Wert wieder in x gespeichert wird. Es wird nur garantiert, dass dies vor dem nächsten Sequenzpunkt geschieht. 'nach der Verarbeitung der aktuellen Anweisung' ist nicht genau, da einige Ausdrücke Sequenzpunkte haben und einige Anweisungen zusammengesetzte Anweisungen sind.
CB Bailey
10
Eigentlich ist die Antwort irreführend. Der Zeitpunkt, zu dem die Variable x geändert wird, unterscheidet sich in der Praxis wahrscheinlich nicht. Der Unterschied besteht darin, dass x ++ so definiert ist, dass ein r-Wert des vorherigen Werts von x zurückgegeben wird, während ++ x immer noch auf die Variable x verweist.
Sellibitze
5
@BeowulfOF: Die Antwort impliziert eine Reihenfolge, die nicht existiert. Der Standard enthält nichts zu sagen, wann die Inkremente stattfinden. Der Compiler ist berechtigt, "x + = i ++" zu implementieren als: int j = i; i = i + 1; x + = j; "(dh 'i' wurde vor dem" Verarbeiten der aktuellen Anweisung "inkrementiert). Aus diesem Grund hat" i = i ++ "ein undefiniertes Verhalten und daher denke ich, dass die Antwort" optimiert "werden muss. Die Beschreibung von" x " + = ++ i "ist korrekt, da es keinen Ordnungsvorschlag gibt:" erhöht i und addiert i + 1 zu x ".
Richard Corden
53

Scott Meyers fordert Sie auf, das Präfix zu bevorzugen, außer in Fällen, in denen die Logik vorschreibt, dass das Postfix angemessen ist.

"Effective C ++" Punkt 6 - das ist ausreichend Autorität für mich.

Für diejenigen, die das Buch nicht besitzen, sind hier die relevanten Zitate. Ab Seite 32:

Aus Ihrer Zeit als C-Programmierer erinnern Sie sich vielleicht daran, dass das Präfixformular des Inkrementoperators manchmal als "Inkrementieren und Abrufen" bezeichnet wird, während das Postfixformular häufig als "Abrufen und Inkrementieren" bezeichnet wird. Es ist wichtig, sich an die beiden Sätze zu erinnern, da sie alle nur als formale Spezifikationen dienen ...

Und auf Seite 34:

Wenn Sie sich Sorgen um die Effizienz machen, sind Sie wahrscheinlich ins Schwitzen gekommen, als Sie die Postfix-Inkrementierungsfunktion zum ersten Mal sahen. Diese Funktion muss ein temporäres Objekt für ihren Rückgabewert erstellen, und die obige Implementierung erstellt auch ein explizites temporäres Objekt, das erstellt und zerstört werden muss. Die Präfix-Inkrement-Funktion hat keine solchen Provisorien ...

Duffymo
quelle
4
Wenn der Compiler nicht erkennt, dass der Wert vor dem Inkrement nicht erforderlich ist, implementiert er das Postfix-Inkrement möglicherweise in mehreren Anweisungen - kopieren Sie den alten Wert und erhöhen Sie es dann. Das Präfixinkrement sollte immer nur eine Anweisung sein.
Gnud
8
Ich habe dies gestern mit gcc getestet: in einer for-Schleife, in der der Wert nach der Ausführung weggeworfen wird, i++oder ++ider generierte Code ist der gleiche.
Giorgio
Versuchen Sie es außerhalb der for-Schleife. Das Verhalten in einer Aufgabe muss unterschiedlich sein.
Duffymo
Ich stimme Scott Meyers in seinem zweiten Punkt ausdrücklich nicht zu - dies ist normalerweise irrelevant, da 90% oder mehr Fälle von "x ++" oder "++ x" normalerweise von jeder Zuweisung isoliert sind und Optimierer klug genug sind, um zu erkennen, dass keine temporären Variablen erforderlich sind in solchen Fällen erstellt werden. In diesem Fall sind die beiden Formen vollständig austauschbar. Dies impliziert, dass alte Codebasen, die mit "x ++" durchsetzt sind, in Ruhe gelassen werden sollten - es ist wahrscheinlicher, dass Sie subtile Fehler einführen, die sie in "++ x" ändern, als die Leistung irgendwo zu verbessern. Wahrscheinlich ist es besser, "x ++" zu verwenden und die Leute zum Nachdenken zu bringen.
Omatai
2
Sie können Scott Meyers vertrauen, was Sie wollen, aber wenn Ihr Code so leistungsabhängig ist, dass Leistungsunterschiede zwischen ++xund x++tatsächlich von Bedeutung sind, ist es viel wichtiger, dass Sie tatsächlich einen Compiler verwenden, der beide Versionen unabhängig von der Version vollständig und ordnungsgemäß optimieren kann Kontext. "Da ich diesen beschissenen alten Hammer benutze, kann ich Nägel nur in einem Winkel von 43,7 Grad einschlagen" ist ein schlechtes Argument für den Bau eines Hauses, indem Nägel in nur 43,7 Grad eingeschlagen werden. Verwenden Sie ein besseres Werkzeug.
Andrew Henle
28

Aus der Referenz beim Inkrementieren von Iteratoren:

Sie sollten den Operator vor dem Inkrementieren (++ Iter) dem Operator nach dem Inkrementieren (iter ++) vorziehen, wenn Sie den alten Wert nicht verwenden möchten. Das Nachinkrementieren wird im Allgemeinen wie folgt implementiert:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

Offensichtlich ist es weniger effizient als das Vorinkrementieren.

Durch die Vorinkrementierung wird das temporäre Objekt nicht generiert. Dies kann einen erheblichen Unterschied machen, wenn die Erstellung Ihres Objekts teuer ist.

Phillip Ngan
quelle
8

Ich möchte nur bemerken, dass der geneatisierte Code derselbe ist, wenn Sie eine Inkrementierung vor / nach der Inkrementierung verwenden, bei der die Semantik (von vor / nach) keine Rolle spielt.

Beispiel:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"
Döbel
quelle
5
Für einen primitiven Typ wie eine Ganzzahl ja. Haben Sie überprüft, was der Unterschied für so etwas wie ein ist std::map::iterator? Natürlich sind die beiden Operatoren dort unterschiedlich, aber ich bin gespannt, ob der Compiler das Postfix auf das Präfix optimieren wird, wenn das Ergebnis nicht verwendet wird. Ich denke nicht, dass es erlaubt ist - da die Postfix-Version Nebenwirkungen enthalten könnte.
SEH
Außerdem sollte " der Compiler wird wahrscheinlich erkennen, dass Sie den Nebeneffekt nicht benötigen und ihn wegoptimieren " keine Entschuldigung sein, um schlampigen Code zu schreiben, der die komplexeren Postfix-Operatoren ohne jeden Grund verwendet, abgesehen von der Tatsache, dass es so viele gibt Angebliche Unterrichtsmaterialien verwenden Postfix ohne ersichtlichen Grund und werden im Großhandel kopiert.
underscore_d
6

Das Wichtigste ist, dass x ++ den Wert zurückgeben muss, bevor das Inkrement tatsächlich stattgefunden hat. Daher muss eine temporäre Kopie des Objekts erstellt werden (Pre-Inkrement). Dies ist weniger effizient als ++ x, das direkt inkrementiert und zurückgegeben wird.

Eine andere erwähnenswerte Sache ist jedoch, dass die meisten Compiler in der Lage sein werden, solche unnötigen Dinge zu optimieren, wenn dies möglich ist. Beispielsweise führen beide Optionen hier zu demselben Code:

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)
rmn
quelle
4

Ich stimme @BeowulfOF zu, obwohl ich aus Gründen der Klarheit immer empfehlen würde, die Aussagen so aufzuteilen, dass die Logik absolut klar ist, dh:

i++;
x += i;

oder

x += i;
i++;

Meine Antwort lautet also: Wenn Sie klaren Code schreiben, sollte dies selten eine Rolle spielen (und wenn es darauf ankommt, ist Ihr Code wahrscheinlich nicht klar genug).

Gebote
quelle
2

Ich wollte nur noch einmal betonen, dass ++ x voraussichtlich schneller als x ++ ist (insbesondere wenn x ein Objekt eines beliebigen Typs ist). Daher sollte ++ x verwendet werden, sofern dies nicht aus logischen Gründen erforderlich ist.

Shailesh Kumar
quelle
2
Ich möchte nur betonen, dass diese Betonung höchstwahrscheinlich irreführend ist. Wenn Sie sich eine Schleife ansehen, die mit einem isolierten "x ++" endet und "Aha! - das ist der Grund, warum dies so langsam läuft!" und Sie ändern es in "++ x", dann erwarten Sie genau keinen Unterschied. Optimierer sind klug genug zu erkennen, dass keine temporären Variablen erstellt werden müssen, wenn niemand ihre Ergebnisse verwenden wird. Die Implikation ist, dass alte Codebasen, die mit "x ++" durchsetzt sind, in Ruhe gelassen werden sollten - es ist wahrscheinlicher, dass Sie Fehler beim Ändern einführen, als die Leistung irgendwo zu verbessern.
Omatai
1

Sie haben den Unterschied richtig erklärt. Es hängt nur davon ab, ob x vor jedem Durchlauf einer Schleife oder danach inkrementiert werden soll. Es hängt von Ihrer Programmlogik ab, was angemessen ist.

Ein wichtiger Unterschied beim Umgang mit STL-Iteratoren (die auch diese Operatoren implementieren) besteht darin, dass ++ eine Kopie des Objekts erstellt, auf das der Iterator zeigt, diese dann inkrementiert und dann zurückgibt. ++ Es führt andererseits zuerst das Inkrement durch und gibt dann einen Verweis auf das Objekt zurück, auf das der Iterator jetzt zeigt. Dies ist meistens nur relevant, wenn jede Leistung zählt oder wenn Sie Ihren eigenen STL-Iterator implementieren.

Bearbeiten: Die Verwechslung von Präfix- und Suffixnotation wurde behoben

Björn Pollex
quelle
Die Rede von "vor / nach" der Iteration einer Schleife ist nur dann sinnvoll, wenn das Pre / Post-Inc / Decrement in der Bedingung auftritt. Häufiger wird es in der Fortsetzungsklausel sein, wo es keine Logik ändern kann, obwohl es für Klassentypen möglicherweise langsamer ist, Postfix zu verwenden, und die Leute sollten dies nicht ohne Grund verwenden.
underscore_d
1

Postfix-Form von ++, - Operator folgt der Regel use-then-change ,

Das Präfixformular (++ x, - x) folgt der Regeländerung-dann-Verwendung .

Beispiel 1:

Wenn mehrere Werte mit << unter Verwendung von cout kaskadiert werden, finden Berechnungen (falls vorhanden) von rechts nach links statt, der Druck erfolgt jedoch von links nach rechts, z. B. (wenn val anfänglich 10).

 cout<< ++val<<" "<< val++<<" "<< val;

wird in ergeben

12    10    10 

Beispiel 2:

Wenn in Turbo C ++ mehrere Vorkommen von ++ oder (in irgendeiner Form) in einem Ausdruck gefunden werden, werden zuerst alle Präfixformulare berechnet, dann wird der Ausdruck ausgewertet und schließlich werden Postfixformulare berechnet, z.

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Es wird in Turbo C ++ ausgegeben

48 13

Während es im modernen Compiler ausgegeben wird (weil sie die Regeln streng befolgen)

45 13
  • Hinweis: Die mehrfache Verwendung von Inkrementierungs- / Dekrementierungsoperatoren für dieselbe Variable in einem Ausdruck wird nicht empfohlen. Die Behandlung / Ergebnisse solcher
    Ausdrücke variieren von Compiler zu Compiler.
Sunil Dhillon
quelle
Es ist nicht so, dass Ausdrücke, die mehrere Inkrementierungs- / Dekrementierungsoperationen enthalten, "von Compiler zu Compiler variieren", sondern schlimmer: Solche mehrfachen Modifikationen zwischen Sequenzpunkten haben ein undefiniertes Verhalten und vergiften das Programm.
underscore_d
0

Das Verständnis der Sprachsyntax ist wichtig, wenn Sie die Klarheit des Codes berücksichtigen möchten. Kopieren Sie eine Zeichenfolge, beispielsweise mit Post-Inkrement:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

Wir möchten, dass die Schleife ausgeführt wird, indem das Nullzeichen (das false testet) am Ende der Zeichenfolge angetroffen wird. Dazu muss der Wert vor dem Inkrementieren getestet und auch der Index erhöht werden. Aber nicht unbedingt in dieser Reihenfolge - eine Möglichkeit, dies mit dem Vorinkrement zu codieren, wäre:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

Es ist eine Geschmackssache, die klarer ist und wenn die Maschine eine Handvoll Register hat, sollten beide die gleiche Ausführungszeit haben, selbst wenn a [i] eine Funktion ist, die teuer ist oder Nebenwirkungen hat. Ein wesentlicher Unterschied könnte der Exit-Wert des Index sein.

shkeyser
quelle