Warum haben wir ein Postfix-Inkrement?


55

Haftungsausschluss : Ich kenne die Semantik von Präfix und Postfix-Inkrement sehr gut. Erklären Sie mir bitte nicht, wie sie funktionieren.

Beim Lesen von Fragen zum Stapelüberlauf kann ich nicht anders, als zu bemerken, dass Programmierer immer und immer wieder durch den Postfix-Inkrement-Operator verwirrt werden. Daraus ergibt sich die folgende Frage: Gibt es einen Anwendungsfall, bei dem ein Postfix-Inkrement einen echten Vorteil in Bezug auf die Codequalität bietet?

Lassen Sie mich meine Frage an einem Beispiel erläutern. Hier ist eine übersichtliche Implementierung von strcpy:

while (*dst++ = *src++);

Aber das ist nicht gerade der selbstdokumentierendste Code in meinem Buch (und er erzeugt zwei nervige Warnungen bei vernünftigen Compilern). Also, was ist los mit der folgenden Alternative?

while (*dst = *src)
{
    ++src;
    ++dst;
}

Wir können dann die verwirrende Zuordnung in der Bedingung aufheben und erhalten völlig warnfreien Code:

while (*src != '\0')
{
    *dst = *src;
    ++src;
    ++dst;
}
*dst = '\0';

(Ja, ich weiß srcund dstwerde in diesen alternativen Lösungen unterschiedliche Endwerte haben, aber da es strcpysofort nach der Schleife zurückkommt, ist es in diesem Fall egal.)

Es scheint, dass der Zweck des Postfix-Inkrements darin besteht, den Code so knapp wie möglich zu halten. Ich verstehe einfach nicht, wie wir das anstreben sollen. Wenn es ursprünglich um Leistung ging, ist sie heute noch relevant?


7
Die Postfix-Variante wird am häufigsten verwendet. Wenn Sie also dagegen vorgehen wollen, ist es besser, wenn es sich um eine ziemlich starke Variante handelt. Und Ihr erstes Beispiel ist ein Strohmann, da ich vermute, dass nur wenige Leute die strcpyMethode auf diese Weise codieren würden (aus Gründen, die Sie bereits erwähnt haben).
Robert Harvey

4
Warum haben Sie entweder überhaupt?
James McNellis

31
Wenn wir alles aus der Sprache entfernen würden, was die Programmierer verwirren könnte, hätten wir nicht sehr viele Funktionen. Die Tatsache, dass etwas für Sie nicht oder nur sehr selten nützlich ist, bedeutet nicht, dass es abgeschnitten werden sollte. Wenn es nicht mehr relevant ist, verwenden Sie es nicht. das Ende.
Cody Grey

4
Ja, aber dann kannst du es nie tunint c = 0; c++;
Biff MaGriff

4
@Cody: Weil einige Dinge sowohl verwirrend als auch nützlich sind. Er ist nicht verwirrt über die Nachvergrößerung selbst, er ist verwirrt über die Nützlichkeit . Wir sollten keine Dinge haben, die nutzlos, verwirrend oder nicht sind.
GManNickG

Antworten:


23

Es hatte zwar früher einige Auswirkungen auf die Leistung, aber ich denke, der wahre Grund ist, Ihre Absicht sauber auszudrücken. Die eigentliche Frage ist, ob etwas while (*d++=*s++);Absichten klar zum Ausdruck bringt oder nicht. IMO, und ich finde die Alternativen, die Sie anbieten, weniger klar - aber das kann (leicht) darauf zurückzuführen sein, dass Sie sich jahrzehntelang an die Arbeitsweise gewöhnt haben. Es hilft wahrscheinlich auch , C von K & R gelernt zu haben (da es zu diesem Zeitpunkt fast keine anderen Bücher über C gab).

Bis zu einem gewissen Grad wurde Knappheit in älterem Code in viel höherem Maße geschätzt. Ich persönlich denke, das war größtenteils eine gute Sache - ein paar Codezeilen zu verstehen ist normalerweise ziemlich trivial; Schwierig ist es, große Codestücke zu verstehen. Tests und Studien haben wiederholt gezeigt, dass die gleichzeitige Anpassung des gesamten Codes auf dem Bildschirm ein wesentlicher Faktor für das Verständnis des Codes ist. Wenn Bildschirme erweitert werden, scheint dies wahr zu sein, sodass es weiterhin wertvoll ist, den Code (einigermaßen) knapp zu halten.

Natürlich ist es möglich, über Bord zu gehen, aber ich denke nicht, dass das so ist. Insbesondere denke ich, dass es über Bord geht, wenn das Verstehen einer einzelnen Codezeile extrem schwierig oder zeitaufwendig wird - insbesondere, wenn das Verstehen von weniger Codezeilen mehr Aufwand erfordert als das Verstehen von mehr Zeilen. Das kommt in Lisp und APL häufig vor, scheint aber (zumindest für mich) hier nicht der Fall zu sein.

