'<' versus '! =' als Bedingung in einer 'for'-Schleife?

31

Angenommen, Sie haben die folgende forSchleife *:

for (int i = 0; i < 10; ++i) {
    // ...
}

Das könnte man auch so schreiben:

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

Die Endergebnisse sind die gleichen. Gibt es also echte Argumente dafür, eins übereinander zu verwenden? Persönlich benutze ich die erstere, falls iaus irgendeinem Grund etwas schief geht und der Wert 10 übersprungen wird.

* Entschuldigen Sie die Verwendung von magischen Zahlen, aber es ist nur ein Beispiel.

gablin
quelle
18
Die int i = 10; while(i --> 0) { /* stuff */ }
bessere
@ Glowcoder der Pfeil-Operator ist mein Favorit
Carson Myers
3
@glowcoder, schön aber es läuft von hinten durch. Das möchten Sie vielleicht nicht immer.
2
@ SuperElectric, es analysiert wiewhile ((i--) > 0)
Kristopher Johnson
17
Es gibt eine (wahrscheinlich apokryphe) Geschichte über einen Industrieunfall, der durch einen While-Loop-Test für einen Sensoreingang von! = MAX_TEMP verursacht wurde. Leider ging eines Tages die Sensoreingabe von weniger als MAX_TEMP auf mehr als MAX_TEMP über, ohne dass jedes Mal MAX_TEMP durchlaufen wurde. Der Prozess überhitzte sich, ohne entdeckt zu werden, und es kam zu einem Brand. Manchmal gibt es einen Unterschied zwischen! = Und <.
Charles E. Grant

Antworten:

59

Der Grund, sich für das eine oder andere zu entscheiden, liegt in der Absicht und damit in der besseren Lesbarkeit .

Absicht: Die Schleife sollte so lange laufen, wie sie ikleiner als 10 ist, und nicht so lange, wie sie inicht gleich 10 ist. Auch wenn letzteres in diesem speziellen Fall gleich sein mag, ist es nicht das, was Sie meinen, und sollte es auch nicht so geschrieben werden.

Lesbarkeit: Wenn Sie aufschreiben, was Sie meinen, ist es auch einfacher zu verstehen. Wenn Sie zum Beispiel verwenden i != 10, könnte sich jemand fragen, ob in der Schleife ein Weg igrößer als 10 werden könnte und ob die Schleife fortgesetzt werden sollte die for-Anweisung, aber das bedeutet nicht, dass die Leute es nicht tun und als Ergebnis erwarten die Betreuer es).

Deckard
quelle
3
Nein, die Schleife sollte nicht so lange laufen, wie i"kleiner als 10" ist. Sie sollte so lange laufen, wie die (in diesem Fall) obere Grenze nicht erreicht wird. Wenn Sie das C ++ - Intervall / Iterator-Bereichs-Grundprinzip verwenden, ist es weitaus logischer, dies über auszudrücken !=als <.
Konrad Rudolph
14
@Konrad Dem kann ich gar nicht widersprechen. Es geht nicht darum, dogmatisch zu sein, sondern das Konstrukt zu wählen, das die Absicht der Bedingung am besten ausdrückt. Wenn Sie sich also dafür entscheiden, eine Schleife von 0 bis nach oben zu durchlaufen, drücken Sie dies <genau aus.
Deckard
6
@Konrad, du verpasst den Punkt. Der Inkrement-Operator in dieser Schleife macht deutlich, dass es sich bei der Schleifenbedingung um eine Obergrenze und nicht um einen Identitätsvergleich handelt. Wenn Sie dekrementieren würden, wäre es eine Untergrenze. Wenn Sie über eine nicht geordnete Sammlung iterieren, ist Identität möglicherweise die richtige Bedingung.
Alex Feinman
3
@Alex das Inkrement war nicht mein Punkt. Ein Intervall nicht notwendigerweise selbst hat einen Auftrag. Es iteriert nur… na ja, etwas, bis es das Ende erreicht. Zum Beispiel Elemente in einer Hash-Menge. In C ++ erfolgt dies mit Iteratoren und einer forSchleife. Mit <hier wäre dumm. Auch Deckard scheint das anzuerkennen. Ich stimme seinem Kommentar als Antwort auf meinen sehr zu.
Konrad Rudolph
1
Beachten Sie, dass Sie!=
SF verwenden
48

Das <Muster ist im Allgemeinen verwendbar, auch wenn das Inkrement nicht genau 1 ist.

Auf diese Weise können Schleifen unabhängig von ihrer tatsächlichen Ausführung auf eine einzige übliche Weise ausgeführt werden.


