Chciałbym przedstawić abstrakcyjną perspektywę wysokiego poziomu.
Współbieżność i równoczesność
Operacje we / wy współdziałają ze środowiskiem. Środowisko nie jest częścią twojego programu i nie jest pod twoją kontrolą. Środowisko naprawdę istnieje „równolegle” z twoim programem. Podobnie jak w przypadku wszystkich rzeczy współbieżnych, pytania dotyczące „obecnego stanu” nie mają sensu: nie ma pojęcia „jednoczesności” między równoległymi zdarzeniami. Wiele właściwości stanu po prostu nie istnieje jednocześnie.
Pozwól, że uściślę: Załóżmy, że chcesz zapytać „czy masz więcej danych”. Możesz zapytać o współbieżny kontener lub swój system I / O. Ale odpowiedź jest generalnie bezczynna, a zatem bez znaczenia. Co z tego, jeśli pojemnik powie „tak” - zanim spróbujesz czytać, może już nie mieć danych. Podobnie, jeśli odpowiedź brzmi „nie”, do czasu próby odczytania dane mogły przybyć. Wniosek jest taki, że po prostu jestżadna właściwość, taka jak „Mam dane”, ponieważ nie można podjąć znaczących działań w odpowiedzi na jakąkolwiek możliwą odpowiedź. (Sytuacja jest nieco lepsza w przypadku buforowanych danych wejściowych, w których można uzyskać odpowiedź „tak, mam dane”, która stanowi pewną gwarancję, ale nadal będziesz musiał poradzić sobie z przypadkiem odwrotnym. I przy wyjściu z sytuacji jest z pewnością tak źle, jak opisałem: nigdy nie wiadomo, czy ten dysk lub bufor sieciowy jest pełny.)
Stwierdzamy zatem, że nie jest możliwe, a wręcz nieuzasadnione , zapytanie systemu we / wy, czy będzie on w stanie wykonać operację we / wy. Jedynym możliwym sposobem na interakcję z nim (podobnie jak przy równoczesnym kontenerze) jest próba wykonania operacji i sprawdzenie, czy się powiodła, czy nie. W tym momencie, w którym wchodzisz w interakcję ze środowiskiem, wtedy i tylko wtedy możesz wiedzieć, czy interakcja była rzeczywiście możliwa, i wtedy musisz zobowiązać się do wykonania interakcji. (Jest to „punkt synchronizacji”, jeśli chcesz).
EOF
Teraz dochodzimy do EOF. EOF to odpowiedź uzyskana z próby operacji we / wy. Oznacza to, że próbujesz coś odczytać lub napisać, ale nie udało ci się odczytać ani zapisać żadnych danych, a zamiast tego napotkano koniec wejścia lub wyjścia. Dotyczy to zasadniczo wszystkich interfejsów API we / wy, bez względu na to, czy jest to biblioteka standardowa C, iostreams C ++, czy inne biblioteki. Dopóki operacje we / wy zakończą się powodzeniem , po prostu nie będzie wiadomo, czy dalsze przyszłe operacje zakończą się powodzeniem. Zawsze musisz najpierw wypróbować operację, a następnie zareagować na sukces lub porażkę.
Przykłady
W każdym z przykładów zwróć uwagę, że najpierw próbujemy operacji We / Wy, a następnie wykorzystujemy wynik, jeśli jest prawidłowy. Zauważ ponadto, że zawsze musimy użyć wyniku operacji We / Wy, chociaż wynik ma różne kształty i formy w każdym przykładzie.
C stdio, odczytane z pliku:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
Wynik, którego musimy użyć n
, to liczba odczytanych elementów (która może wynosić zaledwie zero).
C stdio scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
Wynik, którego musimy użyć, to zwracana wartość scanf
liczby przekonwertowanych elementów.
C ++, ekstrakcja sformatowana przez iostreams:
for (int n; std::cin >> n; ) {
consume(n);
}
Wynik, którego musimy użyć, to std::cin
sam, który można ocenić w kontekście logicznym i mówi nam, czy strumień jest nadal w good()
stanie.
C ++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
Rezultat, którego musimy użyć, jest ponownie std::cin
, tak jak poprzednio.
POSIX, write(2)
aby opróżnić bufor:
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 */ }
Wynik, którego tu używamy k
, to liczba zapisanych bajtów. Chodzi o to, że możemy wiedzieć tylko, ile bajtów zostało napisanych po operacji zapisu.
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);
Wynik, którego musimy użyć nbytes
, to liczba bajtów do nowej linii włącznie (lub EOF, jeśli plik nie kończy się nową linią).
Zauważ, że funkcja wyraźnie zwraca -1
(a nie EOF!), Gdy wystąpi błąd lub osiągnie EOF.
Możesz zauważyć, że bardzo rzadko przeliterujemy słowo „EOF”. Zazwyczaj wykrywamy stan błędu w inny sposób, który jest dla nas od razu interesujący (np. Brak wykonania tak dużej liczby operacji we / wy, jak chcieliśmy). W każdym przykładzie jest jakaś funkcja API, która może nam wyraźnie powiedzieć, że napotkano stan EOF, ale w rzeczywistości nie jest to bardzo przydatna informacja. To jest o wiele więcej szczegółów, niż nam się często zależy. Liczy się to, czy We / Wy się powiodło, bardziej niż to, w jaki sposób zawiodło.
Ostatni przykład, który faktycznie pyta o stan EOF: Załóżmy, że masz ciąg znaków i chcesz przetestować, czy reprezentuje on liczbę całkowitą w całości, bez dodatkowych bitów na końcu, z wyjątkiem białych znaków. Przy użyciu iostreams C ++ wygląda to tak:
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
}
Używamy tutaj dwóch wyników. Pierwszym z nich jest iss
sam obiekt strumienia, aby sprawdzić, czy sformatowana ekstrakcja value
zakończyła się powodzeniem. Ale potem, po zużyciu spacji, wykonujemy kolejną operację I / O / iss.get()
i oczekujemy, że zakończy się ona niepowodzeniem jako EOF, co ma miejsce, jeśli cały ciąg został już wykorzystany przez sformatowaną ekstrakcję.
W standardowej bibliotece C można osiągnąć coś podobnego za pomocą strto*l
funkcji, sprawdzając, czy wskaźnik końca osiągnął koniec ciągu wejściowego.
Odpowiedź
while(!feof)
jest błędny, ponieważ testuje coś, co jest nieistotne i nie sprawdza się pod kątem czegoś, co musisz wiedzieć. Powoduje to, że błędnie wykonujesz kod, który zakłada, że uzyskuje dostęp do danych, które zostały pomyślnie odczytane, podczas gdy w rzeczywistości tak się nigdy nie stało.
feof()
do sterowania pętlą