Ich mache mir weniger Sorgen um Compiler-Warnungen - es ist meine Erfahrung, dass viele Compiler ziemlich regelmäßig äußerst lächerliche Warnungen ausgeben. Während ich sicher denke, dass die Leute ihren Code verstehen sollten (und alle Warnungen, die er erzeugen könnte), ist anständiger Code, der in einigen Compilern eine Warnung auslöst, nicht unbedingt falsch. Zugegeben, Anfänger wissen nicht immer, was sie ignorieren können, aber wir bleiben nicht für immer Anfänger und müssen auch nicht so codieren, wie wir es sind.


12
+1 für den Hinweis, dass die knappe Version für einige von uns leichter zu lesen ist. :-)

1
Wenn Sie einige Ihrer Compiler-Warnungen nicht mögen (und es gibt mehr als ein paar, auf die ich verzichten könnte - ernsthaft, ich wollte tippen if(x = y), ich schwöre!), Sollten Sie die Warnungsoptionen Ihres Compilers anpassen, damit Sie die nicht versehentlich verpassen Warnungen Sie tun möchten.

4
@Brooks Moses: Ich bin viel weniger begeistert darüber. Ich mag es nicht, meinen Code zu verschmutzen, um die Mängel des Compilers auszugleichen.
Jerry Coffin

1
@Jerry, @Chris: Diese Anmerkung ist für den Fall gedacht, dass die Warnung normalerweise eine gute Sache ist, aber nicht für eine bestimmte Zeile gelten soll, die etwas Ungewöhnliches tut. Wenn Sie die Warnung niemals haben wollen und sie als ein Manko betrachten, verwenden Sie -Wno-X, aber wenn Sie nur eine Ausnahme machen wollen und die Warnung überall gelten soll, ist dies viel verständlicher, als wenn Sie das Arkane-Hüpfen verwenden, auf das Chris Bezug nimmt .

1
@Brooks: Die doppelten Klammern und die (void)xzum Schweigen gebrachten Warnungen sind allgemein bekannte Redewendungen, um ansonsten nützliche Warnungen zum Schweigen zu bringen. Die Anmerkung sieht ein bisschen aus wie pragmas, im besten
Matthieu M.

32

Es ist, ähm, eine Hardware-Sache


Interessant, dass du es bemerken würdest. Postfix-Inkremente gibt es wahrscheinlich aus einer Reihe sehr guter Gründe, aber wie viele Dinge in C lässt sich ihre Beliebtheit auf den Ursprung der Sprache zurückführen.

Obwohl C auf einer Vielzahl von frühen und leistungsschwachen Maschinen entwickelt wurde, gelang es C und Unix erstmals, mit den speicherverwalteten Modellen des PDP-11 die relativ große Leistung zu erbringen. Dies waren zu ihrer Zeit relativ wichtige Computer, und Unix war bei weitem besser - exponentiell besser - als die anderen 7 für die -11 verfügbaren Betriebssysteme.

Und so kommt es, dass auf dem PDP-11,

*--p

und

*p++

... wurden als Adressierungsarten in Hardware implementiert . (Auch *p, aber keine andere Kombination.) Bei diesen frühen Maschinen muss das Speichern von ein oder zwei Befehlen in einer Schleife bei weniger als 0,001 GHz fast ein Warten pro Sekunde oder ein Warten pro Minute oder ein Erlöschen gewesen sein -Lunch Unterschied. Dies bezieht sich nicht genau auf das Nachinkrementieren, aber eine Schleife mit einem Nachinkrementieren des Zeigers hätte viel besser sein können, als dies damals der Fall war.

Infolgedessen bilden die Entwurfsmuster C-Idiome, die zu C-Mental-Makros werden.

Es ist so etwas wie das Deklarieren von Variablen direkt nach einem {... nicht, seitdem C89 aktuell war, war dies eine Anforderung, aber es ist jetzt ein Codemuster.

Update: Offensichtlich liegt der Hauptgrund *p++in der Sprache, weil es genau das ist, was man so oft machen möchte. Die Popularität des Codemusters wurde verstärkt durch populäre Hardware , die entlang kam und entsprach das bereits vorhandene Muster einer Sprache etwas vor der Ankunft der PDP-11 ausgelegt.

In diesen Tagen macht es keinen Unterschied , das Muster , das Sie verwenden, oder wenn Sie die Indizierung verwenden, und wir in der Regel Programm auf einem höheren Niveau sowieso, aber es muss eine Menge auf diesen 0.001GHz Maschinen haben zählte, und mit etwas anderes als *--xoder *x++würden Sie haben gemeint habe den PDP-11 nicht "bekommen", und es könnte sein, dass Leute auf dich zukommen und sagen "Wusstest du, dass ..." :-) :-)


