Как работает eof() ifstream?

#include <iostream>
#include <fstream>

int main() {
    std::fstream inf( "ex.txt", std::ios::in );
    while( !inf.eof() ) {
        std::cout << inf.get() << "\n";
    }
    inf.close();
    inf.clear();
    inf.open( "ex.txt", std::ios::in );
    char c;
    while( inf >> c ) {
        std::cout << c << "\n";
    }
    return 0;
}

Я действительно запутался в функции eof(). Предположим, что содержимое моего ex.txt было:

abc

Он всегда считывает дополнительный символ и показывает -1 при чтении с использованием eof(). Но inf >> c дал правильный вывод, который был «abc»? Может ли кто-нибудь помочь мне объяснить это?


person Chan    schedule 26.12.2010    source источник
comment
Спасибо, что указали на это. Я соревновался на TopCoder и обычно импортирую все пространства имен, чтобы сэкономить время.   -  person Chan    schedule 26.12.2010


Ответы (4)


-1 — это способ get сказать, что вы достигли конца файла. Сравните это с помощью std::char_traits<char>::eof() (или std::istream::traits_type::eof()) - избегайте -1, это магическое число. (Хотя другой немного многословен - вы всегда можете просто позвонить istream::eof)

Флаг EOF устанавливается только в том случае, если операция чтения пытается читать за концом файла. Если у меня есть 3-байтовый файл, и я читаю только 3 байта, EOF равен false, потому что я еще не пытался читать дальше конца файла. Хотя это кажется запутанным для файлов, которые обычно знают свой размер, EOF неизвестен до тех пор, пока не будет предпринята попытка чтения на некоторых устройствах, таких как каналы и сетевые сокеты.

Второй пример работает, так как inf >> foo всегда будет возвращать inf с побочным эффектом попытки прочитать что-то и сохранить это в foo. inf в if или while оценивается как true, если файл "хороший": нет ошибок, нет EOF. Таким образом, когда чтение завершается ошибкой, inf оценивается как false, и ваш цикл корректно прерывается. Однако возьмите эту распространенную ошибку:

while(!inf.eof())  // EOF is false here
{
    inf >> x;      // read fails, EOF becomes true, x is not set
    // use x       // we use x, despite our read failing.
}

Однако это:

while(inf >> x)  // Attempt read into x, return false if it fails
{
    // will only be entered if read succeeded.
}

Чего мы и хотим.

person Thanatos    schedule 26.12.2010
comment
Спасибо Танатос. Кажется, теперь я понял ;) - person Chan; 26.12.2010
comment
Вероятно, не следует использовать ›› вместо get(). Помните, что оператор ›› всегда пропускает следующие пробелы перед попыткой чтения следующего значения, в то время как get() считывает каждый символ по одному. - person Martin York; 26.12.2010
comment
@Thanatos: Извините, что беспокою вас, но ... А как насчет потоков? Вы сказали, что EOF устанавливается, когда мы пытаемся прочитать символы после конца файла, но что происходит, когда мы пытаемся peek() во входном потоке cin, когда в нем нет символов? Стандарт говорит, что traits::eof() является вызывается, но peek(), кажется, ожидает ввода, когда я пытаюсь использовать его таким образом. - person chosentorture; 11.07.2013
comment
peek вернет EOF, если вы попытаетесь заглянуть за конец ввода. Однако cin обычно представляет собой терминал, в который вы вводите текст. (Кажется, это ваш случай.) Ожидание ввода данных пользователем — это не то же самое, что EOF, просто источник данных (вы) работает медленно. Итак, peek блокируется, ожидая, пока вы введете его. Если вы введете EOF (Ctrl+D в Linux), то вернется peek, а eof должно быть истинным. В противном случае он возвращает то, что вы набрали. - person Thanatos; 24.08.2013
comment
У меня есть вопрос. В документе говорится, что логический оператор определяется как !fail(), поэтому поток EOFed следует интерпретировать как истинный? Как получилось, что он вернет false? - person Zesen Qian; 25.12.2015
comment
Вопрос решен. В приведенной выше ссылке failed() Reaching the End-of-File sets the eofbit. But note that operations that reach the End-of-File may also set the failbit if this makes them fail (thus setting both eofbit and failbit). - person Zesen Qian; 25.12.2015

Флаг EOF устанавливается только после того, как операция чтения попытается прочитать за конец файла. get() возвращает символическую константу traits::eof() (которая просто равна -1), потому что она достигла конца файла и больше не может считать данные, и только в этот момент eof() будет истинным. Если вы хотите проверить это условие, вы можете сделать что-то вроде следующего:

