Grund für den (Post / Pre) Inkrement-Operator in Java oder C #

8

Vor kurzem bin ich auf die Frage gestoßen, wie die Ausgabe von Code vorhergesagt werden kann, bei dem Post / Pre-Inkrement-Operatoren für Ganzzahlen häufig verwendet werden. Ich bin ein erfahrener C-Programmierer, also fühlte ich mich wie zu Hause, aber die Leute machten Aussagen, dass Java das nur blind von C (oder C ++) kopiert hat und dass dies eine nutzlose Funktion in Java ist.

Abgesehen davon kann ich in Java (oder C #) keinen guten Grund dafür finden (insbesondere unter Berücksichtigung des Unterschieds zwischen Post- / Pre-Formularen), da Sie Arrays nicht manipulieren und in Java als Zeichenfolgen verwenden . Außerdem ist es lange her, als ich mich das letzte Mal mit Bytecode befasst habe, daher weiß ich nicht, ob es eine INCOperation gibt, aber ich sehe keinen Grund dafür

for(int i = 0; i < n; i += 1)

könnte weniger effektiv sein als

for(int i = 0; i < n; i++)

Gibt es einen bestimmten Teil der Sprache, in dem dies wirklich nützlich ist, oder ist dies nur eine Funktion, um C-Programmierer in die Stadt zu bringen?

Nemanja Boric
quelle
2
"Gosling wollte, dass der Code , der von jemandem geschrieben wurde, der Erfahrung mit C hat (nicht mit Java), von jemandem ausgeführt wird, der es gewohnt ist, PostScript auf NeWS auszuführen ..."
Mücke
7
@ TimothyGroote Ich denke nicht, dass dies eine faire Einschätzung ist, da 1) Arrays veränderlich sind, Strings nicht; 2) Sie können einem Zeichen [] keinen String zuweisen oder umgekehrt. Sie sind sicherlich Array-ähnlich, sie werden wahrscheinlich mit Arrays implementiert, und es ist einfach genug, in und aus einem Array zu konvertieren, aber sie sind definitiv nicht dasselbe.
Doval
4
Viele gute Antworten hier, so dass ich nicht noch einmal sagen werde, was andere bereits gesagt haben, außer um zu betonen, dass diese Operatoren schreckliche Funktionen sind. Sie sind sehr verwirrend; Nach über 25 Jahren verwechsle ich immer noch die Prä- und Post-Semantik. Sie fördern schlechte Gewohnheiten wie die Kombination der Bewertung der Ergebnisse mit der Entstehung von Nebenwirkungen. Wären diese Funktionen nicht in C / C ++ / Java / JavaScript / etc enthalten gewesen, wären sie nicht für C # erfunden worden.
Eric Lippert
5
Um direkt auf die Behauptungen Ihres Volkes zu antworten: C # hat nichts von irgendjemandem blind kopiert. Jedes Feature von C # wurde äußerst sorgfältig entworfen. Und das sind keine nutzlosen Funktionen; Sie haben eine Verwendung.
Eric Lippert
2
@EricLippert: Ich denke (hoffe), dass die meisten von uns zustimmen können, dass Pre / Post-Inkremente schrecklich sind, aber in diesem Zusammenhang ... warum wurden sie in C # aufgenommen? Um der Vertrautheit willen?
Phoshi

Antworten:

19

ist dies nur eine Funktion, um C-Programmierer in die Stadt zu bringen

Es ist sicherlich eine Funktion, "C- (oder C ++) - Programmierer in die Stadt zu bringen", aber die Verwendung des Wortes "nur" hier unterschätzt den Wert einer solchen Ähnlichkeit.

  • Es erleichtert das Portieren von Code (oder zumindest Codefragmenten) von C (oder C ++) nach Java oder C #

  • i++ist weniger zu tippen als i += 1und x[i++]=0;ist viel idiomatischer alsx[i]=0;i+=1;