25
Wikipedia sagt: "Ein (falscher) Volksmythos besagt, dass die Befehlssatzarchitektur des PDP-11 die idiomatische Verwendung der Programmiersprache C beeinflusste. Die Adressierungsmodi für Inkremente und Dekremente des PDP-11 entsprechen den −−iund- i++Konstrukten in C. If iund jwaren beide Registervariablen, ein Ausdruck *(−−i) = *(j++), der zu einer einzigen Maschinenanweisung kompiliert werden konnte. [...] Dennis Ritchie widerspricht eindeutig diesem Volksmythos. [Die Entwicklung der C-Sprache ] "
Fredoverflow

3
Huh (Aus dem Link in FredOverflows Kommentar): "Die Leute vermuten oft, dass sie dafür geschaffen wurden, die vom DEC PDP-11 bereitgestellten Adressmodi für automatisches Inkrementieren und automatisches Dekrementieren zu verwenden, auf denen C und Unix zum ersten Mal populär wurden. Dies ist historisch gesehen unmöglich, da es bei der Entwicklung von B kein PDP-11 gab.Das PDP-7 verfügte jedoch über einige "Auto-Inkrement" -Speicherzellen mit der Eigenschaft, dass ein indirekter Speicherbezug durch sie die Zelle inkrementierte schlug Thompson solche Operatoren vor; die Verallgemeinerung, sie sowohl als Präfix als auch als Postfix zu verwenden, war seine eigene. "

3
DMR sagte nur, dass der PDP-11 nicht der Grund war, warum er zu C hinzugefügt wurde. Das habe ich auch gesagt. Was ich sagte, war, dass es für den PDP-11 von Vorteil war und aus genau diesem Grund zu einem beliebten Designmuster wurde. Wenn Wikipedia dem widerspricht , dann sind sie einfach falsch, aber ich lese es nicht so. Es ist auf dieser W-Seite schlecht formuliert.
DigitalRoss

3
@FredOverflow. Ich denke, Sie haben genau verstanden, was der "Volksmythos" ist. Der Mythos ist, dass C entwickelt wurde, um diese 11 Operationen auszunutzen, nicht, dass sie nicht existieren und dass das Codemuster nicht populär wurde, weil jeder wusste, dass sie den 11 Brunnen zugeordnet sind. Falls es nicht klar ist: Der PDP-11 implementiert diese beiden Modi wirklich wirklich in Hardware für fast jeden Befehl als Adressierungsmodus, und es war wirklich die erste wichtige Maschine, auf der C lief.
DigitalRoss

2
msgstr "Auf diesen 0,001 GHz - Maschinen muss es sehr wichtig gewesen sein". Ähm, ich muss es nur ungern sagen, weil es mich datiert, aber ich hatte Motorola- und Intel-Handbücher für meine CPUs, um die Größe der Anweisungen und die Anzahl der Zyklen, die sie benötigten, nachzuschlagen. Die Suche nach den kleinsten Codes und die Möglichkeit, einem Druckerspooler eine weitere Funktion hinzuzufügen und ein paar Zyklen zu sparen, bedeutete, dass ein serieller Anschluss mit einem Protokoll wie dem neu erfundenen MIDI Schritt halten konnte. C erleichterte einiges von der Fehlerkorrektur, zumal die Compiler besser wurden, aber es war trotzdem wichtig zu wissen, wie Code am effizientesten geschrieben werden konnte.
der Blechmann

14

Das Präfix, das Postfix --und die ++Operatoren wurden von Ken Thompson in der Sprache B (dem Vorgänger von C) eingeführt - und nein, sie wurden nicht von der PDP-11 inspiriert, die es zu diesem Zeitpunkt noch nicht gab.

Zitat aus "Die Entwicklung der C-Sprache" von Dennis Ritchie :

Thompson ging noch einen Schritt weiter und erfand das ++und--Operatoren, die inkrementieren oder dekrementieren; Ihre Präfix- oder Postfix-Position bestimmt, ob die Änderung vor oder nach dem Notieren des Operandenwerts erfolgt. Sie waren nicht in den frühesten Versionen von B, sondern erschienen auf dem Weg. Die Leute raten oft, dass sie erstellt wurden, um die vom DEC PDP-11 bereitgestellten Adressmodi für automatisches Inkrementieren und automatisches Dekrementieren zu verwenden, auf denen C und Unix zum ersten Mal populär wurden. Dies ist historisch unmöglich, da es bei der Entwicklung von B kein PDP-11 gab. Der PDP-7 verfügte jedoch über einige "Auto-Inkrement" -Speicherzellen mit der Eigenschaft, dass eine indirekte Speicherreferenz durch sie die Zelle inkrementierte. Diese Funktion hat Thompson wahrscheinlich solche Operatoren nahegelegt. Die Verallgemeinerung, sie sowohl als Präfix als auch als Postfix zu verwenden, war seine eigene. Tatsächlich,++xwar kleiner als das von x=x+1.


