Was ist der wahre Grund, das EOF-Bit nicht als Stream-Extraktionsbedingung zu verwenden?

8

Inspiriert von meiner vorherigen Frage

Ein häufiger Fehler für neue C ++ - Programmierer ist das Lesen von einer Datei mit etwas wie folgt:

%Vor%

Sie melden oft, dass die letzte Zeile der Datei zweimal gelesen wurde. Die gängige Erklärung für dieses Problem (eine, die ich zuvor gegeben habe) lautet etwa:

  

Bei der Extraktion wird nur das EOF-Bit im Stream gesetzt, wenn Sie versuchen, das Dateiende zu extrahieren, und nicht, wenn Ihre Extraktion am Ende der Datei endet. file.eof() sagt Ihnen nur, ob der vorherige Lesevorgang das Dateiende und nicht das nächste Dateiende erreicht hat. Nachdem die letzte Zeile extrahiert wurde, ist das EOF-Bit immer noch nicht gesetzt und die Iteration tritt noch einmal auf. Bei dieser letzten Iteration schlägt die Extraktion jedoch fehl, und line hat immer noch den gleichen Inhalt wie zuvor, d. H. Die letzte Zeile ist dupliziert.

Der erste Satz dieser Erklärung ist jedoch falsch und daher ist auch die Erklärung, was der Code tut, falsch.

Die Definition von formatierten Eingabefunktionen (in der operator>>(std::string&) steht) definiert die Extraktion mit rdbuf()->sbumpc() oder rdbuf()->sgetc() , um Eingabezeichen zu erhalten. Es besagt, dass, wenn eine dieser Funktionen traits::eof() zurückgibt, das EOF-Bit gesetzt ist:

  

Wenn rdbuf()->sbumpc() oder rdbuf()->sgetc() traits::eof() zurückgibt, schließt die Eingabefunktion ihre Aktionen ab und tut% ce_de%, was setstate(eofbit) (27.5.5.4) auslösen kann, bevor sie zurückkehrt .

Das sehen wir an dem einfachen Beispiel, das eine ios_base::failure anstelle einer Datei verwendet (sie sind beide Eingabestreams und verhalten sich beim Extrahieren auf die gleiche Weise):

%Vor%

Hier ist klar, dass die einzelne Extraktion std::stringstream von der Zeichenkette erhält und das EOF-Bit auf 1 setzt.

Was stimmt also nicht mit der Erklärung? Was ist anders an Dateien, die dazu führen, dass hello die letzte Zeile dupliziert? Was ist der wahre Grund, warum wir !file.eof() nicht als Extraktionsbedingung verwenden sollten?

    
Joseph Mansfield 30.01.2013, 23:15
quelle

2 Antworten

16

Ja, das Extrahieren aus einem Eingabestream setzt das EOF-Bit, wenn die Extraktion am Dateiende stoppt, wie das Beispiel std::stringstream zeigt. Wenn es so einfach wäre, würde die Schleife mit !file.eof() als Bedingung für eine Datei wie:

gut funktionieren %Vor%

Die zweite Extraktion würde world essen, am Ende der Datei stoppen und folglich das EOF-Bit setzen. Die nächste Iteration würde nicht auftreten.

Viele Texteditoren haben jedoch ein schmutziges Geheimnis. Sie belügen Sie, wenn Sie eine Textdatei speichern, die so einfach ist. Was sie nicht sagen, ist, dass am Ende der Datei ein versteckter \n ist. Jede Zeile in der Datei endet mit einem \n , einschließlich des letzten. Die Datei enthält also:

%Vor%

Dies bewirkt, dass die letzte Zeile dupliziert wird, wenn !file.eof() als Bedingung verwendet wird. Nun, da wir das wissen, können wir sehen, dass die zweite Extraktion essen wird world stoppt bei \n und nicht setzt das EOF-Bit (weil wir noch nicht dort angekommen sind). Die Schleife wird ein drittes Mal iterieren, aber die nächste Extraktion wird fehlschlagen, weil sie keine zu extrahierende Zeichenfolge findet, sondern nur Leerzeichen. Die Zeichenfolge bleibt mit ihrem vorherigen Wert hängen und so erhalten wir die doppelte Zeile.

Das erfährst du nicht mit std::stringstream , denn was du im Stream steckst, ist genau das, was du bekommst. Es gibt kein \n am Ende von std::stringstream ss("hello") , anders als in der Datei. Wenn Sie std::stringstream ss("hello\n") ausführen würden, würden Sie das gleiche doppelte Zeilenproblem bekommen.

