Gibt es einen technischen Grund,> (<) anstelle von! = Zu verwenden, wenn in einer 'for'-Schleife um 1 erhöht wird?

198

Ich sehe fast nie eine forsolche Schleife:

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

Gibt es einen technischen Grund für die Verwendung >oder <anstelle der !=Erhöhung um 1 in einer forSchleife? Oder ist das eher eine Konvention?

Zingam
quelle
104
Es macht es besser lesbar und wenn Sie jemals i++zu i+=2(zum Beispiel) wechseln , läuft es sehr lange (oder möglicherweise für immer). Da Sie dies normalerweise <für Fälle verwenden, in denen Sie den Iterator um mehr als 1 erhöhen , können Sie ihn <auch für den Fall verwenden, dass Sie ihn um 1 erhöhen (aus Gründen der Konsistenz).
Barak Manos
112
5 != iist böse
Slava
27
Sie sollten sich immer bewusst sein, was Sie tun, aber Sie werden trotzdem Fehler machen. Die Verwendung von <verringert die Möglichkeit, dass jemand einen Fehler in diesen Code einführt.
Aurast
40
@OleTange Wenn Sie mit Gleitkommazahlen iterieren, sind Sie bereits geschraubt ..
CaptainCodeman
29
@ Slava ... nicht böse. Sie wurden anscheinend noch nie von einem einfachen Tippfehler in C gebissen, der den Unterschied zwischen if ( i == 5 ) ...und ausmacht if ( i = 5 ). Ganz andere Dinge. Das Umkehren der Operanden verhindert das Problem: Da dies keine Literale sind lvals, können sie nicht zugewiesen werden, und der Compiler schleudert auf den Tippfehler. @ Zingam: gute defensive Programmierung!
Nicholas Carey

Antworten:

303
while (time != 6:30pm) {
    Work();
}

Es ist 18.31 Uhr ... Verdammt, jetzt ist meine nächste Chance, nach Hause zu gehen, morgen! :) :)

Dies zeigt, dass die stärkere Einschränkung die Risiken mindert und wahrscheinlich intuitiver zu verstehen ist.

Antonio
quelle
25
Manchmal verbirgt der Versuch, "Risiken zu mindern", den Fehler. Wenn das Design der Schleife verhindern soll, dass 18:30 Uhr unbemerkt verrutscht, wird durch die Verwendung <der Fehler ausgeblendet, durch die Verwendung wird jedoch !=deutlich, dass ein Problem vorliegt.
Adrian McCarthy
27
@AdrianMcCarthy: Nicht unbedingt; Es könnte nur einen zweiten Fehler verursachen, der die Diagnose erschwert .
Leichtigkeitsrennen im Orbit
4
@AdrianMcCarthy: Das ist irgendwie mein Punkt; Sie könnten ein paar Beispiele davon haben, die Sie noch nicht gesehen haben;)
Leichtigkeitsrennen im Orbit
6
Iteratoren sind ein perfektes Beispiel für den @ AdrianMcCarthy-Punkt, denke ich. Der empfohlene Vergleich für sie ist! = Anstelle von <.
Taekahn
5
Ich habe genau diesen Fehler gesehen (und er hat 3 Monate Debugging verursacht) - aber diese Antwort geht völlig daneben. In dem gegebenen Beispiel ist es unmöglich für die Endbedingung verpassen. Man könnte sogar sagen, dass eine bewusste Wahl! = Über <eine starke Behauptung dieser Tatsache ist. Ein Risiko zu mindern, das definitiv nicht existiert, ist für mich ein Code-Geruch. (Allerdings können Sie auch argumentieren, dass <idiomatisch ist, dass dies ein ebenso guter Grund ist, es zu verwenden wie jeder
andere
95

Es gibt keinen technischen Grund. Es besteht jedoch eine Risikominderung, Wartbarkeit und ein besseres Verständnis des Codes.

<oder >sind stärkere Einschränkungen als !=und erfüllen in den meisten Fällen genau den gleichen Zweck (ich würde sogar in allen praktischen Fällen sagen).

Es gibt doppelte Frage hier ; und eine interessante Antwort .

Ely
quelle
6
Es gibt einige Typen, z. B. Iteratoren, die <oder> nicht unterstützen, aber == und! = Unterstützen.
Simon B
1
Es ist eine for-Schleife mit int . Keine Iteratoren.
Ely
7
"Aber es gibt eine Risikominderung, Wartbarkeit und ein besseres Verständnis des Codes." All dies sind eigentlich technische Gründe. :-)
TJ Crowder
@TJCrowder Was für ein Grund ist es, wenn Sie nur darüber sprechen möchten, was die Maschinen als Ergebnis tun werden?
Brilliand
1
@DanSheppard Ich würde die Risikominderung und Wartbarkeit im Allgemeinen als geschäftlichen Grund und das bessere Verständnis von Code als sozialen Grund betrachten (obwohl diese Gründe in diesem Fall alle auf eine technische Angelegenheit angewendet werden). Überlegungen zur Risikominderung beschränken sich in keiner Weise auf technische Fragen, und Überlegungen zum besseren Verständnis des Codes können sich ändern, wenn Sie mit einer anderen Personengruppe zusammenarbeiten (auch wenn sich die beteiligten Maschinen überhaupt nicht ändern ).
Brilliand
85

