Я хотів би надати абстрактну перспективу високого рівня.
Паралельність і одночасність
Операції вводу / виводу взаємодіють із середовищем. Навколишнє середовище не є частиною вашої програми і не під вашим контролем. Навколишнє середовище справді існує "одночасно" з вашою програмою. Як і у всіх ситуаціях, що паралельно ставляться, питання щодо "поточного стану" не мають сенсу: Не існує поняття "одночасність" у різних одночасних подіях. Багато властивостей держави просто не існують одночасно.
Дозвольте зробити це більш точним: припустимо, ви хочете запитати, "чи є у вас більше даних". Ви можете запитати про це одночасно з контейнером або з вашої системи вводу / виводу. Але відповідь, як правило, неприйнятна, а значить, безглузда. Що робити, якщо контейнер каже "так" - до моменту, коли ви спробуєте прочитати, він може більше не мати даних. Аналогічно, якщо відповідь "ні", до моменту спроби читання дані, можливо, надійшли. Висновок такий просто єнемає властивості на зразок "У мене є дані", оскільки ви не можете діяти змістовно у відповідь на будь-яку можливу відповідь. (Ситуація дещо краща з буферним вкладом, де ви, можливо, отримаєте "так, у мене є дані", які є певною гарантією, але ви все одно повинні мати можливість вирішувати протилежний випадок. це, звичайно, так само погано, як я описав: ви ніколи не знаєте, чи цей диск чи мережевий буфер повно.)
Таким чином , ми приходимо до висновку , що це неможливо, і справді ип розумного , щоб задати систему введення / виведення він буде в змозі виконати операцію вводу / виводу. Єдиний можливий спосіб, з яким ми можемо взаємодіяти з ним (так само, як і з одночасним контейнером), - це зробити спробу операцію і перевірити, чи вдалося це чи не вдалося. У той момент, коли ви взаємодієте з оточенням, тоді і лише тоді ви зможете дізнатися, чи була взаємодія реально можливою, і в цей момент ви повинні взяти на себе зобов'язання виконувати взаємодію. (Це "точка синхронізації", якщо ви хочете.)
EOF
Тепер ми переходимо до EOF. EOF - це відповідь, яку ви отримуєте від спроби операції вводу / виводу. Це означає, що ви намагалися щось прочитати чи написати, але при цьому вам не вдалося прочитати чи записати будь-які дані, і натомість виник кінець вводу чи виводу. Це стосується практично всіх API API вводу / виводу, будь то стандартна бібліотека C, іострими 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:
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 ++, це виглядає так:
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, що є тим випадком, коли вся струна вже була використана форматованим вилученням.
У стандартній бібліотеці С ви можете досягти чогось подібного з strto*l
функціями, перевіривши, чи кінцевий покажчик досяг кінця рядка введення.
Відповідь
while(!feof)
помиляється, оскільки перевіряє щось, що не має значення, і не може перевірити те, що вам потрібно знати. У результаті ви помилково виконуєте код, який передбачає, що він отримує доступ до даних, які були успішно прочитані, адже насправді цього ніколи не було.
feof()
для керування петлею