Und ja, Code, der häufig Post / Pre-Inkrement-Operatoren verwendet, ist möglicherweise schwer zu lesen und zu warten, aber für jeden Code, bei dem diese Operatoren missbraucht werden, würde ich keine drastische Steigerung der Codequalität erwarten, selbst wenn die Sprache diese Operatoren nicht bereitstellen würde. Das liegt daran, dass Entwickler, die sich nicht um die Wartbarkeit kümmern, immer Möglichkeiten finden, schwer verständlichen Code zu schreiben.

Verwandte: Gibt es einen Unterschied zwischen den Java- und C ++ - Operatoren? Aus dieser Antwort kann man schließen, dass jedes genau definierte Operatorverhalten in C in Java ähnlich ist, und Java fügt nur einige Definitionen für die Operatorrangfolge hinzu, wenn C ein undefiniertes Verhalten aufweist. Dies sieht nicht nach Zufall aus, es scheint ziemlich beabsichtigt zu sein.

Doc Brown
quelle
10
"Wenn Sie Entwickler haben, die sich nicht um Wartbarkeit kümmern, werden sie immer Wege finden, schwer verständlichen Code zu schreiben" - sehr gut gesagt
Lovis
Schließen Sie sich der allgemeinen Überzeugung der C-Programmierer an, dass Sie nur so viele C-Tastenanschläge in sich haben, bevor Sie sterben? Das Speichern dieses einen Tastendrucks scheint mir ein schlechter Kompromiss zu sein.
Eric Lippert
@EricLippert: Hallo Eric, als einer der Schöpfer von C #, warum klären Sie uns nicht auf und erzählen uns ein wenig über Ihre Motivation, die Operatoren in C # ähnlich wie Java und C zu halten?
Doc Brown
@ DocBrown: Ihre Antwort trifft ziemlich gut auf C # zu. Ich habe oben einen Kommentar hinterlassen.
Eric Lippert
2
@DocBrown: x[i++]=0ist in C idiomatisch, aber ich bin nicht der Meinung, dass es in C # und Java idiomatisch ist. Wie oft stoßen Sie auf solche Phrasen, es sei denn, Sie arbeiten im Bereich Ingenieurwesen / Mathematik, was vermutlich nicht mehr als 1% des Java / C # -Codes in freier Wildbahn ausmacht?
Mladen Jablanović
6

Ich verwende manchmal das Postfix-Inkrement in return-Anweisungen. ZB bedenken Sie Folgendes:

//Java
public class ArrayIterator implements Iterator<T> {
   private final T[] array;
   private int index = 0; 
   public ArrayIterator(T ... array) {
      this.array = array;
   }

   public T next {
      if (! hasNext()) throw new NoSuchElementException();
      return array[index++];   
   }
   ...
}

Das kann man nicht machen index += 1. Zumindest die Postfix-Version kann Ihnen also hin und wieder einige Zeilen ersparen.

Diese Bediener sind jedoch in der Tat nicht erforderlich und können die Menschen verwirren. Ich weiß, dass Scala sich gegen diese Betreiber entschieden hat (Sie haben nur += und -=, wie Sie vorgeschlagen haben), und aufgrund des eher "hochrangigen" Ansatzes vermisse ich sie dort nicht wirklich. Ich hoffe, Java entwickelt sich (langsamer) in die gleiche Richtung.

Java hat jedoch eine Reihe anderer Operatoren, die Sie nicht oft "in the wild" sehen (haben Sie jemals verwendet ^=oder >>>=?), Und niemand kümmert sich darum.

Landei
quelle
1
^=ist eine ziemlich häufige. Sie können es zum Umdrehen von Bitfeldflags, zum Vertauschen von zwei Ganzzahlen ohne temporäre Variable und in einigen Hash- / Verschlüsselungsalgorithmen verwenden. Ich gebe zu, dass ich nie benutzt habe >>>=. (Dann wieder habe ich nie benutzt >>>oder <<<in jedem Produktionsprogramm ...)
Brian S
4

