694 Stimmen

Warum wird iostream::eof innerhalb einer Schleifenbedingung (d. h. `while (!stream.eof())`) als falsch angesehen?

Ich habe gerade einen Kommentar in este Antwort, dass die Verwendung von iostream::eof in einer Schleifenbedingung ist "mit ziemlicher Sicherheit falsch". Ich verwende im Allgemeinen etwas wie while(cin>>n) - was wohl implizit auf EOF prüft.

Warum wird die Prüfung auf eof explizit mit while (!cin.eof()) falsch?

Was ist der Unterschied zur Verwendung von scanf("...",...)!=EOF in C (die ich oft ohne Probleme verwende)?

27 Stimmen

scanf(...) != EOF wird auch in C nicht funktionieren, weil scanf gibt die Anzahl der erfolgreich geparsten und zugewiesenen Felder zurück. Die korrekte Bedingung ist scanf(...) < n donde n ist die Anzahl der Felder in der Formatzeichenkette.

8 Stimmen

@Ben Voigt, es wird eine negative Zahl zurückgegeben (die EOF normalerweise als solche definiert ist), wenn EOF erreicht wird

23 Stimmen

@SebastianGodelet: Tatsächlich wird es zurückkehren EOF wenn das Ende der Datei vor der ersten Feldumwandlung erreicht wird (erfolgreich oder nicht). Wird das Ende der Datei zwischen den Feldern erreicht, wird die Anzahl der erfolgreich umgewandelten und gespeicherten Felder zurückgegeben. Das macht den Vergleich mit EOF falsch.

617voto

Xeo Punkte 126280

Denn iostream::eof wird nur zurückgegeben true nach Lesen des Endes des Streams. Es tut no zeigen an, dass der nächste Lesevorgang das Ende des Datenstroms sein wird.

Stellen Sie sich Folgendes vor (und gehen Sie davon aus, dass das nächste Lesen am Ende des Streams stattfindet):

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
}

Dagegen:

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 zu Ihrer zweiten Frage: Weil

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

ist dasselbe wie

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

y no dasselbe wie

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

14 Stimmen

Erwähnenswert ist, dass if (!(inStream >> data).eof()) auch nichts Nützliches bewirkt. Irrtum 1: Die Bedingung wird nicht erfüllt, wenn nach dem letzten Datenelement kein Leerzeichen steht (das letzte Datum wird nicht verarbeitet). Irrtum 2: Die Bedingung tritt auch dann ein, wenn das Lesen der Daten fehlgeschlagen ist, solange EOF nicht erreicht wurde (Endlosschleife, die immer wieder die gleichen alten Daten verarbeitet).

0 Stimmen

Das ist ein bisschen off-topic, aber lassen Sie es mich sagen. Würde dieser Ansatz ohne Probleme gelingen, wenn man eine faule Auswertung verwendet?

5 Stimmen

Ich denke, es lohnt sich, darauf hinzuweisen, dass diese Antwort etwas irreführend ist. Beim Extrahieren von int s oder std::string s oder ähnlichem, das EOF-Bit es gesetzt, wenn Sie das eine kurz vor dem Ende extrahieren und die Extraktion das Ende trifft. Sie brauchen nicht noch einmal zu lesen. Der Grund, warum es beim Lesen von Dateien nicht gesetzt wird, ist, dass es eine zusätzliche \n am Ende. Ich habe das Thema in eine weitere Antwort . Lesen char s ist eine andere Sache, weil es nur eine nach der anderen extrahiert und nicht bis zum Ende durchläuft.

117voto

sly Punkte 1632

Unterm Strich oben: Bei ordnungsgemäßer Handhabung des Leerzeichens sieht das folgendermaßen aus eof verwendet werden können (und sogar zuverlässiger sind als fail() zur Fehlerprüfung):

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

( Danke Tony D für den Vorschlag, die Antwort hervorzuheben. In seinem Kommentar unten finden Sie ein Beispiel dafür, warum dies robuster ist. )


