Я хотел бы представить абстрактную перспективу высокого уровня.
Параллельность и одновременность
Операции ввода / вывода взаимодействуют со средой. Среда не является частью вашей программы и не находится под вашим контролем. Среда действительно существует "одновременно" с вашей программой. Как и в случае с другими вещами, вопросы о «текущем состоянии» не имеют смысла: не существует понятия «одновременности» между одновременными событиями. Многие свойства государства просто не существуют одновременно.
Позвольте мне уточнить это: предположим, вы хотите спросить: «У вас есть больше данных». Вы можете запросить это у параллельного контейнера или вашей системы ввода-вывода. Но ответ, как правило, бездействующий и, следовательно, бессмысленный. Так что, если контейнер скажет «да» - к тому времени, когда вы попытаетесь прочитать, у него больше не будет данных. Точно так же, если ответ «нет», к тому времени, когда вы попытаетесь прочитать, данные, возможно, уже поступили. Вывод таков :нет такого свойства, как «У меня есть данные», так как вы не можете действовать осмысленно в ответ на любой возможный ответ. (Ситуация несколько лучше с буферизованным вводом, где вы можете получить «да, у меня есть данные», что является своего рода гарантией, но вам все равно придется иметь дело с противоположным случаем. И с выводом ситуации это так же плохо, как я описал: вы никогда не знаете, заполнен ли этот диск или сетевой буфер.)
Таким образом , мы приходим к выводу , что это невозможно, и в самом деле ип разумного , чтобы задать систему ввода / вывода ли он будет в состоянии выполнить операцию ввода / вывода. Единственный возможный способ взаимодействия с ним (так же, как с параллельным контейнером) - это попытаться выполнить операцию и проверить, была ли она успешной или неудачной. В тот момент, когда вы взаимодействуете со средой, тогда и только тогда вы можете узнать, действительно ли взаимодействие было возможно, и в этот момент вы должны посвятить себя выполнению взаимодействия. (Это «точка синхронизации», если хотите.)
EOF
Теперь мы добрались до EOF. EOF - это ответ, который вы получаете от попытки ввода-вывода. Это означает, что вы пытались что-то прочитать или записать, но при этом вам не удалось прочитать или записать какие-либо данные, и вместо этого был обнаружен конец ввода или вывода. Это верно практически для всех API ввода-вывода, будь то стандартная библиотека C, iostreams C ++ или другие библиотеки. Пока операции ввода-вывода успешны, вы просто не можете знать, будут ли дальнейшие будущие операции успешными. Вы всегда должны сначала попробовать операцию, а затем ответить на успех или неудачу.
Примеры
В каждом из примеров обратите внимание на то, что мы сначала пытаемся выполнить операцию ввода-вывода, а затем используем результат, если он действителен. Отметим далее, что мы всегда должны использовать результат операции ввода-вывода, хотя результат принимает разные формы и формы в каждом примере.
C stdio, читайте из файла:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
Результат, который мы должны использовать n
, это количество прочитанных элементов (которое может быть равным нулю).
C STDIO, scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
Результатом, который мы должны использовать, является возвращаемое значение scanf
количества преобразованных элементов.
C ++, извлечение в формате iostreams:
for (int n; std::cin >> n; ) {
consume(n);
}
Результат, который мы должны использовать, std::cin
сам по себе, который может быть оценен в логическом контексте и говорит нам, находится ли поток в good()
состоянии.
C ++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
Результат, который мы должны использовать, снова std::cin
, как и прежде.
POSIX, write(2)
чтобы очистить буфер:
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 */ }
В результате мы используем k
количество записанных байтов. Дело в том, что мы можем знать только, сколько байтов было записано после операции записи.
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);
Результат, который мы должны использовать nbytes
, это число байтов до новой строки (включая EOF, если файл не заканчивался новой строкой).
Обратите внимание, что функция явно возвращает -1
(а не EOF!), Когда возникает ошибка или она достигает EOF.
Вы можете заметить, что мы очень редко произносим слово «EOF». Обычно мы обнаруживаем состояние ошибки другим способом, который более интересен для нас (например, невозможность выполнить столько операций ввода-вывода, сколько мы хотели). В каждом примере есть некоторая функция API, которая может явно сообщить нам, что с состоянием EOF было обнаружено, но на самом деле это не очень полезная часть информации. Это гораздо больше деталей, чем мы часто заботимся. Важно то, был ли ввод / вывод успешным, в большей степени, чем как он провалился.
Последний пример, который фактически запрашивает состояние EOF: предположим, что у вас есть строка и вы хотите проверить, что она представляет собой целое число полностью, без лишних битов в конце, кроме пробелов. Используя C ++ iostreams, это выглядит так:
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
}
Мы используем два результата здесь. Первый - iss
это сам объект потока, чтобы проверить, что отформатированное извлечение выполнено value
успешно. Но затем, после использования пустого пространства, мы выполняем еще одну операцию ввода-вывода iss.get()
и ожидаем, что она завершится с ошибкой как EOF, что является случаем, если вся строка уже была использована форматированным извлечением.
В стандартной библиотеке C вы можете добиться чего-то похожего с strto*l
функциями, проверив, что указатель конца достиг конца входной строки.
Ответ
while(!feof)
неправильно, потому что он проверяет что-то, что не имеет значения и не может проверить то, что вам нужно знать. В результате вы ошибочно выполняете код, который предполагает, что он обращается к данным, которые были успешно прочитаны, хотя на самом деле этого никогда не происходило.
feof()
для управления циклом