Frage

Warum ist ++i ein L-Wert und i++ nicht?

War es hilfreich?

Lösung

Nun, ein anderer Antwortender hat bereits auf den Grund dafür hingewiesen ++i ist ein L-Wert, der an eine Referenz übergeben werden soll.

int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue

Der Grund für die zweite Regel besteht darin, die Initialisierung einer Referenz mithilfe eines Literals zu ermöglichen, wenn die Referenz eine Referenz auf const ist:

void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

Sie fragen sich vielleicht, warum wir überhaupt einen R-Wert einführen.Nun, diese Begriffe tauchen auf, wenn man die Sprachregeln für diese beiden Situationen erstellt:

  • Wir möchten einen Locator-Wert haben.Dies stellt einen Ort dar, der einen lesbaren Wert enthält.
  • Wir wollen den Wert eines Ausdrucks darstellen.

Die oben genannten zwei Punkte stammen aus dem C99-Standard, der diese nette Fußnote enthält, die sehr hilfreich ist:

Der Name '' lvalue '' stammt ursprünglich aus dem Zuweisungsausdruck E1 = E2, in dem der linke Operand E1 ein (modifizierbares) LVALUE sein muss.Es wird vielleicht besser als die Darstellung eines Objekts '' Locator -Wert 'angesehen.Was manchmal als "rvalue" bezeichnet wird, ist in diesem internationalen Standard als "Wert eines Ausdrucks" beschrieben.]

Der Locator-Wert wird aufgerufen lWert, während der Wert aufgerufen wird, der sich aus der Auswertung dieses Standorts ergibt Wert.Das stimmt auch nach dem C++-Standard (was die L-Wert-zu-R-Wert-Konvertierung betrifft):

4.1/2:Der in dem vom LVALUE angegebene Objekt enthaltene Wert ist das RValue -Ergebnis.

Abschluss

Anhand der obigen Semantik ist nun klar, warum i++ ist kein L-Wert, sondern ein R-Wert.Weil sich der zurückgegebene Ausdruck nicht in befindet i mehr (er wird erhöht!), es ist nur der Wert, der von Interesse sein kann.Ändern des von zurückgegebenen Werts i++ würde keinen Sinn ergeben, da wir keinen Ort haben, von dem aus wir diesen Wert erneut ablesen könnten.Und so sagt der Standard, dass es sich um einen R-Wert handelt und er daher nur an eine Referenz auf eine Konstante gebunden werden kann.

Im Gegensatz dazu wird jedoch der von zurückgegebene Ausdruck zurückgegeben ++i ist der Ort (L-Wert) von i.Provozieren einer L-Wert-zu-R-Wert-Konvertierung, wie in int a = ++i; liest den Wert daraus aus.Alternativ können wir einen Referenzpunkt darauf erstellen und den Wert später auslesen: int &a = ++i;.

Beachten Sie auch die anderen Gelegenheiten, bei denen R-Werte generiert werden.Beispielsweise sind alle temporären Werte R-Werte, das Ergebnis von binären/unären + und minus und alle Rückgabewertausdrücke, die keine Referenzen sind.Alle diese Ausdrücke befinden sich nicht in einem benannten Objekt, sondern enthalten lediglich Werte.Diese Werte können natürlich durch Objekte gestützt werden, die nicht konstant sind.

Die nächste C++-Version wird sogenannte enthalten rvalue references die, auch wenn sie auf nonconst verweisen, an einen R-Wert binden können.Der Grund dafür besteht darin, diesen anonymen Objekten Ressourcen „stehlen“ zu können und zu vermeiden, dass Kopien dies tun.Angenommen, es handelt sich um einen Klassentyp mit überladenem Präfix ++ (Rückgabe Object&) und Postfix ++ (zurückgebend Object), würde Folgendes zuerst eine Kopie verursachen und im zweiten Fall die Ressourcen vom R-Wert stehlen:

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)

Andere Tipps

Andere Menschen den funktionalen Unterschied zwischen Post und Pre Schritt in Angriff genommen haben.

Was ist ein L-Wert betrifft, i++ nicht zugeordnet werden kann, weil es auf eine Variable nicht bezieht sich. Es bezieht sich auf einen berechneten Wert.

Im Hinblick auf die Zuordnung, die beiden folgenden machen keinen Sinn, in der gleichen Art und Weise:

i++   = 5;
i + 0 = 5;

Da Prä-Inkrement einen Verweis auf die inkrementierte Variable gibt eher als eine temporäre Kopie, ++i ist ein L-Wert.