Das Hauptargument gegen die Verwendung eof() scheint eine wichtige Feinabstimmung über die Rolle des Weißraums zu fehlen. Mein Vorschlag ist, dass bei der Überprüfung eof() ist nicht nur ausdrücklich nicht " immer falsch " -- was eine vorherrschende Meinung in diesem und ähnlichen SO-Threads zu sein scheint --, aber mit der richtigen Behandlung von Leerzeichen sorgt es für eine sauberere und zuverlässigere Fehlerbehandlung und ist die immer richtig Lösung (auch wenn sie nicht unbedingt die kürzeste ist).

Zusammenfassend kann man sagen, dass die "richtige" Reihenfolge der Beendigung und des Lesens wie folgt aussieht:

int data;
while(in >> data) {  /* ... */ }

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

Das Scheitern eines Leseversuchs nach eof wird als Abbruchbedingung angesehen. Dies bedeutet, dass es keine einfache Möglichkeit gibt, zwischen einem erfolgreichen Stream und einem Stream zu unterscheiden, der aus anderen Gründen als eof fehlschlägt. Nehmen Sie die folgenden Streams:

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

while(in>>data) endet mit einem Satz failbit para todo drei Eingaben. In der ersten und dritten, eofbit ist ebenfalls festgelegt. Hinter der Schleife braucht man also eine sehr hässliche zusätzliche Logik, um eine richtige Eingabe (1.) von einer falschen (2. und 3.) zu unterscheiden.

Nehmen Sie dagegen Folgendes an:

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

Hier, in.fail() bestätigt, dass, solange es etwas zu lesen gibt, es auch das Richtige ist. Er dient nicht nur als Begrenzer der while-Schleife.

So weit, so gut, aber was passiert, wenn im Datenstrom Leerzeichen vorhanden sind - das klingt nach dem Hauptproblem gegen eof() als Terminator?

Wir müssen unsere Fehlerbehandlung nicht aufgeben, sondern nur den Leerraum aufbrauchen:

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 jedes potenzielle (null oder mehr) Leerzeichen am Ende des Datenstroms, während es den eofbit y nicht die failbit . Also, in.fail() funktioniert wie erwartet, solange es mindestens eine Datei zu lesen gibt. Wenn auch "All-Blank"-Streams akzeptabel sind, dann ist die korrekte Form:

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: Eine richtig konstruierte while(!eof) ist nicht nur möglich und nicht falsch, sondern ermöglicht auch die Lokalisierung von Daten innerhalb des Geltungsbereichs und bietet eine saubere Trennung der Fehlerprüfung von der normalen Geschäftstätigkeit. Davon abgesehen, while(!fail) ist zweifellos eine gebräuchlichere und prägnantere Redewendung, die in einfachen Szenarien (einzelne Daten pro Lesevorgang) bevorzugt werden kann.

8 Stimmen

" Hinter der Schleife gibt es also keine (einfache) Möglichkeit, eine korrekte Eingabe von einer nicht korrekten zu unterscheiden. " Außer, dass in einem Fall beide eofbit y failbit eingestellt sind, in der anderen nur failbit eingestellt ist. Sie brauchen nur zu testen, dass einmal nachdem die Schleife beendet wurde, nicht bei jeder Iteration; es wird die Schleife nur einmal verlassen, so dass Sie nur prüfen müssen warum er hat die Schleife einmal verlassen. while (in >> data) funktioniert bei allen leeren Strömen einwandfrei.

3 Stimmen

Was Sie damit sagen wollen (und worauf ich bereits hingewiesen habe), ist, dass ein schlecht formatierter Stream wie folgt identifiziert werden kann !eof & fail vergangenen Schleife. Es gibt Fälle, in denen man sich darauf nicht verlassen kann. Siehe obigen Kommentar ( goo.gl/9mXYX ). Wie auch immer, ich schlage nicht vor eof -Prüfung als der-immer-bessere alternativ. Ich will damit nur sagen, dass es es eine mögliche und (in einigen Fällen angemessenere) Art und Weise, dies zu tun, und nicht "ganz sicher falsch", wie es hier in SO oft behauptet wird.

