TL;DR
while(!feof)
ist falsch, weil es auf etwas prüft, das nicht relevant ist, und nicht auf etwas prüft, das man wissen muss. Das Ergebnis ist, dass Sie fälschlicherweise einen Code ausführen, der davon ausgeht, dass er auf erfolgreich gelesene Daten zugreift, obwohl dies in Wirklichkeit nie geschehen ist.
Ich möchte eine abstrakte, übergeordnete Perspektive bieten. Lesen Sie also weiter, wenn Sie daran interessiert sind, was while(!feof)
tatsächlich tut.
Gleichzeitigkeit und Simultaneität
E/A-Operationen interagieren mit der Umgebung. Die Umgebung ist nicht Teil Ihres Programms und unterliegt nicht Ihrer Kontrolle. Die Umgebung existiert wirklich "gleichzeitig" mit Ihrem Programm. Wie bei allen Dingen, die gleichzeitig ablaufen, machen Fragen nach dem "aktuellen Zustand" keinen Sinn: Es gibt kein Konzept der "Gleichzeitigkeit" bei gleichzeitigen Ereignissen. Viele Eigenschaften des Zustands sind einfach nicht existieren gleichzeitig.
Lassen Sie mich das genauer formulieren: Nehmen wir an, Sie wollen fragen: "Haben Sie noch mehr Daten". Sie könnten diese Frage an einen gleichzeitigen Container oder an Ihr E/A-System richten. Aber die Antwort ist im Allgemeinen nicht beantwortbar und daher bedeutungslos. Was soll's, wenn der Container "ja" sagt - bis Sie versuchen zu lesen, hat er vielleicht keine Daten mehr. Wenn die Antwort "nein" lautet, kann es sein, dass zu dem Zeitpunkt, an dem Sie versuchen, die Daten zu lesen, bereits Daten angekommen sind. Die Schlussfolgerung ist, dass es einfach est keine Eigenschaft wie "Ich habe Daten", da Sie nicht sinnvoll auf eine mögliche Antwort reagieren können. (Etwas besser sieht es bei gepufferten Eingaben aus, wo man vielleicht ein "Ja, ich habe Daten" erhält, das eine Art Garantie darstellt, aber man müsste immer noch in der Lage sein, mit dem umgekehrten Fall umzugehen. Und bei der Ausgabe ist die Situation sicherlich genauso schlecht, wie ich sie beschrieben habe: Sie wissen nie, ob die Festplatte oder der Netzwerkpuffer voll ist.)
Daraus schließen wir, dass es unmöglich ist, und zwar un vernünftig um ein E/A-System zu fragen, ob es wird sein eine E/A-Operation durchführen können. Die einzige Möglichkeit, mit ihm zu interagieren (genau wie bei einem nebenläufigen Container), ist Versuch den Vorgang und prüft, ob er erfolgreich war oder nicht. In dem Moment, in dem Sie mit der Umgebung interagieren, können Sie wissen, ob die Interaktion tatsächlich möglich war, und zu diesem Zeitpunkt müssen Sie sich verpflichten, die Interaktion durchzuführen. (Dies ist ein "Synchronisationspunkt", wenn Sie so wollen.)
EOF
Jetzt kommen wir zum EOF. EOF ist die Antwort Sie erhalten von einem Versucht E/A-Betrieb. Es bedeutet, dass Sie versucht haben, etwas zu lesen oder zu schreiben, dabei aber keine Daten lesen oder schreiben konnten und stattdessen auf das Ende der Eingabe oder Ausgabe gestoßen sind. Dies gilt im Wesentlichen für alle E/A-APIs, ob es sich um die C-Standardbibliothek, C++ iostreams oder andere Bibliotheken handelt. Solange die E/A-Operationen erfolgreich sind, können Sie einfach kann nicht wissen ob weitere, zukünftige Operationen erfolgreich sein werden. Sie muss immer zuerst den Vorgang versuchen und dann auf Erfolg oder Misserfolg reagieren.
Beispiele
Beachten Sie in jedem der Beispiele sorgfältig, dass wir erste den E/A-Vorgang versuchen und では verbrauchen das Ergebnis, wenn es gültig ist. Beachten Sie außerdem, dass wir immer muss das Ergebnis der E/A-Operation verwenden, wobei das Ergebnis in jedem Beispiel eine andere Form hat.
-
C stdio, Lesen aus einer Datei:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n == 0) { break; }
}
Das Ergebnis, das wir verwenden müssen, lautet n
die Anzahl der gelesenen Elemente (die auch Null sein kann).
-
C stdio, scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
Das Ergebnis, das wir verwenden müssen, ist der Rückgabewert von scanf
die Anzahl der umgewandelten Elemente.
-
C++, iostreams-formatierte Extraktion:
for (int n; std::cin >> n; ) {
consume(n);
}
Das Ergebnis, das wir verwenden müssen, lautet std::cin
selbst, das in einem booleschen Kontext ausgewertet werden kann und uns sagt, ob sich der Stream noch in der good()
Zustand.
-
C++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
Das Ergebnis, das wir verwenden müssen, ist wieder std::cin
genau wie zuvor.
-
POSIX, write(2)
um einen Puffer zu leeren:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }
Das Ergebnis, das wir hier verwenden, lautet k
die Anzahl der geschriebenen Bytes. Der Punkt hier ist, dass wir nur wissen können, wie viele Bytes geschrieben wurden 後 den Schreibvorgang.
-
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
Das Ergebnis, das wir verwenden müssen, lautet nbytes
die Anzahl der Bytes bis einschließlich des Zeilenumbruchs (oder EOF, wenn die Datei nicht mit einem Zeilenumbruch endet).
Beachten Sie, dass die Funktion explizit zurückgibt -1
(und nicht EOF!), wenn ein Fehler auftritt oder EOF erreicht wird.
Sie werden feststellen, dass wir das Wort "EOF" nur sehr selten buchstabieren. Normalerweise erkennen wir die Fehlerbedingung auf eine andere Art und Weise, die für uns unmittelbar interessanter ist (z. B. das Versagen, so viel E/A durchzuführen, wie wir gewünscht hatten). In jedem Beispiel gibt es eine API-Funktion, die uns explizit mitteilen könnte, dass der EOF-Zustand eingetreten ist, aber das ist eigentlich keine besonders nützliche Information. Es handelt sich dabei um ein viel größeres Detail, als uns oft lieb ist. Wichtig ist vielmehr, ob die E/A erfolgreich war, und nicht, wie sie fehlgeschlagen ist.
-
Ein letztes Beispiel, das tatsächlich den EOF-Status abfragt: Nehmen wir an, Sie haben eine Zeichenkette und wollen prüfen, ob sie eine ganze Zahl darstellt, ohne zusätzliche Bits am Ende außer Leerzeichen. Mit C++ iostreams geht das so:
std::string input = " 123 "; // example
std::istringstream iss(input);
int value;
if (iss >> value >> std::ws && iss.get() == EOF) {
consume(value);
} else {
// error, "input" is not parsable as an integer
}
Wir verwenden hier zwei Ergebnisse. Das erste ist iss
das Stream-Objekt selbst, um zu prüfen, ob die formatierte Extraktion nach value
gelungen. Aber dann, nachdem auch Leerraum verbraucht wurde, führen wir eine weitere E/A-Operation durch, iss.get()
und erwarten, dass es als EOF fehlschlägt, was der Fall ist, wenn die gesamte Zeichenkette bereits durch die formatierte Extraktion verbraucht wurde.
In der C-Standardbibliothek können Sie etwas Ähnliches mit der strto*l
funktioniert, indem sie überprüft, ob der Endzeiger das Ende der Eingabezeichenkette erreicht hat.
29 Stimmen
Warum es schlecht ist, zu verwenden
feof()
zur Steuerung einer Schleife1 Stimmen
Warum wird iostream::eof innerhalb einer Schleifenbedingung als falsch angesehen?