Ja, es gibt einen Grund. Wenn Sie eine (einfache alte indexbasierte) for-Schleife wie diese schreiben

for (int i = a; i < b; ++i){}

dann funktioniert es wie erwartet für alle Werte von aund b(dh null Iterationen, wenn a > banstelle von unendlich, wenn Sie verwendet haben i == b;).

Auf der anderen Seite würden Sie für Iteratoren schreiben

for (auto it = begin; it != end; ++it) 

da jeder Iterator einen implementieren sollte operator!=, aber nicht für jeden Iterator ist es möglich, einen bereitzustellen operator<.

Auch bereichsbezogen für Schleifen

for (auto e : v)

sind nicht nur ausgefallener Zucker, sondern sie reduzieren messbar die Wahrscheinlichkeit, falschen Code zu schreiben.

idclev 463035818
quelle
4
Schönes Beispiel. Ich würde mich für einen Fall für !=statt interessieren <. Ich persönlich habe das nie benutzt, denke ich.
Ely
@Elyasin Ich habe keinen guten gefunden, und ich wollte keinen schlechten posten: Stellen Sie sich vor, Sie haben eine Art kreisförmigen Container, in dem Sie die Größe des Containers N um + x modulo erhöhen und die Schleife stoppen möchten, wenn Sie einen bestimmten Index erreichen ... nun, das ist ein wirklich schlechtes Beispiel. Vielleicht finde ich einen besseren
idclev 463035818
Die gleiche Schleife mit! = Anstelle von <macht deutlich, welchen Vorteil <(oder>) in diesem Zusammenhang hat. for (int i=a; i != b; i++) {}
Paul Smith
10
Die Verwendung von etwas anderem als !=Iteratoren ist ziemlich gefährlich, da die Iteratoren manchmal weniger als verglichen werden können (es ist zum Beispiel ein Zeiger), aber der Vergleich ist bedeutungslos (es ist eine verknüpfte Liste).
Zan Lynx
1
Lernen Sie ernsthaft, Leerzeichen zu verwenden.
Miles Rout
70

Sie können so etwas haben

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

Wenn Ihre Schleifenvariable vom inneren Code geschrieben wird, wird die i!=5 , wird diese Schleife möglicherweise nicht unterbrochen. Dies ist sicherer, um auf Ungleichheit zu prüfen.

Über Lesbarkeit bearbeiten . Die Ungleichungsform wird viel häufiger verwendet. Daher ist dies sehr schnell zu lesen, da nichts Besonderes zu verstehen ist (die Gehirnlast wird reduziert, weil die Aufgabe häufig ist). Es ist also cool für die Leser, diese Gewohnheiten zu nutzen.

