Ответ 1
Я хотел бы предоставить абстрактную, высокоуровневую перспективу.
Concurrency и одновременность
Операции ввода/вывода взаимодействуют с окружающей средой. Окружающая среда не является частью вашей программы, а не под вашим контролем. Окружающая среда действительно существует "одновременно" с вашей программой. Как и во всех параллельных вещах, вопросы о "текущем состоянии" не имеют смысла: нет понятия "одновременность" в параллельных событиях. Многие свойства состояния просто не существуют одновременно.
Позвольте мне сделать это более точным: предположим, вы хотите спросить: "У вас больше данных". Вы можете задать это из параллельного контейнера или вашей системы ввода-вывода. Но ответ, как правило, невозможен и, следовательно, бессмыслен. Так что, если контейнер говорит "да" – к моменту, когда вы попытаетесь прочитать, у него больше нет данных. Аналогичным образом, если ответ "нет", к моменту попытки чтения данные могут быть получены. Вывод заключается в том, что просто нет такого свойства, как "у меня есть данные", поскольку вы не можете действовать значимо в ответ на любой возможный ответ. (Ситуация немного лучше с буферизованным входом, где вы, возможно, можете получить "да, у меня есть данные", который представляет собой какую-то гарантию, но вам все равно придется иметь дело с противоположным случаем. конечно же так же плохо, как я описал: вы никогда не знаете, заполнен ли этот диск или этот сетевой буфер.)
Итак, мы заключаем, что невозможно, а на самом деле необоснованно, запрашивать систему ввода-вывода, сможет ли она выполнить операцию ввода-вывода. Единственный возможный способ, с которым мы можем взаимодействовать (как и с параллельным контейнером), - это попытаться выполнить операцию и проверить, удалось ли это или не удалось. В тот момент, когда вы взаимодействуете с окружающей средой, тогда и только тогда вы можете знать, действительно ли взаимодействие действительно возможно, и в этот момент вы должны выполнить выполнение взаимодействия. (Это будет "точка синхронизации", если вы это сделаете.)
EOF
Теперь мы попадаем в EOF. EOF - это ответ, который вы получаете от попытки ввода-вывода. Это означает, что вы пытались что-то прочитать или написать, но при этом вам не удалось прочитать или написать какие-либо данные, а вместо этого столкнулся конец ввода или вывода. Это справедливо для практически всех API ввода-вывода, будь то стандартная C-библиотека, С++ iostreams или другие библиотеки. Пока операции ввода-вывода преуспевают, вы просто не можете знать, будут ли дальнейшие дальнейшие операции успешными. Вы всегда должны сначала попробовать операцию, а затем ответить на успех или неудачу.
Примеры
В каждом из примеров обратите внимание на то, что мы сначала попытаемся выполнить операцию ввода-вывода и затем будем использовать результат, если он действителен. Обратите внимание, что мы всегда должны использовать результат операции ввода-вывода, хотя в каждом примере результат принимает разные формы и формы.
-
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
, число преобразованных элементов. -
С++, форматированное извлечение iostreams:
for (int n; std::cin >> n; ) { consume(n); }
В результате мы должны использовать сам
std::cin
, который может быть оценен в булевом контексте и сообщает нам, находится ли поток в состоянииgood()
. -
С++, 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 встречается, но на самом деле это не очень полезная информация. Это гораздо более подробно, чем мы часто заботимся. Важно то, что I/O преуспел, более того, чем это не удалось.
-
Последний пример, который фактически запрашивает состояние EOF: предположим, что у вас есть строка и вы хотите проверить, что она представляет целое целое, без лишних бит в конце, кроме пробелов. Используя 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(!eof)
неверен, потому что он проверяет что-то, что не имеет значения, и не может проверить что-то, что вам нужно знать. В результате вы ошибочно выполняете код, который предполагает, что он обращается к данным, которые были прочитаны успешно, а на самом деле этого не произошло.