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


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

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

4 495

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

Bottom-line top: при правильной обработке белого пространства, следующее, Как eof можно использовать (и даже, быть более надежным, чем fail() для проверки ошибок):

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

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


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

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

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 перед чтением.