Prä-Inkrement aus Performance-Gründen vorzieht wird eine besonders gute Idee, wenn Sie so etwas wie ein Iterator-Objekt werden Inkrementieren (zB im STL), die auch ein gutes Stück mehr Schwergewicht als ein int sein kann.

Es scheint, dass viele Menschen zu erklären, wie ++i ein L-Wert ist, aber nicht die Warum , wie in Warum haben die C ++ Normenausschuss diese Funktion setzte in , vor allem in Anbetracht der Tatsache, daß C nicht zulässt, entweder als lvalues. Aus dieser Diskussion auf comp.std.c ++ , es scheint, dass es so Sie ist seine Adresse zu nehmen oder zu einem Referenz zuweisen. Ein Codebeispiel exzerpiert von Christian Bau Beitrag:

   int i;
   extern void f (int* p);
   extern void g (int& p);

   f (&++i);   /* Would be illegal C, but C programmers
                  havent missed this feature */
   g (++i);    /* C++ programmers would like this to be legal */
   g (i++);    /* Not legal C++, and it would be difficult to
                  give this meaningful semantics */

By the way, wenn i geschieht, ein integrierter Typ sein, dann Aussagen Zuordnung wie ++i = 10 invoke nicht definiertes Verhalten , weil i zweimal zwischen Sequenzpunkte geändert.

Ich erhalte die L-Wert Fehler, wenn ich zu kompilieren versuchen

i++ = 2;

aber nicht, wenn ich es ändern zu

++i = 2;

Das ist, weil der Präfix-Operator (++ i) in i den Wert ändert, dann kehrt i, so kann es immer noch zu zugeordnet werden. Der Postfix-Operator (i ++) ändert den Wert in i, sondern gibt eine temporäre Kopie des alten Wertes , die durch den Zuweisungsoperator nicht verändert werden können.


Antwort ursprüngliche Frage :

Wenn Sie sprechen über die Erhöhung Operatoren in einer Erklärung von sich mit, wie in einer for-Schleife, macht es wirklich keinen Unterschied. Vorinkrement scheint effizienter zu sein, weil Postinkrement selbst zu erhöhen und einen temporären Wert zurück, aber ein Compiler wird diese Differenz entfernt optimieren.

for(int i=0; i<limit; i++)
...

ist die gleiche wie

for(int i=0; i<limit; ++i)
...

Die Dinge ein wenig komplizierter, wenn Sie den Rückgabewert der Operation als Teil einer größeren Anweisung verwenden.

Auch die zwei einfache Erklärungen

int i = 0;
int a = i++;

und

int i = 0;
int a = ++i;

sind unterschiedlich. Welche Inkrementoperator Sie wählen, als ein Teil der Multi-Operator-Anweisungen auf, was das gewünschte Verhalten hängt verwenden. Kurz gesagt, nein kann man nicht einfach ein wählen. Sie müssen beide verstehen.

POD Pre Schritt:

Die Pre-Erhöhung sollte so handeln, wenn das Objekt vor dem Ausdruck erhöht wurde und in diesem Ausdruck verwendbar sein, als wenn das passiert ist. So ist der C ++ Standards Comitee beschlossen, es kann auch als L-Wert verwendet werden.

POD Beitrag Schritt:

Das Post-Inkrement sollte das POD-Objekt erhöhen und eine Kopie für die Verwendung in dem Ausdruck zurückzukehren (siehe n2521 Abschnitt 5.2.6). Als Kopie nicht wirklich eine Variable ist es ein L-Wert macht keinen Sinn machen.

Objekte:

Pre- und Post-Inkrement auf Objekte ist nur syntaktischer Zucker der Sprache ein Mittel bereitstellt Methoden für das Objekt aufzurufen. So technisch Objekte werden nicht durch das Standardverhalten der Sprache beschränkt, sondern nur durch die durch Methodenaufrufe auferlegten Beschränkungen.

Es ist an der Implementierer dieser Methoden das Verhalten dieser Objekte zu machen, das Verhalten der POD-Objekte spiegeln (Es ist nicht erforderlich, aber erwartet).

Objekte Pre-Schritt:

Die Anforderung (erwartetes Verhalten) ist hier, dass die Objekte erhöht wird (dh abhängig von Objekt) und das Verfahren einen Wert zurückgeben, der veränderbar ist und sieht aus wie das ursprüngliche Objekt, nachdem der Zuwachs passierte (als ob der Zuwachs vor diesem geschehen war Anweisung).