johan d
quelle
6
Diese Art von innerem "wenn (Bedingung) dann i ++" ist das erste, woran ich dachte.
WernerCD
3
Ich habe erwartet, dass dies die Antwort mit der höchsten Stimme ist. Ich war überrascht, dass ich so weit scrollen musste, um es zu finden. Dies trägt viel mehr dazu bei, einen unerfahrenen Programmierer zu überzeugen, als "Nun, Sie sollten die stärkste Bedingung verwenden". Die anderen Antworten sollten, wenn sie oben bleiben, eine klare und offensichtliche Erklärung dafür enthalten, was mit dem von OP vorgeschlagenen Code schief gehen kann.
msouth
1
@msouth Ich stimme voll und ganz zu, aber mein POV ist ziemlich subjektiv;)
johan d
3
@msouth Ich liebe es, wenn unerwartetes Produktionsverhalten meine Fehler aufdeckt, nicht wahr? :-)
Deworde
3
@msouth Schau, alles was wir tun müssen ist niemals Fehler zu machen und alles wird gut. Einfache.
Deworde
48

Und zu guter Letzt nennt man das defensive Programmierung , was bedeutet, immer den stärksten Fall zu wählen, um aktuelle und zukünftige Fehler zu vermeiden, die das Programm beeinflussen.

Der einzige Fall, in dem keine defensive Programmierung erforderlich ist, besteht darin, dass Zustände durch Vor- und Nachbedingungen bewiesen wurden (was jedoch beweist, dass dies die defensivste aller Programmierungen ist).

Paul Ogilvie
quelle
2
Ein gutes Beispiel dafür: Der Speicher ist anfällig für Bit Flips ( SEUs ), die durch Strahlung verursacht werden. Wenn Sie a intfür eine i != 15Bedingung verwenden, läuft der Zähler für eine lange Zeit, wenn etwas über dem vierten Bit umgedreht wird, insbesondere bei Maschinen mit sizeof(int)64. SEUs sind in höheren Lagen oder im Weltraum (aufgrund höherer Strahlung) ein sehr reales Problem. oder in großen Supercomputern (weil so viel Speicher vorhanden ist).
Josh Sanford
2
Zu denken, dass Ihr Programm in Ordnung ist, wenn Strahlung Ihr Gedächtnis verändert, ist eine optimistische und antikatastrophische Denkweise ... guter Kommentar! :)
Luis Colorado
37

Ich würde argumentieren, dass ein Ausdruck wie

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

ist mehr Ausdruck der Absicht als es ist

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

Ersteres weist eindeutig darauf hin, dass die Bedingung ein Test für eine ausschließliche Obergrenze eines Bereichs ist; Letzteres ist ein binärer Test einer Ausgangsbedingung. Und wenn der Hauptteil der Schleife nicht trivial ist, ist es möglicherweise nicht ersichtlich, dass der Index nur in der forAnweisung selbst geändert wird .

Nicholas Carey
quelle
13
"Ich möchte, dass diese Schleife höchstens fünf Mal ausgeführt wird" vs. "Ich möchte diese Schleife so oft wie nötig ausführen, außer genau fünf Mal, was ich nicht möchte."
Bischof
+1 für die Erwähnung der Obergrenze eines Bereichs. Ich denke, das ist wichtig für numerische Bereiche. ! = definiert keinen richtigen Bereich in einer for-Schleife wie dieser
element11
22

Iteratoren sind ein wichtiger Fall, wenn Sie am häufigsten die !=Notation verwenden:

for(auto it = vector.begin(); it != vector.end(); ++it) {
 // do stuff
}

Zugegeben: In der Praxis würde ich dasselbe schreiben, indem ich mich auf Folgendes stütze range-for:

for(auto & item : vector) {
 // do stuff
}

aber der Punkt bleibt: Normalerweise vergleicht man Iteratoren mit ==oder !=.

Escualo
quelle
2
Iteratoren haben oft nur einen Begriff von Gleichheit / Ungleichheit und keine Ordnung, deshalb verwenden Sie !=sie. In a könntenstd::vector Sie beispielsweise verwenden, da der Iterator an den Index / Zeiger gebunden ist, in a gibt es jedoch keine so strenge Reihenfolge, da er einen Binärbaum durchläuft. <std::set
Martin C.
19

