Ich möchte eine abstrakte Perspektive auf hoher Ebene bieten.
Parallelität und Gleichzeitigkeit
E / A-Vorgänge interagieren mit der Umgebung. Die Umgebung ist nicht Teil Ihres Programms und nicht unter Ihrer Kontrolle. Die Umgebung existiert wirklich "gleichzeitig" mit Ihrem Programm. Wie bei allen gleichzeitigen Dingen sind Fragen zum "aktuellen Zustand" nicht sinnvoll: Es gibt kein Konzept der "Gleichzeitigkeit" zwischen gleichzeitigen Ereignissen. Viele Eigenschaften von Staat einfach nicht existieren gleichzeitig.
Lassen Sie mich das präzisieren: Angenommen, Sie möchten fragen: "Haben Sie mehr Daten?". Sie können dies von einem gleichzeitigen Container oder von Ihrem E / A-System verlangen. Aber die Antwort ist im Allgemeinen nicht handlungsfähig und daher bedeutungslos. Was ist, wenn der Container "Ja" sagt? Wenn Sie versuchen zu lesen, enthält er möglicherweise keine Daten mehr. Wenn die Antwort "Nein" lautet, sind zum Zeitpunkt des Leseversuchs möglicherweise Daten eingetroffen. Die Schlussfolgerung ist, dass es einfach gibtKeine Eigenschaft wie "Ich habe Daten", da Sie auf eine mögliche Antwort nicht sinnvoll reagieren können. (Bei gepufferten Eingaben ist die Situation etwas besser, da Sie möglicherweise ein "Ja, ich habe Daten" erhalten, das eine Art Garantie darstellt, aber Sie müssten immer noch in der Lage sein, den umgekehrten Fall zu behandeln. Und bei der Ausgabe die Situation ist sicherlich genauso schlimm wie ich beschrieben habe: Man weiß nie, ob diese Festplatte oder dieser Netzwerkpuffer voll ist.)
So schließen wir , dass es unmöglich ist, und in der Tat un vernünftig , ein I / O - System zu fragen , ob es sein wird , kann eine E / A - Operation auszuführen. Die einzige Möglichkeit, mit ihm zu interagieren (genau wie mit einem gleichzeitigen Container), besteht darin , den Vorgang zu versuchen und zu überprüfen, ob er erfolgreich war oder fehlgeschlagen ist. In dem Moment, in dem Sie mit der Umgebung interagieren, können Sie dann und nur dann wissen, ob die Interaktion tatsächlich möglich war, und an diesem Punkt müssen Sie sich zur Durchführung der Interaktion verpflichten. (Dies ist ein "Synchronisationspunkt", wenn Sie so wollen.)
EOF
Jetzt kommen wir zu EOF. EOF ist die Antwort, die Sie von einem versuchten E / A-Vorgang erhalten. Dies bedeutet, dass Sie versucht haben, etwas zu lesen oder zu schreiben, dabei jedoch keine Daten gelesen oder geschrieben haben und stattdessen das Ende der Eingabe oder Ausgabe festgestellt wurde. Dies gilt im Wesentlichen für alle E / A-APIs, unabhängig davon, ob es sich um die C-Standardbibliothek, C ++ - Iostreams oder andere Bibliotheken handelt. Solange die E / A-Vorgänge erfolgreich sind, können Sie einfach nicht wissen, ob weitere zukünftige Vorgänge erfolgreich sein werden. Sie müssen immer zuerst den Vorgang versuchen und dann auf Erfolg oder Misserfolg reagieren.
Beispiele
Beachten Sie in jedem der Beispiele sorgfältig, dass wir zuerst die E / A-Operation versuchen und dann das Ergebnis verwenden, wenn es gültig ist. Beachten Sie außerdem, dass wir immer das Ergebnis der E / A-Operation verwenden müssen, obwohl das Ergebnis in jedem Beispiel unterschiedliche Formen und Formen annimmt.
C stdio, aus einer Datei lesen:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
Das Ergebnis, das wir verwenden müssen, ist n
die Anzahl der gelesenen Elemente (die möglicherweise nur Null betragen).
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 konvertierten Elemente.
C ++, iostreams formatierte Extraktion:
for (int n; std::cin >> n; ) {
consume(n);
}
Das Ergebnis, das wir verwenden müssen, ist std::cin
selbst, das in einem booleschen Kontext ausgewertet werden kann und uns sagt, ob sich der Stream noch im good()
Status befindet.
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
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, ist k
die Anzahl der geschriebenen Bytes. Der Punkt hier ist, dass wir nur wissen können, wie viele Bytes nach dem Schreibvorgang geschrieben wurden.
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, ist nbytes
die Anzahl der Bytes bis einschließlich der neuen Zeile (oder EOF, wenn die Datei nicht mit einer neuen Zeile endete).
Beachten Sie, dass die Funktion explizit -1
(und nicht EOF!) Zurückgibt, wenn ein Fehler auftritt oder EOF erreicht.
Sie werden feststellen, dass wir das eigentliche Wort "EOF" sehr selten buchstabieren. Normalerweise erkennen wir den Fehlerzustand auf eine andere Weise, die für uns unmittelbar interessanter ist (z. B. wenn nicht so viele E / A-Vorgänge ausgeführt werden, wie wir es gewünscht hatten). In jedem Beispiel gibt es eine API-Funktion, die uns explizit mitteilen könnte, dass der EOF-Status aufgetreten ist, aber dies ist in der Tat keine besonders nützliche Information. Es ist viel mehr ein Detail, als uns oft wichtig ist. Entscheidend ist, ob die E / A erfolgreich war, mehr als wie sie fehlgeschlagen ist.
Ein letztes Beispiel, das den EOF-Status tatsächlich abfragt: Angenommen, Sie haben eine Zeichenfolge und möchten testen, ob sie eine Ganzzahl in ihrer Gesamtheit darstellt, ohne zusätzliche Bits am Ende außer Leerzeichen. Mit C ++ iostreams geht es 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 überprüfen, ob die formatierte Extraktion value
erfolgreich war. Nachdem wir jedoch auch Leerzeichen verbraucht haben, führen wir eine weitere E / A / Operation aus iss.get()
und erwarten, dass diese als EOF fehlschlägt. Dies ist der Fall, wenn die gesamte Zeichenfolge bereits von der formatierten Extraktion verbraucht wurde.
In der C-Standardbibliothek können Sie mit den strto*l
Funktionen etwas Ähnliches erreichen, indem Sie überprüfen, ob der Endzeiger das Ende der Eingabezeichenfolge erreicht hat.
Die Antwort
while(!feof)
ist falsch, weil es auf etwas testet, das irrelevant ist und nicht auf etwas testet, das Sie wissen müssen. Das Ergebnis ist, dass Sie fälschlicherweise Code ausführen, der davon ausgeht, dass er auf Daten zugreift, die erfolgreich gelesen wurden, obwohl dies tatsächlich nie geschehen ist.
feof()
eine Schleife zu steuern