Um dies zu tun ist siple und erfordern nur, dass das Verfahren einen Bezug zu ihr zurückkehren Selbst. Eine Referenz ist ein L-Wert und somit verhält sich wie erwartet.

Objekte Post-Schritt:

Die Anforderung (erwartete Verhalten) ist hier, dass das Objekt (in der gleichen Weise wie Prä-Inkrement) und der zurückgegebene Wert sieht aus wie der alte Wert erhöht wird, und ist nicht wandelbar (so, dass es nicht wie ein l verhält -Wertes).

Non-Mutable:
Um dies zu tun, sollten Sie ein Objekt zurückgeben. Wenn das Objekt in einem Ausdruck verwendet wird, wird sie in einer temporären Variablen konstruiert Kopie werden. Temporäre Variablen sind const und somit wird es nicht wandelbar und verhalten sich wie erwartet.

Sieht aus wie der alte Wert:
Dieses einfach, indem Sie eine Kopie des Originals erreicht wird (wahrscheinlich die Kopie Konstruktor verwenden), bevor alle Änderungen makeing. Die Kopie sollte eine tiefe Kopie sein sonst irgendwelche Änderungen an den ursprünglichen die Kopie auswirken werden und damit der Staat in Beziehung zum Ausdruck unter Verwendung des Objekts ändern.

In der gleichen Weise wie Pre-Schritt:.
Es ist wahrscheinlich am besten Beitrag Zuwachs in Bezug auf die Pre-Erhöhung zu implementieren, so dass Sie das gleiche Verhalten bekommen

class Node // Simple Example
{
     /*
      * Pre-Increment:
      * To make the result non-mutable return an object
      */
     Node operator++(int)
     {
         Node result(*this);   // Make a copy
         operator++();         // Define Post increment in terms of Pre-Increment

         return result;        // return the copy (which looks like the original)
     }

     /*
      * Post-Increment:
      * To make the result an l-value return a reference to this object
      */
     Node& operator++()
     {
         /*
          * Update the state appropriatetly */
         return *this;
     }
};

In Bezug auf LValue

  • In C (und Perl zum Beispiel), weder ++i noch i++ sind Lvalues.

  • In C++, i++ ist nicht und LValue aber ++i ist.

    ++i entspricht i += 1, die i = i + 1 entspricht.
    Das Ergebnis ist, dass wir immer noch mit dem gleichen Objekt i handeln.
    Es kann gesehen werden:

    int i = 0;
    ++i = 3;  
    // is understood as
    i = i + 1;  // i now equals 1
    i = 3;
    

    i++ auf der anderen Seite könnte betrachtet werden als:
    Zuerst verwenden wir das Wert von i, dann erhöhen Sie die Objekt i.

    int i = 0;
    i++ = 3;  
    // would be understood as 
    0 = 3  // Wrong!
    i = i + 1;
    

(edit: aktualisiert, nachdem ein fleckiger erster Versuch).

Der Hauptunterschied ist, dass i ++ den pre-increment Wert zurückgibt, während die i ++ Post-Inkrement-Wert zurückgibt. Ich benutze normalerweise ++ i, wenn ich einen sehr zwingenden Grund haben zu verwenden i ++ -. Nämlich, wenn I wirklich müssen die Pre-Inkrementwert

IMHO ist es gute Praxis zu verwenden, um die '++ i' Form. Während der Unterschied zwischen Pre- und Post-Zuwachs nicht wirklich messbar ist, wenn Sie ganze Zahlen oder andere PODs vergleichen, das zusätzliche Objekt kopieren müssen Sie machen und zurück bei der Verwendung von ‚i ++‘ eine erhebliche Auswirkung auf die Leistung darstellen kann, wenn das Objekt entweder recht teuer zu kopieren, oder häufig erhöht.

Durch die Art und Weise - vermeiden mehrere Schritt Operatoren auf die gleiche Variable in der gleichen Anweisung. Sie erhalten in ein Chaos von und undefinierter Reihenfolge der Operationen „wo die Sequenzpunkte sind“, zumindest in C. Ich denke, einige davon wurden in Java nd C # gereinigt.

Vielleicht hat dies etwas mit der Art und Weise des Post-Inkrement implementiert ist zu tun. Vielleicht ist es so etwas wie folgt aus:

  • Erstellen Sie eine Kopie des ursprünglichen Wertes im Speicher
  • Erhöhen Sie die ursprüngliche Variable
  • Gibt die Kopie

Da die Kopie ist weder eine Variable noch eine Referenz dynamisch Speicher zugewiesen, es kann kein l-Wert sein.

Wie übersetzt der Compiler diesen Ausdruck? a++