Die Schleifenbedingung ist eine erzwungene Schleifeninvariante.

Angenommen, Sie sehen nicht auf den Körper der Schleife:

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

In diesem Fall wissen Sie zu Beginn der Schleifeniteration, dass idies nicht gleich ist 5.

for (int i = 0; i < 5; ++i)
{
  // ?
}

In diesem Fall wissen Sie zu Beginn der Schleifeniteration, dass ikleiner als ist 5.

Die zweite ist viel, viel mehr Information als die erste, nein? Nun, die Absicht des Programmierers ist (mit ziemlicher Sicherheit) dieselbe, aber wenn Sie nach Fehlern suchen, ist es eine gute Sache, Vertrauen in das Lesen einer Codezeile zu haben. Und die zweite erzwingt diese Invariante, was bedeutet, dass einige Fehler, die Sie im ersten Fall beißen würden, im zweiten Fall einfach nicht auftreten können (oder beispielsweise keine Speicherbeschädigung verursachen).

Sie wissen mehr über den Status des Programms, wenn Sie weniger Code mit <als mit lesen !=. Und auf modernen CPUs benötigen sie genauso viel Zeit wie kein Unterschied.

Wenn Ihr inicht im Schleifenkörper manipuliert wurde und es immer um 1 erhöht wurde und es weniger als anfing 5, würde es keinen Unterschied geben. Aber um zu wissen, ob es manipuliert wurde, müssten Sie jede dieser Tatsachen bestätigen.

Einige dieser Fakten sind relativ einfach, aber Sie können sich irren. Das Überprüfen des gesamten Körpers der Schleife ist jedoch ein Schmerz.

In C ++ können Sie einen indexesTyp wie folgt schreiben :

for( const int i : indexes(0, 5) )
{
  // ?
}

führt dasselbe aus wie eine der beiden oben genannten forSchleifen, auch wenn der Compiler sie auf denselben Code optimiert. Hier wissen Sie jedoch, dass idies nicht im deklarierten Hauptteil der Schleife manipuliert werden kann const, ohne dass der Code den Speicher beschädigt.

Je mehr Informationen Sie aus einer Codezeile herausholen können, ohne den Kontext verstehen zu müssen, desto einfacher ist es, festzustellen, was falsch läuft. <Im Fall von Ganzzahlschleifen erhalten Sie mehr Informationen über den Status des Codes in dieser Zeile als !=.

Yakk - Adam Nevraumont
quelle
13

Ja; OpenMP parallelisiert Schleifen nicht mit der !=Bedingung.

user541686
quelle
13

Es kann vorkommen, dass die Variable iauf einen großen Wert gesetzt ist. Wenn Sie nur den !=Operator verwenden, gelangen Sie in eine Endlosschleife.

LSchueler
quelle
1
Nicht wirklich endlos - bei den meisten Implementierungen von C iwird es lautlos still herumlaufen. Aber ja, höchstwahrscheinlich länger, als Sie bereit sind zu warten.
usr2564301
12

Wie Sie den anderen zahlreichen Antworten entnehmen können, gibt es Gründe, <anstelle von! = Zu verwenden, was in Randfällen, Anfangsbedingungen, unbeabsichtigten Änderungen des Schleifenzählers usw. hilfreich ist.

Ehrlich gesagt denke ich nicht, dass Sie die Wichtigkeit von Konventionen genug betonen können. In diesem Beispiel ist es für andere Programmierer leicht genug zu sehen, was Sie versuchen, aber es wird eine doppelte Aufnahme verursachen. Eine der Aufgaben beim Programmieren ist es, es für alle so lesbar und vertraut wie möglich zu machen. Wenn also jemand Ihren Code aktualisieren / ändern muss, ist es nicht sehr aufwendig, herauszufinden, was Sie in verschiedenen Codeblöcken getan haben . Wenn ich jemanden benutzen sehen würde !=, würde ich annehmen, dass es einen Grund gibt, warum er es benutzt, <und wenn es eine große Schleife wäre, würde ich die ganze Sache durchsehen und herausfinden, was Sie getan haben, was das notwendig gemacht hat ... und das ist es verschwendete Zeit.