quelle
6
Es erlaubt auch i, innerhalb der Schleife völlig sicher zu modifizieren, wenn nötig.
Matthew Scharley
1
oder wenn 'i' total unsicher modifiziert ist ... Ein anderes Team hatte ein seltsames Serverproblem. Es wurde eine 100% ige CPU-Auslastung gemeldet und es muss ein Problem mit dem Server oder dem Überwachungssystem geben, oder? Nein, ich habe eine Schleifenbedingung gefunden, die von einem erfahrenen Senior-Programmierer mit demselben Problem geschrieben wurde, von dem wir sprechen.
JQA
12
Abgesehen davon, dass nicht alle C ++ for-Schleifen verwendet werden können <, z. B. Iteratoren über eine Sequenz, und dies !=wäre eine einzige gängige Methode, um Schleifen in C ++ auszuführen.
David Thornley
@ SnOrfus: Ich analysiere diesen Kommentar nicht ganz. Sie werden im Allgemeinen keine zuverlässigen Ausnahmen für das Inkrementieren eines Iterators erhalten (obwohl es spezifischere Situationen gibt, in denen Sie dies tun).
David Thornley
Nun, ich folge dem <Muster, weil es einen Tastendruck anstelle von zwei mit dem >Muster und vier mit erfordert !=. Es ist wirklich eine Frage der OCD-ähnlichen Effizienz.
Spoike
21

In C ++ lautet die Empfehlung von Scott Myers in „Effektiveres C ++“ (Punkt 6), immer die zweite zu verwenden, es sei denn, Sie haben einen Grund, dies nicht zu tun, da dies bedeutet, dass Sie für Iterator- und Ganzzahlindizes dieselbe Syntax verwenden, sodass Sie nahtlos zwischen intIterator und Iterator wechseln können ohne Änderung der Syntax.

In anderen Sprachen trifft dies nicht zu, weshalb ich <es wahrscheinlich vorziehen würde, weil Thorbjørn Ravn Andersen das so meint.

By the way, neulich war ich Rücksprache mit einem anderen Entwickler und er sagte , der Grund zu bevorzugen <über !=, weil inicht versehentlich um mehr als eins erhöhen, und das könnte die Abbruchbedingung verursacht nicht erfüllt werden; Das ist meiner Meinung nach eine Menge Unsinn.


quelle
17
"Jede Menge Unsinn" ... bis zu dem Tag, an dem Sie versehentlich ein zusätzliches i ++ im Körper der Schleife haben.
2
+1, vor allem für "Unfug", weil es ist. Wenn der Schleifenkörper den Zähler versehentlich inkrementiert, treten weitaus größere Probleme auf.
Konrad Rudolph
12
@B Tyler, wir sind nur Menschen und es sind schon größere Fehler passiert. Betrachten Sie <als defensiveres Programmieren als !=damals ...
2
@ Thorbjørn Ravn Andersen - Ich sage nicht, dass ich Ihnen nicht zustimme. <ist defensiver und meine Kollegen hassen es, wenn ich schreibe, !=also nicht. Es gibt jedoch einen Fall für !=in C ++ und das ist, was ich vorgestellt habe.
9
Ein Szenario, in dem ein versehentliches Extra auftreten kann, i++besteht darin, eine while-Schleife in eine for-Schleife umzuwandeln. Ich bin mir nicht sicher, ob es besser ist, die Hälfte der Iterationen zu haben als eine (nahezu) Endlosschleife. Letzteres würde den Fehler wahrscheinlich offensichtlicher machen.
Ak2
12

Die Verwendung von (i <10) ist meiner Meinung nach sicherer. Es wird die maximale Anzahl möglicher Beendigungsfälle abgefangen - alles, was größer oder gleich 10 ist. Vergleichen Sie dies mit dem anderen Fall (i! = 10); es fängt nur einen möglichen Abbruchfall auf - wenn ich genau 10 bin.

Ein Nebenprodukt davon ist, dass es die Lesbarkeit verbessert. Sollte das Inkrement etwas anderes 1 sein, kann dies außerdem dazu beitragen, die Wahrscheinlichkeit eines Problems zu minimieren, falls wir beim Schreiben des Beendigungsfalls einen Fehler machen.

Erwägen:

1) for (i = 1; i < 14; i+=2)

2) for (i = 1; i != 14; i+=2)

Obwohl beide Fälle wahrscheinlich fehlerhaft / falsch sind, ist der zweite wahrscheinlich MEHR falsch, da er nicht beendet wird. Der erste Fall wird beendet und es besteht eine höhere Wahrscheinlichkeit, dass er an der richtigen Stelle beendet wird, obwohl 14 wahrscheinlich die falsche Zahl ist (15 wäre wahrscheinlich besser).