2 Stimmen

"Überlegen Sie zum Beispiel, wie Sie auf Fehler prüfen würden, wenn die Daten eine Struktur mit überladenem Operator>> sind, der mehrere Felder gleichzeitig liest. - Ein sehr viel einfacheres Beispiel, das Ihren Standpunkt unterstützt, ist stream >> my_int wobei der Stream z.B. "-" enthält: eofbit y failbit eingestellt sind. Das ist schlimmer als die operator>> Szenario, bei dem die vom Benutzer bereitgestellte Überlast zumindest die Möglichkeit hat, die eofbit bevor er zurückkehrt, um zu helfen while (s >> x) Verwendung. Generell könnte diese Antwort eine Bereinigung vertragen - nur der letzte while( !(in>>ws).eof() ) ist in der Regel robust, und es ist am Ende vergraben.

79voto

Nawaz Punkte 339767

Denn wenn Programmierer nicht schreiben while(stream >> n) schreiben sie dies möglicherweise:

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

Das Problem dabei ist, dass man nicht some work on n ohne vorher zu prüfen, ob das Lesen des Datenstroms erfolgreich war, denn wenn dies nicht der Fall war, wird Ihr some work on n würde zu einem unerwünschten Ergebnis führen.

Das ist der springende Punkt, eofbit , badbit o failbit eingestellt sind nachdem ein Leseversuch aus dem Stream unternommen wurde. Wenn also stream >> n fehlschlägt, dann eofbit , badbit o failbit wird sofort gesetzt, daher ist es idiomatischer, wenn Sie schreiben while (stream >> n) weil das zurückgegebene Objekt stream konvertiert zu false wenn ein Fehler beim Lesen des Datenstroms aufgetreten ist und die Schleife folglich anhält. Und sie konvertiert zu true wenn das Lesen erfolgreich war und die Schleife fortgesetzt wird.

2 Stimmen

Abgesehen von dem erwähnten "unerwünschten Ergebnis" bei der Arbeit mit dem undefinierten Wert von n kann das Programm auch in eine Endlosschleife wenn die fehlgeschlagene Stream-Operation keinen Input verbraucht.

17voto

melpomene Punkte 81665

Die anderen Antworten haben erklärt, warum die Logik falsch ist in while (!stream.eof()) und wie man sie beheben kann. Ich möchte mich auf etwas anderes konzentrieren:

warum wird die Prüfung auf eof explizit mit iostream::eof falsch?

Generell kann man sagen, dass die Prüfung auf eof nur ist falsch, weil die Stream-Extraktion ( >> ) kann fehlschlagen, ohne das Ende der Datei zu erreichen. Wenn Sie z.B. int n; cin >> n; und der Strom enthält hello entonces h ist keine gültige Ziffer, so dass die Extraktion fehlschlägt, ohne das Ende der Eingabe zu erreichen.

Dieses Problem in Verbindung mit dem allgemeinen Logikfehler bei der Überprüfung des Stromstatus Der Versuch, daraus zu lesen, was bedeutet, dass die Schleife für N Eingabeelemente N+1 Mal durchlaufen wird, führt zu folgenden Symptomen:

  • Wenn der Stream leer ist, wird die Schleife einmal durchlaufen. >> scheitert (es gibt keine zu lesende Eingabe) und alle Variablen, die gesetzt werden sollten (durch stream >> x ) sind eigentlich nicht initialisiert. Dies führt dazu, dass Datenmüll verarbeitet wird, der sich in unsinnigen Ergebnissen (oft riesigen Zahlen) äußern kann.

    (Wenn Ihre Standardbibliothek mit C++11 konform ist, liegen die Dinge jetzt etwas anders: Eine fehlgeschlagene >> setzt nun numerische Variablen auf 0 anstatt sie uninitialisiert zu lassen (außer bei char s).)

  • Wenn der Stream nicht leer ist, wird die Schleife nach der letzten gültigen Eingabe erneut durchlaufen. Da in der letzten Iteration alle >> Operationen fehlschlagen, behalten die Variablen wahrscheinlich ihren Wert aus der vorherigen Iteration. Dies kann sich so äußern, dass "die letzte Zeile zweimal gedruckt" oder "der letzte Eingabesatz zweimal verarbeitet" wird.

    (Dies sollte sich seit C++11 ein wenig anders darstellen (siehe oben): Statt einer wiederholten letzten Zeile erhält man nun einen "Phantomsatz" aus Nullen).

  • Wenn der Stream fehlerhafte Daten enthält, Sie aber nur auf .eof landet man in einer Endlosschleife. >> kann keine Daten aus dem Stream extrahieren, so dass die Schleife weiterläuft, ohne jemals das Ende zu erreichen.