int ch;
while ((ch = inf.get()) != EOF) {
    std::cout << static_cast<char>(ch) << "\n";
}
person Justin Spahr-Summers    schedule 26.12.2010
comment
Флаг EOF устанавливается только после того, как операция чтения достигает конца файла. это именно то утверждение, которое вызывает путаницу в отношении EOF. Флаг EOF устанавливается только после того, как операция чтения попытается прочитать за концом файла. Последний бит имеет решающее значение: если я прочитаю 3 байта из файла длиной 3 байта, EOF будет ложным, пока я не попытаюсь прочитать еще раз. - person Thanatos; 26.12.2010
comment
iostream не возвращает константу EOF. Возвращаемое значение int istream::get() кажется неопределенным в конце файла — вы должны проверить istream::eof() - person Ken Bloom; 26.12.2010
comment
@Кен Блум: ты уверен? В моем стандарте указано, что возвращается: c, если доступно, иначе traits::eof(). 27.6.1.3 (черновик 2, 2 декабря 96 г.) - person Thanatos; 26.12.2010
comment
‹s›Вы правы, но это все еще далеко от константы EOF, используемой C stdio.‹s› Я ошибаюсь. Где-то в определении char_traits<char> сказано, что char_traits<char>::eof() возвращает EOF - person Ken Bloom; 26.12.2010
comment
@Ken Bloom Только что нашел раздел 21.1.3.1 struct char_traits<char>: член eof() возвращает EOF. - person Justin Spahr-Summers; 26.12.2010
comment
@ Джастин, я тоже только что нашел это в то же время, что и ты. - person Ken Bloom; 26.12.2010
comment
@Ken Bloom: Моя точка зрения заключалась не столько в том, что он может возвращать или не возвращать EOF - хотя, хорошая находка, я не знал, что EOF и char_traits<char>::eof() гарантированно равны, но что его возвращаемое значение было четко определено для какая-то постоянная где-то. - person Thanatos; 26.12.2010
comment
@Justin Spahr-Summers: Спасибо за еще одно отличное решение;) - person Chan; 26.12.2010
comment
Вы по-прежнему не должны тестировать EOF в условии. Что произойдет, если что-то еще пойдет не так в потоке и будет установлен плохой бит. Тогда поток никогда не вернет EOF. - person Martin York; 26.12.2010
comment
@Martin York. флаг EOF может быть не установлен, но get() все равно будет возвращать traits::eof(), который используется в качестве общего кода ошибки в случае сбоя чтения. - person Justin Spahr-Summers; 26.12.2010
comment
@Ken Bloom: где я могу найти стандарт C++ для ссылки? Спасибо! - person Chan; 26.12.2010
comment
@Chan: вам нужно купить стандарт, но большинство из нас смотрит на рабочий проект N1905. - person Ken Bloom; 26.12.2010

iostream не знает, что он находится в конце файла, пока не попытается прочитать этот первый символ после конца файла.

В примере кода на сайте cplusplus.com сказано сделать это следующим образом: (Но вы на самом деле не должен делать это таким образом)

  while (is.good())     // loop while extraction from file is possible
  {
    c = is.get();       // get character from file
    if (is.good())
      cout << c;
  }

Лучшей идиомой является перемещение чтения в условие цикла, например так: (Это можно сделать с помощью всех операций чтения istream, которые возвращают *this, включая оператор >>)

  char c;
  while(is.get(c))
    cout << c;
person Ken Bloom    schedule 26.12.2010
comment
Должны ли istream читать операции ... включая оператор << читать ... включая оператор >> ? - person Thanatos; 26.12.2010
comment
@Танатос: да. Вот что я получаю за публикацию поздно ночью. - person Ken Bloom; 26.12.2010
comment
Вы хотите !is.fail() вместо is.good(). При чтении, например, числа из файла поток должен достичь конца файла, прежде чем определить, прочитана ли последняя цифра числа. В этом случае число будет успешно прочитано, но будет достигнут конец файла, в результате чего is.good() вернет false. Это может привести к тому, что вы пропустите последнее значение в файле. Вместо этого проверка на !is.fail() будет правильной во всех случаях. - person binki; 12.03.2018
comment
@KenBloom - если поток в вашем while(is.get(c)) является двоичным, не приведет ли это к преждевременному завершению цикла, если значение байта равно 0 (полностью ожидаемое значение для двоичного файла?) лучше использовать while(n=is.get() != Traits::eof()) для бинарного файла? - person SMGreenfield; 09.07.2019
comment
@smgreenfield: перегрузка get, которая принимает выходной параметр, возвращает istream& для дальнейшего связывания или сообщения об ошибках. - person Ken Bloom; 11.07.2019

eof() проверяет eofbit в состоянии потока.

При каждой операции чтения, если позиция находится в конце потока и необходимо прочитать больше данных, для eofbit устанавливается значение true. Поэтому вы получите дополнительный символ, прежде чем получите eofbit=1.

Правильный способ — проверить, был ли достигнут eof (или успешно ли выполнена операция чтения) после операции чтения. Это то, что делает ваша вторая версия - вы выполняете операцию чтения, а затем используете результирующую ссылку на объект потока (которая >> возвращает) в качестве логического значения, что приводит к проверке на fail().

person zeuxcg    schedule 26.12.2010
comment
Спасибо за объяснение ;) - person Chan; 26.12.2010
comment
На самом деле он проверяет несколько битов. - person Martin York; 26.12.2010