Natürlich können wir sehen, dass wir niemals !file.eof() als Bedingung beim Extrahieren aus einer Textdatei verwenden sollten - aber was ist das eigentliche Problem? Warum sollten wir das wirklich nie als unsere Bedingung verwenden, unabhängig davon, ob wir aus einer Datei extrahieren oder nicht?

Das eigentliche Problem ist, dass eof() uns keine Ahnung gibt, ob der nächste Lesevorgang fehlschlägt oder nicht . Im obigen Fall haben wir gesehen, dass, obwohl eof() 0 war, die nächste Extraktion fehlgeschlagen ist, weil keine Zeichenfolge zum Extrahieren vorhanden war. Die gleiche Situation würde auftreten, wenn wir keinen Dateistream mit einer Datei verknüpfen oder wenn der Stream leer ist. Das EOF-Bit wäre nicht gesetzt, aber es gibt nichts zu lesen. Wir können nicht einfach blindlings fortfahren und aus der Datei extrahieren, nur weil eof() nicht gesetzt ist.

Die Verwendung von while (std::getline(...)) und verwandten Bedingungen funktioniert einwandfrei, da die formatierte Eingabefunktion kurz vor dem Start der Extraktion prüft, ob eines der bad-, fail- oder EOF-Bits gesetzt ist. Wenn einer von ihnen ist, endet es sofort und setzt das Fehlerbit in dem Prozess. Es wird auch fehlschlagen, wenn es das Dateiende findet, bevor es findet, was es extrahieren will, indem es sowohl die eof- als auch die fail-Bits setzt.

Hinweis: Sie können eine Datei ohne den zusätzlichen \n in vim speichern, wenn Sie :set noeol und :set binary vor dem Speichern ausführen.

    
Joseph Mansfield 30.01.2013 23:15
quelle
4

Ihre Frage hat einige falsche Vorstellungen. Du gibst eine Erklärung:

  

"Die Extraktion setzt nur das EOF-Bit im Stream, wenn Sie versuchen, das Dateiende zu extrahieren, nicht, wenn Ihre Extraktion am Ende der Datei endet."

Dann behaupten Sie es ist "falsch und so ist die Erklärung dessen, was der Code tut, auch falsch."

Eigentlich ist es richtig. Schauen wir uns ein Beispiel an ...

Beim Einlesen in std::string ...

%Vor%

... standardmäßig und wie in Ihrer Frage operator>> liest Zeichen, bis es whitespace oder EOF findet. Also:

  • Lesen von 'abc\n' - & gt; Sobald das '\n' gefunden wurde, versucht es nicht, das Dateiende zu extrahieren, sondern "stoppt nur bei [EOF]" und eof() gibt true ,
  • nicht zurück
  • Lesen von 'abc' statt - & gt; Es ist der Versuch, das Dateiende zu extrahieren, das das Ende des string -Inhalts entdeckt, sodass eof() true zurückgibt.

Ähnlich analysiert '123' in int setzt eof() , weil das Parsing nicht weiß, ob es eine andere Ziffer gibt und versucht, sie weiter zu lesen, indem es eof() trifft. Wenn '123 ' auf int analysiert wird, wird eof() nicht festgelegt.

Entscheidend ist, dass das Parsen von 'a' in char eof() nicht setzt, da nachfolgende Leerzeichen nicht benötigt werden, um zu wissen, dass das Parsing vollständig ist - sobald ein Zeichen gelesen wird, wird kein Versuch unternommen, ein anderes Zeichen zu finden Das eof() wurde nicht gefunden. (Natürlich kommt die weitere Analyse aus dem gleichen Stream eof ).

  

Es ist klar [für stringstream "Hallo" & gt; & gt; std :: string], dass die einzelne Extraktion hallo aus der Zeichenfolge erhält und das EOF-Bit auf 1 setzt.   Was stimmt also nicht mit der Erklärung? Was ist bei Dateien anders, die bewirken, dass Datei.eof () dazu führt, dass die letzte Zeile dupliziert wird? Was ist der wahre Grund, warum wir file.eof () nicht als Extraktionsbedingung verwenden sollten?

Der Grund ist wie oben ... dass Dateien mit einem '\ n'-Zeichen enden, und wenn sie heißen, getline oder >> std::string geben das letzte Nicht-Leerzeichen-Token zurück, ohne "versuchen zu müssen, das Ende der Datei "(um Ihre Phrase zu verwenden).

    
Tony Delroy 23.04.2013 03:25
quelle

Tags und Links