Почему iostream :: eof внутри условия цикла (т. Е. `While (! Stream.eof ())`) считается неправильным? - PullRequest
551 голосов
/ 09 апреля 2011

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

Почему проверка eof явно с использованием while (!cin.eof()) неверна?

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

Ответы [ 4 ]

502 голосов
/ 09 апреля 2011

Поскольку 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
98 голосов
/ 24 ноября 2012

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

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

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


Theосновной аргумент против использования 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() проверяет, что если есть что почитать, то оно правильное.Это не просто терминатор цикла while.

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

67 голосов
/ 09 апреля 2011

Потому что, если программисты не пишут 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, если чтение прошло успешно и цикл продолжается.

2 голосов
/ 04 мая 2019

Другие ответы объяснили, почему логика неверна в while (!stream.eof()) и как ее исправить. Я хочу сосредоточиться на чем-то другом:

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

В общих чертах, проверка только для eof является неправильной, поскольку извлечение потока (>>) может завершиться неудачно без попадания в конец файла. Если у вас есть, например, int n; cin >> n; и поток содержит hello, тогда h не является допустимой цифрой, поэтому извлечение не будет выполнено без достижения конца ввода.

Эта проблема в сочетании с общей логической ошибкой проверки состояния потока перед попыткой чтения из него, что означает, что для N входных элементов цикл будет выполняться N + 1 раз, приводит к следующим признакам :

  • Если поток пуст, цикл будет запущен один раз. >> потерпит неудачу (нет входных данных для чтения), и все переменные, которые должны были быть установлены (stream >> x), фактически неинициализированы. Это приводит к обработке данных мусора, которые могут проявляться как бессмысленные результаты (часто огромные числа).

  • Если поток не пустой, цикл запустится снова после последнего действительного ввода. Поскольку в последней итерации все операции >> не выполняются, переменные, скорее всего, сохранят свое значение по сравнению с предыдущей итерацией. Это может проявляться как «последняя строка печатается дважды» или «последняя входная запись обрабатывается дважды».

  • Если поток содержит искаженные данные, но вы проверяете только на .eof, вы получаете бесконечный цикл. >> не удастся извлечь какие-либо данные из потока, поэтому цикл вращается, даже не достигнув конца.


Напомним: решение состоит в том, чтобы проверить успешность самой операции >>, а не использовать отдельный метод .eof(): while (stream >> n >> m) { ... }, так же как в C вы проверяете успешность самого вызова scanf : while (scanf("%d%d", &n, &m) == 2) { ... }.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...