Sparky
quelle
Der erste Fall mag richtig sein! Wenn Sie über alle natürlichen Zahlen unter 14 iterieren möchten, gibt es keinen besseren Weg, dies auszudrücken - die Berechnung der "richtigen" Obergrenze (13) wäre einfach dumm.
Maaartinus
9

Hier ist eine weitere Antwort, die noch niemand gefunden zu haben scheint. forSchleifen sollten verwendet werden, wenn Sie eine Sequenz durchlaufen müssen . Verwenden !=ist die präziseste Methode, um die Abschlussbedingung für die Schleife anzugeben. Die Verwendung eines weniger restriktiven Operators ist jedoch eine sehr verbreitete Defensiv-Programmiersprache. Für ganze Zahlen spielt es keine Rolle - es ist nur eine persönliche Wahl ohne ein spezifischeres Beispiel. Durchlaufen Sie Sammlungen mit Iteratoren, die Sie !=aus den von anderen angegebenen Gründen verwenden möchten . Wenn Sie Sequenzen von floatoder berücksichtigen double, dann möchten Sie dies unbedingt vermeiden !=.

Was ich darauf hinweisen wollte, ist, dass fores verwendet wird, wenn Sie über eine Sequenz iterieren müssen. Die generierte Sequenz hat einen Startpunkt, ein Intervall und eine Abbruchbedingung. Diese sind in der forErklärung kurz angegeben. Wenn Sie feststellen, dass Sie entweder (1) den Step-Teil von foroder (2) so etwas wie truedie Guard-Bedingung nicht angeben, sollten Sie keine forSchleife verwenden!

Die whileSchleife wird verwendet, um die Verarbeitung fortzusetzen, solange eine bestimmte Bedingung erfüllt ist. Wenn Sie keine Sequenz verarbeiten, möchten Sie wahrscheinlich whilestattdessen eine Schleife. Die Argumente für die Schutzbedingung sind hier ähnlich, aber die Entscheidung zwischen a whileund einer forSchleife sollte sehr bewusst sein. Die whileSchleife wird in C ++ - Kreisen IMO unterschätzt.

Wenn Sie eine Sammlung von Elementen verarbeiten (eine sehr häufige forVerwendung von Schleifen), sollten Sie wirklich eine spezialisiertere Methode verwenden. Leider std::for_eachist in C ++ aus mehreren Gründen ziemlich schmerzhaft. In vielen Fällen führt das Trennen des Körpers einer forSchleife in einer freistehenden Funktion (obwohl dies etwas schmerzhaft ist) zu einer viel saubereren Lösung. Wenn sie mit Sammlungen arbeiten, betrachten std::for_each, std::transformoder std::accumulate. Die Implementierung vieler Algorithmen wird auf diese Weise präzise und kristallklar. Ganz zu schweigen davon, dass Sie sich beim Isolieren des Schleifenkörpers in eine separate Funktion / Methode auf den Algorithmus, seine Eingabeanforderungen und Ergebnisse konzentrieren müssen.

Wenn Sie Java, Python, Ruby oder sogar C ++ 0x verwenden , sollten Sie eine geeignete Auflistung für jede Schleife verwenden. Wenn C ++ - Compiler diese Funktion implementieren, forverschwinden eine Reihe von Schleifen sowie diese Art von Diskussionen.

D. Shawley
quelle
2
"Die Verwendung eines weniger restriktiven Operators ist jedoch eine sehr verbreitete defensive Programmiersprache." Das bringt es mehr oder weniger auf den Punkt.
James P.
+1 für die Erörterung der Unterschiede in der Absicht im Vergleich zu while, forund foreach(oder Sprache gleichwertig)
Kevin Peno
Genau genommen wird die whileSchleife verwendet, um die Verarbeitung fortzusetzen, bis eine bestimmte Bedingung nicht mehr erfüllt ist
Alnitak,
@Alnitak - D'oh ... Ich werde das beheben :)
D.Shawley
Ich war durch die zwei möglichen Bedeutungen von "weniger restriktiv" verwirrt: Es könnte sich darauf beziehen, dass der Operator in den übergebenen Werten nachsichtig ist ( <) oder dass der Operator in den fehlgeschlagenen Werten nachsichtig ist ( !=).
Mateen Ulhaq
4

Wenn Sie "weniger als" verwenden, ist dies (normalerweise) semantisch korrekt. Sie meinen also, dass Sie bis zu i10 zählen müssen, damit "weniger als" Ihre Absichten klar zum Ausdruck bringt. Die Verwendung von "ungleich" funktioniert offensichtlich in Fällen virtueller Anrufe, vermittelt jedoch eine etwas andere Bedeutung. In einigen Fällen ist dies vielleicht das, was Sie brauchen, aber meiner Erfahrung nach war dies nie der Fall. Wenn Sie wirklich einen Fall igehabt hätten, in dem möglicherweise mehr oder weniger als 10 vorliegen, Sie aber eine Schleife ausführen möchten, bis der Wert 10 erreicht ist, müsste dieser Code wirklich sehr deutlich kommentiert werden und könnte wahrscheinlich besser mit einem anderen Konstrukt wie z als whileSchleife vielleicht.