8
Nein, ich bin nicht mit Ken Thompson verwandt.
Keith Thompson

Vielen Dank für diesen sehr interessanten Hinweis! Dies bestätigt mein Zitat von K & R, das eher das Ziel der Kompaktheit als einer technischen Optimierung vorschlug. Ich wusste jedoch nicht, dass diese Betreiber bereits in B.
Christophe

@BenPen Soweit ich weiß, gibt es keine Richtlinie zum Schließen von Fragen, wenn auf einer anderen SE-Site ein Duplikat vorhanden ist , sofern beide Fragen zu den jeweiligen Sites passen.
Ordous

Interessant ... Dem Programmierer ist man mit Sicherheit keine so enge Frage.
BenPen

@BenPen: Die akzeptierte Antwort auf die Frage zum Stapelüberlauf zitiert bereits die gleiche Quelle, die ich getan habe (obwohl nicht ganz so viel davon).
Keith Thompson

6

Der offensichtliche Grund für die Existenz des Post-Inkrement-Operators besteht darin, dass Sie keine Ausdrücke wie (++x,x-1)oder (x+=1,x-1)überall schreiben oder unnötig triviale Aussagen mit leicht verständlichen Nebenwirkungen in mehrere Aussagen aufteilen müssen . Hier sind einige Beispiele:

while (*s) x+=*s++-'0';

if (x) *s++='.';

Wann immer ein String in Vorwärtsrichtung gelesen oder geschrieben wird, ist das Nachinkrementieren und nicht das Vorinkrementieren fast immer die natürliche Operation. Ich bin fast nie auf reale Anwendungen zum Vorab-Inkrementieren gestoßen, und die einzige Hauptanwendung, die ich beim Vorab-Inkrementieren gefunden habe, ist das Konvertieren von Zahlen in Zeichenfolgen (weil menschliche Schriftsysteme Zahlen rückwärts schreiben).

Edit: Eigentlich wäre es nicht ganz so hässlich; statt (++x,x-1)du könntest natürlich auch ++x-1oder verwenden (x+=1)-1. Ist x++noch lesbarer.


Sie müssen mit diesen Ausdrücken vorsichtig sein, da Sie eine Variable zwischen zwei Sequenzpunkten ändern und lesen. Die Reihenfolge, in der diese Ausdrücke ausgewertet werden, kann nicht garantiert werden.

@Schneemann: Welches? Ich sehe keine solchen Probleme in meiner Antwort.
R ..

Wenn Sie etwas wie (++x,x-1)(Funktionsaufruf, vielleicht?)

@Schneemann: Das ist kein Funktionsaufruf. Dies ist der C-Komma-Operator, ein Sequenzpunkt, der in Klammern angegeben ist, um die Priorität zu steuern. Das Ergebnis ist das gleiche wie x++in den meisten Fällen (eine Ausnahme: Es xhandelt sich um einen vorzeichenlosen Typ, dessen Rang niedriger ist als intund dessen Anfangswert xder Maximalwert dieses Typs ist).
R ..

Ah, der knifflige Kommaoperator ... wenn ein Komma kein Komma ist. Die Klammer und die Seltenheit des Komma-Operators haben mich abgeworfen. Keine Ursache!

4

Der PDP-11 bot Operationen nach dem Inkrementieren und vor dem Dekrementieren im Befehlssatz an. Das waren keine Anweisungen. Sie waren Befehlsmodifikatoren, mit denen Sie angeben konnten, dass Sie den Wert eines Registers vor dessen Inkrementierung oder nach dessen Dekrementierung wünschen.

Hier ist ein Wortkopierschritt in der Maschinensprache:

movw (r1)++,(r2)++

die ein Wort von einem Ort zu einem anderen bewegten, auf das die Register zeigten, und die Register wären bereit, es erneut zu tun.

Als C auf und für den PDP-11 aufgebaut wurde, fanden viele nützliche Konzepte Eingang in C. C sollte ein nützlicher Ersatz für Assembler-Sprache sein. Pre-Inkrement und Post-Dekrement wurden für die Symmetrie hinzugefügt.


Das sind großartige Modifikatoren ... Ich möchte die PDP-11-Montage erlernen. Übrigens, haben Sie einen Hinweis auf "für Symmetrie?" Es macht Sinn, aber das ist Geschichte. Manchmal war es nicht der Schlüssel, Sinn zu machen. LOL
BenPen

2
Wird auch als Adressierungsmodus bezeichnet . Der PDP-11 lieferte Post-Increment und Pre-Decrement, die für Stapeloperationen gut zusammenpassen.
Erik Eidt

