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

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

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

4 ответов


, потому что 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

нижняя строка сверху: при правильной обработке белого пространства, следующее, Как 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) бесспорно, более распространенная и лаконичная идиома и может быть предпочтена в простых (одиночных данных на тип чтения) сценариях.


потому что если программисты не пишут 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 while (!read.fail()) {
2     cout << ch;
3     read.get(ch);
4 }

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