Warum ist iostream::eof innerhalb einer Schleife ist die Bedingung (D. H. " beim (!stream.eof())`) als falsch?

Ich habe gerade einen Kommentar in dieser Antwort sagen, dass die Verwendung von iostream::eof in einer Schleife, Bedingung ist "nahezu sicher falsch". Ich verwende in der Regel so etwas wie while(cin>>n) - ich denke mal, dass implizit prüft EOF.

Warum ist die überprüfung für eof explizit mit while (!cin.eof()) falsch?

Wie unterscheidet es sich von der Verwendung von scanf("...",...)!=EOF in C (was ich oft verwenden, ohne Probleme)?

  • Ausgewählte Antwort
    Xeo
    9. April 2011

    Weil iostream::eof wird nur return true nach dem Lesen das Ende des Streams. Es tut nicht zeigen, dass Sie das nächste Lesen wird das Ende des Streams.

    Betrachten Sie diese (und übernehmen dann weiter Lesen, werden am Ende des Streams):

    while(!inStream.eof()){
      int data;
      // yay, not end of stream yet, now read ...
      inStream >> data;
      // oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
      // do stuff with (now uninitialized) data
    }
    

    Gegen dieses:

    int data;
    while(inStream >> data){
      // when we land here, we can be sure that the read was successful.
      // if it wasn't, the returned stream from operator>> would be converted to false
      // and the loop wouldn't even be entered
      // do stuff with correctly initialized data (hopefully)
    }
    

    Und auf Ihre zweite Frage: Da

    if(scanf("...",...)!=EOF)
    

    ist das gleiche wie

    if(!(inStream >> data).eof())
    

    und nicht das gleiche wie

    if(!inStream.eof())
        inFile >> data
    

3 Antworten

  • Nawaz
    9. April 2011

    Denn wenn die Programmierer nicht schreiben while(stream >> n), Sie könnten dies schreiben:

    while(!stream.eof())
    {
        stream >> n;
        //some work on n;
    }
    

    Hier ist das problem, man kann nicht some work on n, ohne zuerst überprüfen, ob der stream Lesen erfolgreich war, denn wenn es nicht erfolgreich war, Ihr some work on n würde zu unerwünschten Ergebnis.

    Der springende Punkt ist, dass, eofbit, badbit oder failbit gesetzt werden , nachdem ein Versuch gemacht wird, aus dem stream gelesen. Also, wenn stream >> n scheitert, dann eofbit, badbit oder failbit gesetzt ist, unmittelbar, so dass Ihr weitere Redewendungen, wenn Sie schreiben while (stream >> n), weil das zurückgegebene Objekt stream konvertiert zu false, wenn es einige Fehler in der Lesung aus dem stream und somit die Schleife beendet. Und es konvertiert zu true wenn das Lesen erfolgreich war und die Schleife fortgesetzt.

  • sly
    24. November 2012

    Bottom-line top: Mit der richtigen Handhabung von white-space, das folgende ist, wie eof verwendet werden kann (und sogar zuverlässiger sein als fail() für die Fehlerprüfung):

    while( !(in>>std::ws).eof() ) {  
       int data;
       in >> data;
       if ( in.fail() ) /* handle with break or throw */; 
       // now use data
    }    
    

    (Dank Tony D für die Anregung zu markieren die Antwort. Siehe sein Kommentar unten für ein Beispiel, warum dies ist robuster.)


    Das wichtigste argument gegen die Verwendung von eof() scheint zu sein, fehlt eine wichtige Feinheit, über die Rolle der weißen Raum. Mein Vorschlag ist, dass die überprüfung eof() explizit ist nicht nur nicht "immer falsch"- das scheint zu einem überwiegenden Meinung in diesen und ähnlichen SO threads --, aber mit der richtigen Handhabung von white-space, sorgt es für eine saubere und zuverlässige Fehlerbehandlung, und ist das immer die richtige Lösung (obwohl, nicht unbedingt die tersest).

    Um zusammenzufassen, was wird vorgeschlagen, die als die "richtige" Kündigung und lese-Reihenfolge ist folgende:

    int data;
    while(in >> data) {  /* ... */ }
    
    // which is equivalent to 
    while( !(in >> data).fail() )  {  /* ... */ }
    

    Das scheitern aufgrund von lese-Versuch über eof als Abbruchbedingung. Dies bedeutet, dass es keine einfache Möglichkeit, zu unterscheiden zwischen einem erfolgreichen stream-und eine, die wirklich scheitert aus anderen Gründen als der eof. Nehmen Sie die folgenden Ströme:

    • 1 2 3 4 5<eof>
    • 1 2 a 3 4 5<eof>
    • a<eof>

    while(in>>data) endet mit einem set failbit für alle drei geben. In der ersten und Dritten, eofbit eingestellt. Also vorbei an den loop braucht man sehr hässlich zusätzliche Logik zu unterscheiden, dem richtigen input (1.) durch unsachgemäße lieben (2. und 3.).

    In der Erwägung, dass, nehmen Sie die folgenden:

    while( !in.eof() ) 
    {  
       int data;
       in >> data;
       if ( in.fail() ) /* handle with break or throw */; 
       // now use data
    }    
    

    Hier in.fail() überprüft, ob, wie lange, wie es ist, etwas zu Lesen, es ist die richtige. Es ist Ziel ist nicht eine bloße while-loop-terminator.

    So weit so gut, aber was passiert, wenn es Leerzeichen im stream -- was sich anhört wie die großen Bedenken gegen eof() als terminator?

    Wir brauchen nicht zu kapitulieren unsere Fehler-handling; einfach nur Essen, bis die white-space:

    while( !in.eof() ) 
    {  
       int data;
       in >> data >> ws; // eat whitespace with std::ws
       if ( in.fail() ) /* handle with break or throw */; 
       // now use data
    }
    

    std::ws überspringt alle Potenzial (null oder mehr) Leerzeichen in den Strom während der Einstellung der eofbit und nicht failbit. Also, in.fail() funktioniert wie erwartet, solange es mindestens einen Daten zu Lesen. Wenn alle-leer-streams sind auch in Ordnung, dann die richtige form ist:

    while( !(in>>ws).eof() ) 
    {  
       int data;
       in >> data; 
       if ( in.fail() ) /* handle with break or throw */; 
       /* this will never fire if the eof is reached cleanly */
       // now use data
    }
    

    Zusammenfassung: Ein richtig konstruiertes while(!eof) ist nicht nur möglich und nicht falsch, sondern es erlaubt, Daten lokalisiert werden, die in Umfang, und liefert eine sauberere Trennung der Fehlerprüfung von business as usual. That being said, while(!fail) ist unbestritten ein häufiger und knappe Sprache, und kann bevorzugt in einfacher (single-Daten-per-Lesen-Typ-Szenarien).

  • melpomene
    4. Mai 2019

    Die anderen Antworten haben erklärt, warum die Logik ist falsch while (!stream.eof()) und wie es zu lösen ist. Ich möchte den Fokus auf etwas anderes:

    warum ist die überprüfung für eof explizit mit iostream::eof falsch?

    Im Allgemeinen, die überprüfung für die eof nur ist falsch, weil Strom-Extraktion (>>) ausfallen kann, ohne auf das Ende der Datei. Wenn Sie z.B. int n; cin >> n; und der stream enthält hello, dann h ist keine gültige Ziffer, also die Extraktion wird fehlschlagen, ohne erreichen des Endes der Eingabe.

    Dieses Problem, verbunden mit der Allgemeinen Logik-Fehler überprüfen Sie den stream-Zustand vor dem Lesen, was bedeutet, dass für die N Eingabe Elemente, die Schleife wird ausgeführt N+1-mal, führt zu folgenden Symptomen:

    • Wenn der stream leer ist, wird die Schleife einmal ausgeführt. >> fehl (es ist kein Eingang zu Lesen) und alle Variablen, die angeblich eingestellt werden (von stream >> x) sind tatsächlich nicht initialisiert. Dies führt zu Datenmüll verarbeitet werden, welche sich dann als unsinnige Ergebnisse (oft riesige zahlen).

    • Wenn der stream nicht leer ist, wird die Schleife erneut ausgeführt, nachdem die Letzte gültige Eingabe. Da in der letzten iteration alle >> Operationen fehlschlagen Variablen sind wahrscheinlich zu halten, die Ihren Wert aus der vorherigen iteration. Dies kann sich als "die Letzte Zeile ist zweimal gedruckt" oder "die Letzte input-Datensatz ist doppelt verarbeitet".

    • Wenn der stream enthält fehlerhafte Daten, aber Sie prüfen nur auf .eof, Sie am Ende mit einer unendlichen Schleife. >> fehl, um zu extrahieren alle Daten aus dem stream, so dass die Schleife dreht sich in Ort, ohne jemals das Ende.


    Zur Erinnerung: Die Lösung ist zum testen der Erfolg der >> operation selbst, nicht mit einem separaten .eof() Methode: while (stream >> n >> m) { ... }, genau wie in C-testen Sie den Erfolg der scanf sich selbst aufrufen: while (scanf("%d%d", &n, &m) == 2) { ... }.