Um es kurz zu machen: Die Lösung besteht darin, den Erfolg der >> Operation selbst, und nicht die Verwendung eines separaten .eof() Methode: while (stream >> n >> m) { ... } so wie man in C den Erfolg der Methode scanf selbst aufrufen: while (scanf("%d%d", &n, &m) == 2) { ... } .

1 Stimmen

Dies ist die genaueste Antwort, obwohl ab C++11, ich nicht glaube, dass die Variablen uninitialized mehr sind (der erste Aufzählung pt)

5voto

Das Wichtigste ist, dass man sich daran erinnert, inFile.eof() wird nicht True bis nach ein Leseversuch scheitert, weil Sie das Ende der Datei erreicht haben. In diesem Beispiel erhalten Sie also eine Fehlermeldung.

while (!inFile.eof()){
    inFile >> x;
        process(x);
}

Um diese Schleife korrekt zu gestalten, müssen Lesen und Prüfen zu einem einzigen Vorgang zusammengefasst werden, und zwar wie folgt

while (inFile >> x) 
    process(x); 

Nach Konvention, operator>> gibt den Stream zurück, aus dem wir gelesen haben, und ein boolescher Test auf einen Stream gibt False wenn der Stream fehlschlägt (z. B. wenn das Ende der Datei erreicht wird).

Damit haben wir die richtige Reihenfolge:

  • lesen
  • prüfen, ob das Lesen erfolgreich war
  • wenn und nur wenn der Test erfolgreich ist, verarbeiten Sie das Gelesene

Wenn Sie zufällig auf einige autre Wenn Sie ein Problem haben, das Sie daran hindert, die Datei korrekt zu lesen, können Sie die Datei nicht erreichen. eof() als solche. Betrachten wir zum Beispiel etwas wie dieses

int x; 
while (!inFile.eof()) { 
    inFile >> x; 
    process(x);
} 

Lassen Sie uns die Funktionsweise des obigen Codes anhand eines Beispiels nachvollziehen

  • Angenommen, der Inhalt der Datei lautet '1', '2', '3', 'a', 'b' .
  • Die Schleife liest die 1, 2 und 3 richtig.
  • Dann wird es zu a .
  • Wenn es versucht, die a als int, wird es scheitern.
  • Der Strom ist jetzt in einem gescheiterten Zustand, bis oder solange wir clear der Stream, schlagen alle Versuche, ihn zu lesen, fehl.
  • Aber wenn wir auf eof() testen, wird das Ergebnis False weil wir noch nicht am Ende der Datei sind, denn es gibt noch a die darauf warten, gelesen zu werden.
  • Die Schleife wird immer wieder versuchen, aus der Datei zu lesen, und jedes Mal scheitern, so dass sie niemals das Ende der Datei erreicht.
  • Die obige Schleife wird also ewig laufen.

Wenn wir jedoch eine Schleife wie diese verwenden, erhalten wir die gewünschte Ausgabe.

while (inFile >> x)
    process(x);

In diesem Fall wird der Stream in False nicht nur bei Dateiende, sondern auch bei einer fehlgeschlagenen Konvertierung, wie z. B. bei der a die wir nicht als Ganzzahl lesen können.

1 Stimmen

?Syntaxfehler: undefinierte Bezeichner True y False

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X