1
Der PDP-8 lieferte eine Indirektion des Speichers nach dem Inkrementieren, jedoch keine entsprechende Operation vor dem Dekrementieren. Beim Magnetkernspeicher löschte das Lesen eines Speicherworts es aus (!), Sodass der Prozessor eine Rückschreibefunktion verwendete, um das gerade gelesene Wort wiederherzustellen. Das Anwenden eines Inkrements vor dem Zurückschreiben erfolgte ziemlich natürlich, und eine effizientere Blockverschiebung wurde möglich.
Erik Eidt

3
Entschuldigung, der PDP-11 hat diese Bediener nicht inspiriert. Es existierte noch nicht, als Ken Thompson sie erfand. Siehe meine Antwort .
Keith Thompson

3

Ich bezweifle, dass es jemals wirklich notwendig war. Soweit ich weiß, kompiliert es sich auf den meisten Plattformen zu nichts Kompakterem, als das Pre-Increment zu verwenden, wie Sie es getan haben, in der Schleife. Es war nur so, dass zum Zeitpunkt der Erstellung die Kürze des Codes wichtiger war als die Klarheit des Codes.

Das soll nicht heißen, dass die Klarheit des Codes nicht wichtig ist (oder war), aber wenn Sie auf einem Modem mit niedrigem Baud-Wert tippen (alles, wo Sie in Baud messen, ist langsam), wo jeder Tastendruck es schaffen musste Wenn Sie den Mainframe mit einem einzelnen Bit als Paritätsprüfung zurücksenden und dann ein Byte nach dem anderen zurücksenden, wollten Sie nicht viel tippen müssen.

Das ist so ähnlich wie das & und | Operator mit niedrigerer Priorität als ==

Es wurde mit den besten Absichten entworfen (eine nicht kurzschließende Version von && und ||), aber jetzt verwirrt es Programmierer jeden Tag und wird sich wahrscheinlich nie ändern.

Wie auch immer, dies ist nur eine Antwort, da ich denke, dass es keine gute Antwort auf Ihre Frage gibt, aber ich werde wahrscheinlich von einem Guru-Kodierer, der mehr als ich ist, als falsch eingestuft.

--BEARBEITEN--

Ich sollte beachten, dass ich es unglaublich nützlich finde, beides zu haben. Ich weise nur darauf hin, dass es sich nicht ändern wird, ob es jemandem gefällt oder nicht.


Dies ist nicht einmal im entferntesten wahr. Zu keinem Zeitpunkt war "Kürze des Codes" wichtiger als Klarheit.
Cody Grey

Nun, ich würde argumentieren, dass es viel mehr ein Schwerpunkt war. Sie haben recht , obwohl, ist es sicherlich nie mehr wichtiger als Klarheit des Code ... für die meisten Menschen.

6
Nun, die Definition von "knapp" ist "glatt elegant: poliert" oder "mit wenigen Worten: ohne Überflüssigkeiten", was im Allgemeinen eine gute Sache beim Schreiben von Code ist. "Terse" bedeutet nicht "obskur, schwer zu lesen und verschleiert", wie viele Leute denken.
James McNellis

@James: Während Sie Recht haben, meinen die meisten Leute die zweite Definition von "knapp", wenn sie es in einem Programmierkontext verwenden: "mit wenigen Worten, ohne Überflüssigkeiten". Unter dieser Definition ist es sicherlich einfacher, sich Situationen vorzustellen, in denen es einen Kompromiss zwischen Kürze und Ausdruckskraft eines bestimmten Codeteils gibt. Offensichtlich ist das Richtige beim Schreiben von Code dasselbe wie beim Sprechen: Machen Sie die Dinge so kurz, wie sie sein müssen, aber nicht kürzer. Das Schätzen von Knappheit gegenüber Expressivität kann zu verschleiertem Code führen, sollte es aber nicht.
Cody Grey

1
Spulen Sie 40 Jahre zurück und stellen Sie sich vor, Sie müssten Ihr Programm auf die Trommel legen, die nur 1000 Zeichen fasst, oder Sie schreiben, dass ein Compiler nur mit viertausend Byte RAM arbeiten kann. Es gab Zeiten, in denen es nicht einmal um Knappheit oder Klarheit ging. Sie mussten knapp sein, es gab keinen Computer auf diesem Planeten, der Ihr nicht knappes Programm speichern, ausführen oder kompilieren konnte.
Nr.

3

Ich mag den Postfix-Aperator beim Umgang mit Zeigern (ob ich sie dereferenziere oder nicht), weil

p++

liest sich natürlicher als "zum nächsten Punkt bewegen" als das Äquivalent

p += 1

was Anfänger sicherlich verwirren muss, wenn sie es zum ersten Mal mit einem Zeiger sehen, bei dem sizeof (* p)! = 1 ist.

Sind Sie sicher, dass Sie das Verwirrungsproblem richtig ausdrücken? Ist nicht das, was Anfänger verwirrt, dass der Postfix ++ - Operator eine höhere Priorität hat als der Dereferenzierungsoperator *, so dass

*p++

analysiert als

*(p++)

und nicht

(*p)++

wie manche vielleicht erwarten?

(Denken Sie, das ist schlecht? Siehe Lesen von C-Erklärungen .)


2

Zu den subtilen Elementen einer guten Programmierung gehören Lokalisierung und Minimalismus :

  • Setzen von Variablen in einem minimalen Verwendungsbereich
  • Verwenden von const, wenn kein Schreibzugriff erforderlich ist
  • usw.

Im gleichen Sinne x++kann dies als eine Möglichkeit angesehen werden, einen Verweis auf den aktuellen Wert von zu lokalisieren, xwährend sofort angezeigt wird , dass Sie - zumindest vorerst - mit diesem Wert fertig sind und zum nächsten übergehen möchten (gültig, ob xein intoder ist) ein Zeiger oder Iterator). Mit ein wenig Fantasie könnten Sie dies damit vergleichen, dass Sie den alten Wert / die Position von x"out of scope" sofort nach dessen Nichtverwendung wieder auf den neuen Wert umstellen. Der genaue Punkt, an dem dieser Übergang möglich ist, wird durch das Postfix hervorgehoben++ . Die Implikation, dass dies wahrscheinlich die endgültige Verwendung des "alten" ist, xkann einen wertvollen Einblick in den Algorithmus geben und den Programmierer beim Durchsuchen des umgebenden Codes unterstützen. Natürlich das Postfix++ kann den Programmierer auf die Verwendung des neuen Wertes aufmerksam machen, was gut oder schlecht sein kann, je nachdem, wann dieser neue Wert tatsächlich benötigt wird, so dass es ein Aspekt der "Kunst" oder "Kunst" des Programmierens ist, um zu bestimmen, was mehr ist hilfreich in den Umständen.

Während viele Anfänger durch eine Funktion verwirrt sein können, ist es sinnvoll, dies gegen die langfristigen Vorteile abzuwägen: Anfänger bleiben nicht lange Anfänger.


2

Wow, viele Antworten, die nicht ganz zutreffend sind (wenn ich so kühn bin), und bitte verzeihen Sie mir, wenn ich auf das Offensichtliche hinweise - insbesondere angesichts Ihres Kommentars, nicht auf die Semantik hinzuweisen, sondern auf das Offensichtliche (Aus der Sicht von Stroustrup, nehme ich an) scheint noch nicht gepostet worden zu sein! :)

Postfix x++erzeugt eine temporäre Variable, die im Ausdruck 'aufwärts' übergeben wird, während die Variable xanschließend inkrementiert wird.

Prefix ++xerzeugt kein temporäres Objekt, erhöht aber 'x' und übergibt das Ergebnis an den Ausdruck.

Der Wert ist Bequemlichkeit für diejenigen, die den Bediener kennen.

Zum Beispiel:

int x = 1;
foo(x++); // == foo(1)
// x == 2: true

x = 1;
foo(++x); // == foo(2)
// x == 2: true

Natürlich können die Ergebnisse dieses Beispiels aus einem anderen äquivalenten (und möglicherweise weniger schrägen) Code erstellt werden.

Warum haben wir den Postfix-Operator? Ich denke, weil es eine Redewendung ist, die trotz der Verwirrung, die sie offensichtlich schafft, fortbesteht. Es ist ein Legacy-Konstrukt, das früher als wertvoll angesehen wurde, obwohl ich nicht sicher bin, ob der wahrgenommene Wert eher der Leistung als der Zweckmäßigkeit diente. Diese Bequemlichkeit ist nicht verloren gegangen, aber ich denke, die Wertschätzung der Lesbarkeit hat zugenommen, was zu einer Infragestellung des Zwecks des Betreibers geführt hat.

Benötigen wir den Postfix Operator mehr? Wahrscheinlich nicht. Das Ergebnis ist verwirrend und erschwert die Verständlichkeit. Einige gute Programmierer werden sicherlich sofort wissen, wo sie es nützlich finden, oft an Orten, an denen es eine besonders "PERL-artige" Schönheit hat. Der Preis für diese Schönheit ist Lesbarkeit.

Ich bin damit einverstanden, dass expliziter Code Vorteile gegenüber Kürze, Lesbarkeit, Verständlichkeit und Wartbarkeit hat. Gute Designer und Manager wollen das.

Es gibt jedoch eine gewisse Schönheit, die einige Programmierer erkennen und die sie dazu ermutigt, Code nur aus Gründen der Einfachheit zu erstellen - wie z. B. den Postfix-Operator -, an den sie sonst nie gedacht oder den sie als intellektuell anregend empfunden hätten. Es gibt ein gewisses Etwas, das es trotz seiner Prägnanz nur schöner, wenn nicht wünschenswerter macht.

Mit anderen Worten, einige Leute finden while (*dst++ = *src++);, dass es einfach eine schönere Lösung ist, die Ihnen durch ihre Einfachheit den Atem raubt, so als wäre es ein Pinsel auf Leinwand. Dass Sie gezwungen sind, die Sprache zu verstehen, um die Schönheit zu schätzen, trägt nur zu ihrer Pracht bei.


3
+1 "Manche Leute finden while (* dst ++ = * src ++); um einfach eine schönere Lösung zu sein". Bestimmt. Leute, die die Assemblersprache nicht verstehen, verstehen nicht wirklich, wie elegant C sein kann, aber diese bestimmte Code-Anweisung ist ein leuchtender Stern.
der Blechmann

"Brauchen wir den Postfix-Operator nicht mehr? Wahrscheinlich nicht." - Ich muss an Turing-Maschinen denken. Haben wir jemals automatische Variablen gebraucht, die forAnweisung, zum Teufel sogar while(wir haben es gotoimmerhin)?

@ Bernd: Ha! Stellen Sie sich Verschlüsse vor! Verschlüsse! Skandalös! lol
Brian M. Hunt

Verschlüsse! Wie lächerlich. Gib mir einfach ein Band! Im Ernst, was ich meinte, ist, dass ich nicht denke / brauche / ein Feature sollte das primäre Entscheidungskriterium sein (es sei denn, Sie möchten, sagen wir, lisp). IME ich (ja, müssen ) , um die Postfix - Operatoren fast ausschließlich, und müssen nur selten das Präfix ein , statt.

2

Schauen wir uns die ursprüngliche Begründung von Kernighan & Ritchie an (Original K & R Seite 42 und 43):

Das Ungewöhnliche ist, dass ++ und - entweder als Präfix oder als Postfix verwendet werden können. (...) In dem Kontext, in dem kein Wert gewünscht wird (..), wählen Sie je nach Geschmack Präfix oder Postfix. Es gibt aber Situationen, in denen der eine oder andere speziell gefordert ist.

Der Text wird mit einigen Beispielen fortgesetzt, die Inkremente innerhalb des Index verwenden, mit dem expliziten Ziel, " kompakter " Code zu schreiben . Der Grund für diese Operatoren ist die Bequemlichkeit von kompakterem Code.

In den drei angegebenen Beispielen ( squeeze(), getline()und strcat()) wird nur Postfix in Ausdrücken verwendet, die die Indizierung verwenden. Die Autoren vergleichen den Code mit einer längeren Version, die keine eingebetteten Inkremente verwendet. Dies bestätigt, dass der Schwerpunkt auf Kompaktheit liegt.

K & R heben auf Seite 102 die Verwendung dieser Operatoren in Kombination mit der Dereferenzierung von Zeigern (z . B. *--pund *p--) hervor. Es wird kein weiteres Beispiel gegeben, aber sie verdeutlichen erneut, dass der Vorteil die Kompaktheit ist.

Das Präfix wird zum Beispiel sehr häufig verwendet, wenn ein Index oder ein Zeiger dekrementiert wird, der von dem Ende ausgeht.

 int i=0; 
 while (i<10) 
     doit (i++);  // calls function for 0 to 9  
                  // i is 10 at this stage
 while (--i >=0) 
     doit (i);    // calls function from 9 to 0 

1

Ich werde mit der Prämisse nicht einverstanden sein, dass ++p, p++irgendwie schwer zu lesen oder unklar ist. Eines bedeutet "Inkrementieren von p und dann Lesen von p", das andere bedeutet "Lesen von p und dann Inkrementieren von p". In beiden Fällen befindet sich der Operator im Code genau dort, wo er sich in der Erläuterung des Codes befindet. Wenn Sie also wissen, was ++bedeutet, wissen Sie, was der resultierende Code bedeutet.

Sie können verschleierten Code erstellen jede Syntax, aber ich sehe keinen Fall hier das p++/ ++pist von Natur aus obfuscatory.


1

Dies ist aus der POV eines Elektroingenieurs:

Es gibt viele Prozessoren, die über integrierte Post-Increment- und Pre-Decrement-Operatoren verfügen, um einen LIFO-Stack (Last-In-First-Out) zu verwalten.

es könnte sein:

 float stack[4096];
 int stack_pointer = 0;

 ... 

 #define push_stack(arg) stack[stack_pointer++] = arg;
 #define pop_stack(arg) arg = stack[--stack_pointer];

 ...

Ich weiß nicht, aber das ist der Grund, warum ich erwarten würde, sowohl Präfix- als auch Postfix-Inkrementoperatoren zu sehen.


1

Um Christophes Standpunkt zur Kompaktheit zu unterstreichen, möchte ich Ihnen einen Code aus V6 zeigen. Dies ist ein Teil des ursprünglichen C-Compilers von http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/c/c00.c :