RyanP
quelle
12

Wie bereits von Ian Newson gesagt, können Sie eine schwebende Variable nicht zuverlässig durchlaufen und mit beenden !=. Zum Beispiel,

for (double x=0; x!=1; x+=0.1) {}

wird tatsächlich für immer schleifen, da 0.1 nicht genau im Gleitkomma dargestellt werden kann, daher verfehlt der Zähler knapp 1. Mit < endet er.

(Beachten Sie jedoch, dass es im Grunde genommen ein undefiniertes Verhalten ist, ob Sie 0,9999 ... als letzte akzeptierte Zahl erhalten - was gegen die Annahme verstößt - oder bereits bei 1,0000000000000001 beenden.)

links herum
quelle
10

Ich verstehe das Adjektiv "technisch" als Sprachverhalten / Macken und Compiler-Nebenwirkungen wie die Leistung des generierten Codes.

Zu diesem Zweck lautet die Antwort: nein (*). Das (*) lautet "Bitte konsultieren Sie Ihr Prozessorhandbuch". Wenn Sie mit einem Edge-Case-RISC- oder FPGA-System arbeiten, müssen Sie möglicherweise überprüfen, welche Anweisungen generiert werden und was sie kosten. Aber wenn Sie so ziemlich jede herkömmliche moderne Architektur verwendet wird , dann gibt es keine signifikante Prozessorebene Kostendifferenz zwischen lt, eq, neund gt.

Wenn Sie eine Kante Fall verwenden könnten Sie feststellen , dass !=erfordert drei Operationen ( cmp, not, beq) vs zwei ( cmp, blt xtr myo). Wieder RTM in diesem Fall.

Die Gründe sind größtenteils defensiv / härtend, insbesondere wenn mit Zeigern oder komplexen Schleifen gearbeitet wird. Erwägen

// highly contrived example
size_t count_chars(char c, const char* str, size_t len) {
    size_t count = 0;
    bool quoted = false;
    const char* p = str;
    while (p != str + len) {
        if (*p == '"') {
            quote = !quote;
            ++p;
        }
        if (*(p++) == c && !quoted)
            ++count;
    }
    return count;
}

Ein weniger ausgeklügeltes Beispiel wäre, wenn Sie Rückgabewerte verwenden, um Inkremente durchzuführen und Daten von einem Benutzer zu akzeptieren:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;

        std::cin >> step;
        i += step; // here for emphasis, it could go in the for(;;)
    }
}

Versuchen Sie dies und geben Sie die Werte 1, 2, 10, 999 ein.

Sie könnten dies verhindern:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        if (step + i > len)
            std::cout << "too much.\n";
        else
            i += step;
    }
}

Aber was Sie wahrscheinlich wollten, war

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i < len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        i += step;
    }
}

Es gibt auch eine gewisse konventionelle Tendenz <, da das Bestellen in Standardcontainern häufig davon abhängt operator<, dass beispielsweise das Hashing in mehreren STL-Containern die Gleichheit bestimmt, indem es sagt

if (lhs < rhs) // T.operator <
    lessthan
else if (rhs < lhs) // T.operator < again
    greaterthan
else
    equal

Wenn lhsund rhssind eine benutzerdefinierte Klasse, die diesen Code als schreibt

if (lhs < rhs) // requires T.operator<
    lessthan
else if (lhs > rhs) // requires T.operator>
    greaterthan
else
    equal

Der Implementierer muss zwei Vergleichsfunktionen bereitstellen. So <ist der bevorzugte Betreiber geworden.

kfsone
quelle
Zur besseren Lesbarkeit sollte i + = Schritt im for-Steuerteil sein. In der wenn Sie Schritt = 0
Mikkel Christiansen
1
Während dies die Lesbarkeit von gutem Code
verbessern
9

Es gibt verschiedene Möglichkeiten, Code zu schreiben (normalerweise). In diesem Fall gibt es nur zwei Möglichkeiten (drei, wenn Sie <= und> = zählen).

In diesem Fall bevorzugen die Benutzer> und <, um sicherzustellen, dass selbst wenn etwas Unerwartetes in der Schleife passiert (wie ein Fehler), es nicht unendlich wiederholt wird (BAD). Betrachten Sie zum Beispiel den folgenden Code.

for (int i = 1; i != 3; i++) {
    //More Code
    i = 5; //OOPS! MISTAKE!
    //More Code
}

Wenn wir (i <3) verwenden würden, wären wir vor einer Endlosschleife sicher, da dies eine größere Einschränkung darstellt.

Es ist wirklich Ihre Wahl, ob Sie einen Fehler in Ihrem Programm wollen, um das Ganze herunterzufahren oder mit dem Fehler dort weiter zu funktionieren.

Hoffe das hat geholfen!

Zufälliger Programmierer 123
quelle
man könnte argumentieren, dass die Endlosschleife ein guter Indikator für den Fehler wäre, aber ein anderer würde argumentieren, dass i = 5 nicht unbedingt ein Fehler ist
njzk2
7

Der häufigste Grund für die Verwendung <ist die Konvention. Mehr Programmierer denken an Schleifen wie diese als "während der Index im Bereich liegt" und nicht als "bis der Index das Ende erreicht". Es ist wichtig, sich an die Konvention zu halten, wenn Sie können.

Auf der anderen Seite behaupten viele Antworten, dass die Verwendung des <Formulars dazu beiträgt, Fehler zu vermeiden. Ich würde argumentieren, dass dies in vielen Fällen nur dazu beiträgt , Fehler zu verbergen . Wenn der Schleifenindex den Endwert erreichen soll und stattdessen tatsächlich darüber hinausgeht, passiert etwas, das Sie nicht erwartet haben und das eine Fehlfunktion verursachen kann (oder eine Nebenwirkung eines anderen Fehlers sein kann). Dies <wird wahrscheinlich die Entdeckung des Fehlers verzögern. Das!= ist wahrscheinlicher, dass dies zu einem Stillstand, einem Hängenbleiben oder sogar zu einem Absturz führt, wodurch Sie den Fehler früher erkennen können. Je früher ein Fehler gefunden wird, desto billiger ist die Behebung.

Beachten Sie, dass diese Konvention der Array- und Vektorindizierung eigen ist. Wenn Sie nahezu jede andere Art von Datenstruktur durchlaufen, verwenden Sie einen Iterator (oder Zeiger) und suchen direkt nach einem Endwert. In diesen Fällen müssen Sie sicherstellen, dass der Iterator den tatsächlichen Endwert erreicht und nicht überschreitet.

Wenn Sie beispielsweise eine einfache C-Zeichenfolge durchlaufen, schreiben Sie im Allgemeinen häufiger:

for (char *p = foo; *p != '\0'; ++p) {
  // do something with *p
}

als

int length = strlen(foo);
for (int i = 0; i < length; ++i) {
  // do something with foo[i]
}

Zum einen ist die zweite Form langsamer, wenn die Zeichenfolge sehr lang ist, da strlenes sich um einen weiteren Durchlauf durch die Zeichenfolge handelt.

Mit einem C ++ std :: string würden Sie eine bereichsbasierte for-Schleife, einen Standardalgorithmus oder Iteratoren verwenden, selbst wenn die Länge leicht verfügbar ist. Wenn Sie Iteratoren verwenden, gilt die Konvention!= anstatt <wie in:

for (auto it = foo.begin(); it != foo.end(); ++it) { ... }

In ähnlicher Weise muss beim Iterieren eines Baums, einer Liste oder einer Deque normalerweise nach einem Nullzeiger oder einem anderen Sentinel gesucht werden, anstatt zu prüfen, ob ein Index innerhalb eines Bereichs bleibt.

Adrian McCarthy
quelle
3
Sie betonen zu Recht die Bedeutung der Redewendung bei der Programmierung. Wenn ich mir einen Code ansehe und er ein bekanntes Muster hat, muss ich keine Zeit damit verschwenden, auf das Muster zu schließen, sondern kann direkt zum Kern des Musters gehen. Ich denke, das ist mein Hauptproblem bei Yoda-Vergleichen - sie sehen nicht idiomatisch aus, daher muss ich sie am Ende zweimal lesen, um sicherzugehen, dass sie das bedeuten, was ich denke, dass sie bedeuten.
Toby Speight
7

Ein Grund, dieses Konstrukt nicht zu verwenden, sind Gleitkommazahlen. !=ist ein sehr gefährlicher Vergleich mit Floats, da er selten als wahr bewertet wird, selbst wenn die Zahlen gleich aussehen. <oder >beseitigt dieses Risiko.

Ian Newson
quelle
Wenn Ihr Schleifeniterator eine Gleitkommazahl ist, ist der !=Versus <das geringste Ihrer Probleme.
Lundin
7

Es gibt zwei verwandte Gründe für die Befolgung dieser Praxis, die beide damit zu tun haben, dass eine Programmiersprache schließlich eine Sprache ist, die (unter anderem) von Menschen gelesen wird.

(1) Ein bisschen Redundanz. In natürlicher Sprache liefern wir normalerweise mehr Informationen als unbedingt erforderlich, ähnlich wie bei einem Fehlerkorrekturcode. Hier ist die zusätzliche Information, dass die Schleifenvariable i(siehe, wie ich Redundanz hier verwendet habe? Wenn Sie nicht wussten, was "Schleifenvariable" bedeutet, oder wenn Sie den Namen der Variablen vergessen haben, nachdem Sie "Schleifenvariable" gelesen habeni " die volle information) ist während der Schleife kleiner als 5 und unterscheidet sich nicht nur von 5. Die Redundanz verbessert die Lesbarkeit.

(2) Übereinkommen. Sprachen haben bestimmte Standardmethoden, um bestimmte Situationen auszudrücken. Wenn Sie nicht der etablierten Art folgen, etwas zu sagen, werden Sie immer noch verstanden, aber der Aufwand für den Empfänger Ihrer Nachricht ist größer, da bestimmte Optimierungen nicht funktionieren. Beispiel:

Reden Sie nicht über den heißen Brei. Beleuchten Sie einfach die Schwierigkeit!

Der erste Satz ist eine wörtliche Übersetzung einer deutschen Sprache. Das zweite ist eine gebräuchliche englische Sprache, bei der die Hauptwörter durch Synonyme ersetzt werden. Das Ergebnis ist verständlich, aber es dauert viel länger, es zu verstehen:

Nicht um den heißen Brei herumreden. Erkläre einfach das Problem!

Dies gilt auch dann, wenn die in der ersten Version verwendeten Synonyme besser zur Situation passen als die herkömmlichen Wörter in der englischen Sprache. Ähnliche Kräfte wirken, wenn Programmierer Code lesen. Dies ist auch der Grund 5 != iund 5 > ieine seltsame Art, es auszudrücken , es sei denn, Sie arbeiten in einer Umgebung, in der es Standard ist, das Normalere i != 5und i < 5auf diese Weise zu tauschen . Solche Dialektgemeinschaften existieren wahrscheinlich, weil die Konsistenz es einfacher macht, sich an das Schreiben zu erinnern, 5 == ianstatt an das natürliche, aber fehleranfällige i == 5.


quelle
1
Ausgezeichnet . Ebenso würde man den Test nicht als schreiben i < 17+(36/-3)(selbst wenn dies Konstanten sind, die an anderer Stelle verwendet werden; dann müssen Sie ihre Namen aus Gründen der Klarheit aufschreiben !).
usr2564301
6

Die Verwendung relationaler Vergleiche ist in solchen Fällen eher eine beliebte Gewohnheit als alles andere. Es gewann seine Popularität in Zeiten, in denen konzeptionelle Überlegungen wie Iteratorkategorien und ihre Vergleichbarkeit nicht als hohe Priorität angesehen wurden.

Ich würde sagen, dass man nach Möglichkeit lieber Gleichheitsvergleiche als relationale Vergleiche verwenden sollte, da Gleichheitsvergleiche weniger Anforderungen an die zu vergleichenden Werte stellen. Sein EqualityComparable ist eine geringere Anforderung als sein LessThanComparable .

Ein weiteres Beispiel, das die breitere Anwendbarkeit des Gleichheitsvergleichs in solchen Kontexten demonstriert, ist das beliebte Rätsel bei der Implementierung der unsignedIteration bis hinunter 0. Es kann gemacht werden als

for (unsigned i = 42; i != -1; --i)
  ...

Beachten Sie, dass das oben Gesagte sowohl für signierte als auch für nicht signierte Iterationen gleichermaßen gilt, während die relationale Version mit nicht signierten Typen zusammenbricht.

Ameise
quelle
2

Neben den Beispielen, bei denen sich die Schleifenvariable (unbeabsichtigt) im Körper ändert, gibt es andere Gründe, die Operatoren kleiner als oder größer als zu verwenden:

  • Negationen erschweren das Verständnis von Code
  • <oder >ist nur ein Zeichen, aber !=zwei
MiCo
quelle
4
Die zweite Kugel ist bestenfalls ein leichtfertiger Grund. Der Code sollte korrekt und klar sein. Kompakt ist weit weniger wichtig.
Jonathan Leffler
@JonathanLeffler Nun, in einer TDD-REPL ist dieses zusätzliche Zeichen eine zusätzliche Nanosekunde für das Parsen und eine Milliarde Iterationen, um den durch das Schreiben verursachten Fehler zu finden, der 5 != $inur eine ganze Sekunde meines Lebens in Anspruch genommen hat. Äh ... kichern
Bischof
1

Zusätzlich zu den verschiedenen Personen, die erwähnt haben, dass es das Risiko mindert, reduziert es auch die Anzahl der Funktionsüberladungen, die für die Interaktion mit verschiedenen Standardbibliothekskomponenten erforderlich sind. Wenn Sie beispielsweise möchten, dass Ihr Typ in einem gespeichert std::setoder als Schlüssel für std::mapeinige der Such- und Sortieralgorithmen verwendet oder mit diesen verwendet wird, werden in der Standardbibliothek normalerweise std::lessObjekte verglichen, da die meisten Algorithmen nur eine strikte schwache Reihenfolge benötigen . Daher wird es zur Gewohnheit, <Vergleiche anstelle von !=Vergleichen zu verwenden (wo dies natürlich sinnvoll ist).

Andre Kostur
quelle
1
Sie haben vielleicht Recht mit der Anzahl der Überladungen, aber wenn Sie die Anforderungen an die Objekte berücksichtigen, können Sie für fast jedes Objekt einen !=Operator definieren , aber es ist nicht immer möglich, eine strikte schwache Reihenfolge zu definieren. In diesem Sinne !=wäre es besser, weil es weniger Anforderungen an den Typ stellt. Ich bleibe aber immer noch bei der <.
idclev 463035818
0

Aus syntaktischer Sicht gibt es kein Problem, aber die Logik hinter diesem Ausdruck 5!=iist nicht solide.

Meiner Meinung nach ist die Verwendung !=zum Festlegen der Grenzen einer for-Schleife nicht logisch sinnvoll, da eine for-Schleife den Iterationsindex entweder erhöht oder verringert. Setzen Sie die Schleife daher so, dass sie iteriert, bis der Iterationsindex außerhalb der Grenzen liegt (!= ist es nicht a zu etwas) liegt ordnungsgemäße Umsetzung.

Es wird funktionieren, aber es ist anfällig für schlechtes Benehmen , da die Grenzdatenverarbeitung verloren bei der Verwendung !=für eine inkrementelle Problem (was bedeutet , dass Sie von Anfang an wissen , ob es erhöht oder verringert), das ist , warum statt !=der <>>==>verwendet werden.

Cosmin Gherghel
quelle
2
Die Logik ist vollkommen in Ordnung. Wann iwird 5die Schleife beendet? Wolltest du noch etwas sagen?
Dan Getz
1
Wenn die Daten aufgrund von Hitze nicht korrekt übertragen werden, werden Ihre Codes durch elektromagnetische Einflüsse für immer ausgeführt. Wenn Sie dies auf einer normalen Workstation oder einem Server mit ECC-RAM tun, gibt es kein Problem (weder logisch, technisch noch physisch)
Markus