Почему iostream :: eof внутри условия цикла считается неправильным?

498

Я только что нашел комментарий в этом, отвечая тем, что использование iostream::eof в условиях цикла является "почти наверняка неправильным". Обычно я использую что-то вроде while(cin>>n), которое, как я предполагаю, неявно проверяет EOF, почему проверка того, что eof явно использует iostream::eof неправильно?

Как это отличается от использования scanf("...",...)!=EOF в C (который я часто использую без проблем)?

  • 15
    scanf(...) != EOF также не будет работать в C, потому что scanf возвращает количество полей, успешно проанализированных и назначенных. Правильное условие: scanf(...) < n где n - количество полей в строке формата.
  • 5
    @Ben Voigt, он вернет отрицательное число (которое EOF обычно определяется как таковое) в случае достижения EOF
Показать ещё 6 комментариев
Теги:
iostream
c++-faq

4 ответа

448
Лучший ответ

Потому что iostream::eof возвращает только true после прочтения конца потока. Он не указывает, что следующее чтение будет концом потока.

Рассмотрим это (и предположим, что следующее чтение будет в конце потока):

while(!inStream.eof()){
  int data;
  // yay, not end of stream yet, now read ...
  inStream >> data;
  // oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
  // do stuff with (now uninitialized) data
}

Против этого:

int data;
while(inStream >> data){
  // when we land here, we can be sure that the read was successful.
  // if it wasn't, the returned stream from operator>> would be converted to false
  // and the loop wouldn't even be entered
  // do stuff with correctly initialized data (hopefully)
}

И на ваш второй вопрос: Потому что

if(scanf("...",...)!=EOF)

совпадает с

if(!(inStream >> data).eof())

и не, как

if(!inStream.eof())
    inFile >> data
  • 11
    Стоит отметить, что if (! (InStream >> data) .eof ()) тоже ничего полезного не делает. Ошибка 1: Не будет введено условие, если после последнего фрагмента данных не было пробелов (последний элемент данных не будет обработан). Ошибка 2: Он войдет в условие, даже если чтение данных не удалось, до тех пор, пока EOF не был достигнут (бесконечный цикл, обработка одних и тех же старых данных снова и снова).
  • 0
    Немного не по теме, но позвольте мне это выразить. Если кто-то использует ленивую оценку, будет ли этот подход успешным без проблем?
Показать ещё 7 комментариев
84

Верх нижней части: При правильной обработке белого пространства, как eof можно использовать (и даже быть более надежным, чем fail() для проверки ошибок):

while( !(in>>std::ws).eof() ) {  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    

(Спасибо Tony D за предложение выделить ответ. См. его комментарий ниже для примера, почему это более надежное.)


Основной аргумент против использования eof(), похоже, не содержит важной тонкости в отношении роли белого пространства. Мое предложение состоит в том, что проверка eof() явно не только не всегда "всегда ошибочна", что, по-видимому, является основным мнением в этом и подобном потоке SO, но при правильном обращении с белым пространством он обеспечивает чище и более надежная обработка ошибок, и это всегда правильное решение (хотя и не обязательно tersest).

Подводя итог тому, что предлагается, поскольку "правильное" окончание и порядок чтения следующие:

int data;
while(in >> data) {  /* ... */ }

// which is equivalent to 
while( !(in >> data).fail() )  {  /* ... */ }

Ошибка, вызванная попыткой чтения за пределами eof, принимается за условие завершения. Это означает, что нет простого способа отличить успешный поток и тот, который действительно терпит неудачу по другим причинам, кроме eof. Возьмите следующие потоки:

  • 1 2 3 4 5<eof>
  • 1 2 a 3 4 5<eof>
  • a<eof>

while(in>>data) завершается с набором failbit для всех трех входных данных. В первом и третьем также устанавливается eofbit. Таким образом, за циклом нужна очень уродливая дополнительная логика, чтобы отличить правильный вход (1-й) от неправильных (2-й и 3-й).

Принимая во внимание следующее:

while( !in.eof() ) 
{  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    

Здесь in.fail() проверяет, что до тех пор, пока есть что прочитать, он правильный. Цель - не просто терминатор цикла.

До сих пор так хорошо, но что происходит, если в потоке есть конечное пространство - что звучит как основная проблема с eof() как терминатором?

Нам не нужно сдавать свою обработку ошибок; просто съешь белое пространство:

while( !in.eof() ) 
{  
   int data;
   in >> data >> ws; // eat whitespace with std::ws
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}

std::ws пропускает любое потенциальное (ноль или более) конечное пространство в потоке при установке eofbit и не failbit. Таким образом, in.fail() работает так, как ожидалось, пока есть хотя бы одна информация для чтения. Если все пустые потоки также приемлемы, то правильная форма:

while( !(in>>ws).eof() ) 
{  
   int data;
   in >> data; 
   if ( in.fail() ) /* handle with break or throw */; 
   /* this will never fire if the eof is reached cleanly */
   // now use data
}

Сводка: Правильно построенный while(!eof) не только возможен, но и ошибочен, но позволяет локализовать данные в пределах области действия и обеспечивает более чистое разделение проверки ошибок от бизнеса, как обычно. При этом while(!fail) является, несомненно, более распространенной и краткой идиомой, и может быть предпочтительнее в простых (одиночных данных для типа чтения).

  • 6
    « Таким образом, после цикла нет (простого) способа отличить правильный ввод от неправильного. » За исключением того, что в одном случае установлены и eofbit и failbit , в другом - только failbit . Вам нужно проверить это только один раз после завершения цикла, а не на каждой итерации; он выйдет из цикла только один раз, поэтому вам нужно только проверить, почему он вышел из цикла один раз. while (in >> data) отлично работает для всех пустых потоков.
  • 3
    То, что вы говорите (и высказанное ранее замечание), состоит в том, что плохо отформатированный поток может быть идентифицирован как !eof & fail past loop. Есть случаи, в которых нельзя на это положиться. Смотрите выше комментарий ( goo.gl/9mXYX ). В любом случае, я не предлагаю eof -check как всегда лучшую альтернативу. Я просто говорю, что это возможно , и (в некоторых случаях более подходящим) способ сделать это, а не «безусловно , не так!» как это обычно утверждается здесь, в SO.
Показать ещё 2 комментария
57

Потому что, если программисты не пишут while(stream >> n), они, возможно, напишут это:

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}

Здесь проблема заключается в том, что вы не можете выполнить some work on n, не проверив, действительно ли чтение потока было успешным, потому что если оно не увенчалось успехом, ваш some work on n приведет к нежелательному результату.

Все дело в том, что eofbit, badbit или failbit устанавливаются после попытки чтения из потока. Так что если stream >> n не удается, тогда eofbit, badbit или failbit устанавливается немедленно, поэтому его более идиоматично, если вы пишете while (stream >> n), потому что возвращенный объект stream преобразуется в false, если был некоторый сбой при чтении из потока и, следовательно цикл останавливается. И он преобразуется в true, если чтение было успешным, и цикл продолжается.

  • 1
    Помимо упомянутого «нежелательного результата» при выполнении работы с неопределенным значением n , программа может также попасть в бесконечный цикл , если сбойная операция потока не потребляет никакого ввода.
-3
1 while (!read.fail()) {
2     cout << ch;
3     read.get(ch);
4 }

Если вы используете строку 2 в 3 и строку 3 в 2, вы дважды печатаете ch. Итак, cout перед чтением.

  • 4
    Каково значение ch на первой итерации?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню