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ß src
und dst
werde in diesen alternativen Lösungen unterschiedliche Endwerte haben, aber da es strcpy
sofort 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?
strcpy
Methode auf diese Weise codieren würden (aus Gründen, die Sie bereits erwähnt haben).int c = 0; c++;
Antworten:
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.
quelle
if(x = y)
, ich schwöre!), Sollten Sie die Warnungsoptionen Ihres Compilers anpassen, damit Sie die nicht versehentlich verpassen Warnungen Sie tun möchten.-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 .(void)x
zum Schweigen gebrachten Warnungen sind allgemein bekannte Redewendungen, um ansonsten nützliche Warnungen zum Schweigen zu bringen. Die Anmerkung sieht ein bisschen aus wiepragmas
, im bestenEs 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,
und
... 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
*--x
oder*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 ..." :-) :-)quelle
−−i
und-i++
Konstrukten in C. Ifi
undj
waren 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 ] "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 :
quelle
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: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-1
oder verwenden(x+=1)-1
. Istx++
noch lesbarer.quelle
(++x,x-1)
(Funktionsaufruf, vielleicht?)x++
in den meisten Fällen (eine Ausnahme: Esx
handelt sich um einen vorzeichenlosen Typ, dessen Rang niedriger ist alsint
und dessen Anfangswertx
der Maximalwert dieses Typs ist).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:
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.
quelle
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.
quelle
Ich mag den Postfix-Aperator beim Umgang mit Zeigern (ob ich sie dereferenziere oder nicht), weil
liest sich natürlicher als "zum nächsten Punkt bewegen" als das Äquivalent
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
analysiert als
und nicht
wie manche vielleicht erwarten?
(Denken Sie, das ist schlecht? Siehe Lesen von C-Erklärungen .)
quelle
Zu den subtilen Elementen einer guten Programmierung gehören Lokalisierung und Minimalismus :
Im gleichen Sinne
x++
kann dies als eine Möglichkeit angesehen werden, einen Verweis auf den aktuellen Wert von zu lokalisieren,x
während sofort angezeigt wird , dass Sie - zumindest vorerst - mit diesem Wert fertig sind und zum nächsten übergehen möchten (gültig, obx
einint
oder ist) ein Zeiger oder Iterator). Mit ein wenig Fantasie könnten Sie dies damit vergleichen, dass Sie den alten Wert / die Position vonx
"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,x
kann 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.
quelle
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 Variablex
anschließend inkrementiert wird.Prefix
++x
erzeugt 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:
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.quelle
for
Anweisung, zum Teufel sogarwhile
(wir haben esgoto
immerhin)?Schauen wir uns die ursprüngliche Begründung von Kernighan & Ritchie an (Original K & R Seite 42 und 43):
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()
undstrcat()
) 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.
*--p
und*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.
quelle
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++
/++p
ist von Natur aus obfuscatory.quelle
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:
Ich weiß nicht, aber das ist der Grund, warum ich erwarten würde, sowohl Präfix- als auch Postfix-Inkrementoperatoren zu sehen.
quelle
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 :
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
if
und unterwhile
Bedingungen aufgetreten sind, und umgekehrt, wie beidefor
Schleifen 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.)
quelle
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.)Vielleicht sollten Sie auch an Kosmetik denken.
wohl "sieht besser aus" als
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:
"schaut nach hinten", nicht wahr? In der Mathematik wird das Pluszeichen NIE ZUERST angezeigt, es sei denn, jemand ist dumm, etwas Positives (
+0
oder ähnliches) anzugeben . Wir sind es gewohnt, OBJEKT + ETWAS zu sehenHat 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!quelle
i++
Gibt den ursprünglichen Wert voni
undi+=1
und++i
den ursprünglichen Wert voni
plus 1 zurück. Da Sie die Rückgabewerte für den Kontrollfluss verwenden, ist dies von Bedeutung.Rückwärts zählen 9 bis 0 inklusive:
Oder die äquivalente while-Schleife.
quelle
for (unsigned i = 9; i <= 9; --i)
?