/*
 * Look up the identifier in symbuf in the symbol table.
 * If it hashes to the same spot as a keyword, try the keyword table
 * first.  An initial "." is ignored in the hash.
 * Return is a ptr to the symbol table entry.
 */
lookup()
{
    int ihash;
    register struct hshtab *rp;
    register char *sp, *np;

    ihash = 0;
    sp = symbuf;
    if (*sp=='.')
        sp++;
    while (sp<symbuf+ncps)
        ihash =+ *sp++;
    rp = &hshtab[ihash%hshsiz];
    if (rp->hflag&FKEYW)
        if (findkw())
            return(KEYW);
    while (*(np = rp->name)) {
        for (sp=symbuf; sp<symbuf+ncps;)
            if (*np++ != *sp++)
                goto no;
        csym = rp;
        return(NAME);
    no:
        if (++rp >= &hshtab[hshsiz])
            rp = hshtab;
    }
    if(++hshused >= hshsiz) {
        error("Symbol table overflow");
        exit(1);
    }
    rp->hclass = 0;
    rp->htype = 0;
    rp->hoffset = 0;
    rp->dimp = 0;
    rp->hflag =| xdflg;
    sp = symbuf;
    for (np=rp->name; sp<symbuf+ncps;)
        *np++ = *sp++;
    csym = rp;
    return(NAME);
}

Dies ist Code, der in Samizdat als eine Erziehung mit gutem Stil herumgereicht wurde, und dennoch würden wir ihn heute niemals so schreiben. Sehen Sie sich an, wie viele Nebenwirkungen in ifund unter whileBedingungen aufgetreten sind, und umgekehrt, wie beide forSchleifen keinen inkrementellen Ausdruck haben, da dies im Schleifenkörper erfolgt. Schauen Sie sich an, wie Blöcke nur dann in geschweifte Klammern gehüllt werden, wenn dies unbedingt erforderlich ist.

Ein Teil davon war sicherlich manuelle Mikro-Optimierung der Art , die wir der Compiler erwarten heute für uns zu tun, aber ich würde vorschlagen auch die Hypothese , dass , wenn Ihre gesamte Schnittstelle an den Computer ein einzelnes 80x25 Glas tty ist, Sie wollen Ihren Code um so dicht wie möglich zu sein, damit Sie gleichzeitig mehr davon sehen können.

(Die Verwendung globaler Puffer für alles ist wahrscheinlich ein Kater, wenn man sich die Assemblersprache zunutze macht.)


Vorsichtig behandeln: "ihash = + * sp ++;" Irgendwann hatte C nicht nur den Operator + =, sondern auch = +. Heutzutage ist = + ein Zuweisungsoperator, gefolgt von einem unären Plus, das nichts bewirkt. Es entspricht also "ihash = * sp; sp + = 1;"
gnasher729

@ gnasher729 Ja und später hat es rp->hflag =| xdflg, was meiner Meinung nach ein Syntaxfehler auf einem modernen Compiler sein wird. "Irgendwann" ist genau V6, wie es passiert; Die Umstellung auf +=Form geschah in V7. (Der V6-Compiler akzeptiert nur=+ , wenn ich diesen Code richtig lese - siehe Symbol () in der gleichen Datei.)
zwol

-1

Vielleicht sollten Sie auch an Kosmetik denken.

while( i++ )

wohl "sieht besser aus" als

while( i+=1 )

Aus irgendeinem Grund sieht der Post-Inkrement-Operator sehr ansprechend aus. Es ist kurz, es ist süß und es erhöht alles um 1. Vergleichen Sie es mit dem Präfix:

while( ++i )

"schaut nach hinten", nicht wahr? In der Mathematik wird das Pluszeichen NIE ZUERST angezeigt, es sei denn, jemand ist dumm, etwas Positives ( +0oder ähnliches) anzugeben . Wir sind es gewohnt, OBJEKT + ETWAS zu sehen

Hat das etwas mit der Benotung zu tun? A, A +, A ++? Ich kann Ihnen sagen, dass ich anfangs ziemlich verärgert war, dass die Python-Leute operator++ihre Sprache verloren haben!


1
Ihre Beispiele machen nicht dasselbe. i++Gibt den ursprünglichen Wert von iund i+=1und ++iden ursprünglichen Wert von iplus 1 zurück. Da Sie die Rückgabewerte für den Kontrollfluss verwenden, ist dies von Bedeutung.
David Thornley

Ja, du hast Recht. Aber ich schaue hier nur auf die Lesbarkeit.
Bobobobo

-2

Rückwärts zählen 9 bis 0 inklusive:

for (unsigned int i=10; i-- > 0;)  {
    cout << i << " ";
}

Oder die äquivalente while-Schleife.


1
Wie wäre es for (unsigned i = 9; i <= 9; --i)?
Fredoverflow
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.