Steve
quelle
2

Ein Grund, warum ich a less thangegenüber a favorisieren würde, not equalsist, als Wache zu fungieren. Unter bestimmten Umständen (schlechte Programmierung oder Desinfektion) kann das not equalsübersprungen werden, obwohl less thanes immer noch gültig ist .

Hier ist ein Beispiel, in dem das Fehlen einer Desinfektionsprüfung zu merkwürdigen Ergebnissen geführt hat: http://www.michaeleisen.org/blog/?p=358

James P.
quelle
0

Hier ist ein Grund, warum Sie vielleicht lieber verwenden <als !=. Wenn Sie eine Sprache mit globalem Variablenumfang verwenden, was passiert, wenn anderer Code geändert wird i? Wenn Sie verwenden , <anstatt !=, passiert das Schlimmste, ist , dass die Iteration beendet schneller: vielleicht einige andere Code - Schritten idurch Zufall, und Sie ein paar Iterationen in der for - Schleife überspringen. Aber was passiert, wenn Sie eine Schleife von 0 bis 10 durchführen und die Schleife auf 9 wechselt und einige falsch geschriebene Thread-Inkremente iaus irgendeinem seltsamen Grund. Dann beendet Ihre Schleife diese Iteration und erhöht sie, iso dass der Wert jetzt 11 ist. Da die Schleife die Ausgangsbedingung ( inie gleich 10) übersprungen hat , wird sie nun eine Endlosschleife ausführen.

Es muss nicht unbedingt eine besonders ausgeflippte Logik vom Typ Threading und globale Variablen sein, die dies verursacht. Möglicherweise schreiben Sie gerade eine Schleife, die zurückverfolgt werden muss. Wenn Sie iinnerhalb der Schleife mutieren und Ihre Logik durcheinander bringen, !=ist es weniger wahrscheinlich, dass Sie sich in einer Endlosschleife befinden , wenn Sie eine Obergrenze anstelle von a haben .

Tom Morris
quelle
3
Dies scheint natürlich ein perfektes Argument für for-each-Schleifen und einen funktionaleren Programmierstil im Allgemeinen zu sein. Aber warum sollten Sie das tun, wenn veränderbare Variablen so viel mehr Spaß machen ?
Tom Morris
3
Ich denke nicht, dass das ein schrecklich guter Grund ist. In beiden Fällen muss ein Fehler gefunden und behoben werden, aber eine Endlosschleife macht einen Fehler eher offensichtlich.
Ak2
0

In der Embedded-Welt, insbesondere in lauten Umgebungen, können Sie sich nicht darauf verlassen, dass der Arbeitsspeicher ordnungsgemäß funktioniert. In einer Bedingung (für, während, wenn), in der Sie mit '==' oder '! =' Vergleichen, laufen Sie immer Gefahr, dass Ihre Variablen den entscheidenden Wert überspringen, der die Schleife beendet - dies kann katastrophale Folgen haben - Mars Lander Level Konsequenzen. Die Verwendung von "<" oder ">" in der Bedingung bietet eine zusätzliche Sicherheitsstufe, um die "unbekannten Unbekannten" abzufangen.

Im ursprünglichen Beispiel iwürde der Vergleich '<' den Fehler sofort abfangen und die Schleife verlassen , wenn er aus unerklärlichen Gründen auf einen Wert größer als 10 katapultiert würde, aber '! =' Würde weiter hochzählen, bis er ium die 0 herum und zurück gewickelt ist bis 10.

Oosterwal
quelle
2
Eine andere verwandte Variante gibt es mit Code, for (i=4; i<length; i++) csum+=dat[i];bei dem legitime Pakete immer lengthmindestens vier haben, aber möglicherweise fehlerhafte Pakete empfangen werden. Wenn unterschiedliche Pakettypen unterschiedliche Längenanforderungen haben, kann es wünschenswert sein, die Länge als Teil der Verarbeitung zu validieren, die für jeden Pakettyp unterschiedlich ist, aber die Prüfsumme zuerst als Teil der gemeinsamen Logik zu validieren. Wenn ein Paket mit geringer Länge empfangen wird, spielt es keine Rolle, ob die Prüfsummenberechnung "gut" oder "schlecht" ausgibt, aber es sollte nicht abstürzen.
Supercat