Ich habe im Laufe der Jahre viele Sprachen gebaut, hauptsächlich für spezielle Zwecke. Was passiert ist, dass Sie ein großes Publikum haben und alle Erwartungen haben. Bei der Betrachtung jeder Funktion müssen Sie den Nutzen der Funktion gegen die Kosten abwägen, die möglicherweise gegen Erwartungen verstoßen.

Eine, auf die ich persönlich zurücktreten musste, war die Ganzzahldivision, die normalerweise nach unten abschneidet, oder? Wie 3 / 2 = 1.5 = 1, richtig? Nun, in den frühen Tagen von Fortran hat sich jemand entschieden -3 / 2 = -1.5 = -1(nicht -2). Mit anderen Worten, wenn die Dividende negativ und der Divisor positiv ist, sollte sie abgeschnitten werden . Beachten Sie, was dies bedeutet - in diesem Fall ist der Rest negativ . Dies verstößt gegen die übliche Annahme, dass Rest = Modul. Wenn Sie Grafiken mit ganzzahligen Koordinaten erstellen, kann dies zu Problemen aller Art führen. SO, habe ich einfach Integer - Division truncate hatte nach unten in der Sprache all die Zeit, und sagen , so in der doc.

Und SO, raten Sie mal, was passiert ist? Alles brach los. Die Sprache hatte einen Fehler in der Ganzzahldivision!

Es war nicht gut zu sagen, dass der alte Weg gebrochen war. Es war einfacher, es zu ändern, als es zu argumentieren.

Um Ihre Frage zu beantworten, ist es in Sprachen wie C # und Java einfach einfacher, die ++ - Operatoren (die mir persönlich gefallen) einzuschließen, als zu erklären, warum nicht.

Mike Dunlavey
quelle
4
Als weiteres Beispiel hat die Programmiersprache D (die in vielerlei Hinsicht der nächste Evolutionsschritt von C ++ ist) eine switch-Anweisung, und sie hat die erforderlichen breaks in jeder Klausel beibehalten , obwohl die Sprache kein Durchfallen zulässt. Während D die Abwärtskompatibilität von C ++ mit C verworfen hat, lautet ihre Richtlinie, dass jedes C-Konstrukt, das D beibehalten hat, einen C-Programmierer so wenig wie möglich überraschen sollte.
Doval
@Doval: Ich habe sogar eine Sprache namens D erstellt, wie C, aber mit OO- und Parallelitätsfunktionen. Es ging nirgendwo hin (gnädig) und hat keine Beziehung zu dem D, von dem Sie sprechen. Ich mache Leute verrückt in C? indem break; case ...
ich
1
@MikeDunlavey: Meine Präferenz wäre zu sagen, dass /Ausbeuten doubleoder float( doublein Fällen, in denen beides funktionieren würde) und separate Operatoren für die Division mit Boden, abgeschnitten und "was auch immer tun" sowie Modul, Rest und "gleichmäßig teilbar durch" haben "[letzteres ergibt einen Booleschen Wert]. Ich würde davon ausgehen, dass jedes Ergebnis von int x=y/z;mit erheblicher Wahrscheinlichkeit von dem abweicht, was beabsichtigt war. Daher sollte der Ausdruck kein Ergebnis liefern .
Supercat
In Sprachen ist es nicht ungewöhnlich, separate Operatoren für int-divid-by-int zu haben, die int oder Gleitkomma ergeben. Auch das Vorzeichen des Restes (beeinflusst den Quotienten) variiert stark. Eine Liste der Sprachen finden Sie unter en.wikipedia.org/wiki/Modulo_operation . Ein Teil der Entscheidung für FORTRAN beruhte zweifellos darauf, welche Hardware sie verwendeten (IBM vor 360?) Und wie sie sich mit signierter Dividende und / oder Divisor verhielt.
Phil Perry
Ich weiß, dass sowohl Pascal als auch Python die Operatoren "Yielding Int" von "Yielding Floating Point" trennen, obwohl ich keine gesehen habe, die die Rundungsmodi voneinander trennen. Ich finde es wirklich schwierig, dass Code, der z. B. den Durchschnitt von drei Zahlen berechnen möchte und in Python so geschrieben werden könnte, wie er (a+b+c)//3in den meisten Sprachen geschrieben werden muss, als unangenehmes Durcheinander geschrieben werden muss, um mit der Semantik abgeschnittener Divisionen umzugehen (insbesondere seitdem auf vielen Prozessoren) , eine Etage-Division-durch-drei könnte schneller als eine abgeschnittene gemacht werden!)
Supercat
2