Wir wissen, dass wir das zurückgeben wollen nicht inkrementiert Version von a, die alte Version von a Vor das Inkrement.Auch wir wollen uns steigern a als Nebenwirkung.Mit anderen Worten: Wir geben die alte Version von zurück a, was nicht mehr den aktuellen Stand von darstellt a, es ist nicht mehr die Variable selbst.

Der zurückgegebene Wert ist eine Kopie von a die in a gelegt wird registrieren.Anschließend wird die Variable inkrementiert.Hier geben Sie also nicht die Variable selbst zurück, sondern eine Kopie, die eine ist separate juristische Person!Diese Kopie wird vorübergehend in einem Register gespeichert und dann zurückgegeben.Denken Sie daran, dass ein L-Wert in C++ ein Objekt ist, das einen identifizierbaren Ort hat in Erinnerung.Aber die Kopie wird im Inneren gespeichert ein Register in der CPU, nicht im Speicher. Alle R-Werte sind Objekte, die keinen identifizierbaren Standort haben in Erinnerung.Das erklärt, warum die Kopie der alten Version von a ist ein R-Wert, da er vorübergehend in einem Register gespeichert wird.Im Allgemeinen sind alle Kopien, temporären Werte oder die Ergebnisse langer Ausdrücke wie (5 + a) * b werden in Registern gespeichert und dann der Variablen zugewiesen, bei der es sich um einen L-Wert handelt.

Der Postfix-Operator muss den ursprünglichen Wert in einem Register speichern, damit er als Ergebnis den nicht inkrementierten Wert zurückgeben kann.Betrachten Sie den folgenden Code:

for (int i = 0; i != 5; i++) {...}

Diese for-Schleife zählt bis fünf, aber i++ ist der interessanteste Teil.Es sind eigentlich zwei Anweisungen in einer.Zuerst müssen wir den alten Wert von verschieben i in das Register, dann erhöhen wir i.Im Pseudo-Assembler-Code:

mov i, eax
inc i

eax register enthält jetzt die alte Version von i als Kopie.Wenn die Variable i Wenn sich die Kopie im Hauptspeicher befindet, kann es viel Zeit in Anspruch nehmen, bis die CPU die Kopie vollständig aus dem Hauptspeicher geholt und in das Register verschoben hat.Das ist bei modernen Computersystemen normalerweise sehr schnell, aber wenn Ihre For-Schleife hunderttausend Mal iteriert, summieren sich all diese zusätzlichen Operationen!Es wäre eine erhebliche Leistungseinbuße.

Moderne Compiler sind normalerweise intelligent genug, um diese zusätzliche Arbeit für Ganzzahl- und Zeigertypen zu optimieren.Bei komplizierteren Iteratortypen oder möglicherweise Klassentypen kann dieser zusätzliche Aufwand möglicherweise kostspieliger sein.

Was ist mit der Präfixerhöhung? ++a?

Wir möchten das zurückgeben erhöht Version von a, die neue Version von a nach das Inkrement.Die neue Version von a stellt den aktuellen Stand dar a, weil es die Variable selbst ist.

Erste a wird erhöht.Da wir die aktualisierte Version von erhalten möchten a, warum nicht einfach das zurückgeben Variable a selbst?Wir müssen keine temporäre Kopie im Register erstellen, um einen R-Wert zu generieren.Das würde unnötige Mehrarbeit erfordern.Wir geben also einfach die Variable selbst als L-Wert zurück.

Wenn wir den nicht inkrementierten Wert nicht benötigen, ist die zusätzliche Arbeit zum Kopieren der alten Version von nicht erforderlich a in ein Register, was durch den Postfix-Operator erledigt wird.Deshalb sollten Sie nur verwenden a++ wenn du Wirklich Sie müssen den nicht inkrementierten Wert zurückgeben.Für alle anderen Zwecke einfach verwenden ++a.Durch die gewohnheitsmäßige Verwendung der Präfixversionen müssen wir uns keine Gedanken darüber machen, ob der Leistungsunterschied von Bedeutung ist.

Ein weiterer Vorteil der Verwendung ++a ist, dass es die Absicht des Programms direkter zum Ausdruck bringt:Ich möchte nur erhöhen a!Allerdings, wenn ich sehe a++ Im Code eines anderen frage ich mich, warum er den alten Wert zurückgeben möchte.Wofür ist das?

C #:

public void test(int n)
{
  Console.WriteLine(n++);
  Console.WriteLine(++n);
}

/* Output:
n
n+2
*/
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top