Ja, es ist an sich nichts Wichtiges, sein einziger Vorteil ist der syntaktische Zucker, der das Tippen erleichtert.

Der Grund, warum es sich in erster Linie um C handelt, liegt allein darin, dass der PDP-11, der früher die Grundlage für C war, eine spezielle Anweisung zum Inkrementieren um 1 hatte. In jenen Tagen war es wichtig, diese Anweisung zu verwenden Holen Sie mehr Geschwindigkeit aus dem System, daher die Sprachfunktion.

Ich denke jedoch, dass die Leute das Kommando so sehr mögen, dass sie sich beschweren würden, wenn es nicht da wäre.

gbjbaanb
quelle
Nur aus Gründen der Genauigkeit verfügte der PDP-11 nicht so sehr über einen Befehl zum Vor- oder Nachinkrementieren, sondern über Adressierungsmodi (und Dekrementieren), was bedeutet, dass jeder Befehl , der über ein Register auf den Speicher zugegriffen hat, dies auch konnte auf entweder vor oder nach dem Inkrementieren oder Dekrementieren dieses Registers gleichzeitig eingestellt werden. Die Compiler-Technologie war zu dieser Zeit wirklich nicht in der Lage, dies automatisch durch Optimierung zu tun. Daher war explizite Unterstützung der einzige Weg, um sie zum Laufen zu bringen.
Jules
2

Dies ist eine idiomatische Syntax in C für viele Situationen (wie die von forIhnen zitierte Schleife). Wie andere bereits gesagt haben, kann sie den Übergang zu Java als neue Sprache erleichtern und das Portieren von bereits vorhandenem Code im C-Stil erleichtern. Aus diesen Gründen war es auch eine natürliche Entscheidung, in den frühen Tagen von Java die Einführung der neuen Sprache zu beschleunigen. Dies ist jetzt weniger sinnvoll, da der kompaktere Code die Programmierer dazu zwingt, Syntax zu erlernen, die nicht unbedingt erforderlich ist.

Diejenigen, die es mögen, finden es sowohl bequem als auch natürlich. Es scheint unangenehm, darauf zu verzichten. Die Verwendung gewinnt keine Effizienz, es ist eine Frage der Lesbarkeit. Wenn Sie der Meinung sind, dass Ihre Programme mit diesen Operatoren einfacher zu lesen und zu warten sind, verwenden Sie sie.

Der ursprüngliche Sinn in C war mehr als nur "1 addieren" oder "1 subtrahieren". Es ist viel mehr als nur syntaktischer Zucker oder kleine Leistungssteigerungen. Der Operator in C bedeutet "Inkrementieren (oder Dekrementieren) zum nächsten Element". Wenn Sie einen Zeiger auf etwas haben, das größer als ein Byte ist, und wie in Schritt zur nächsten Adresse *pointer++wechseln, kann das tatsächliche Inkrement 4 oder 8 betragen oder was auch immer erforderlich ist. Der Compiler erledigt die Arbeit für Sie, verfolgt die Größe der Datenelemente und überspringt die richtige Anzahl von Bytes:

int k;
int *kpointer = &k;
*kpointer++ = 7;

Auf meinem Computer addiert dies 4 zu kpointer, es addiert nicht 1. Dies ist eine echte Hilfe in C, um Fehler zu vermeiden und falsche Aufrufe von sizeof () zu speichern. In Java gibt es keine äquivalente Zeigerarithmetik, daher ist dies keine Überlegung. Dort geht es mehr um die Kraft des Ausdrucks als um alles andere.

K. Eno
quelle