为什么循环条件内的 iostream::eof(即 \`while (!stream.eof())\`)被认为是错误的?

新手上路,请多包涵

我刚刚在 这个 答案中发现了一条评论,说在循环条件中使用 iostream::eof 是“几乎肯定是错误的”。我通常使用 while(cin>>n) 类的东西——我猜它会隐式检查 EOF。

为什么使用 while (!cin.eof()) 显式检查 eof 错误?

它与在 C 中使用 scanf("...",...)!=EOF 有什么不同(我经常使用没有问题)?

原文由 MAK 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 747
2 个回答

因为 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

原文由 Xeo 发布,翻译遵循 CC BY-SA 3.0 许可协议

底线顶部: 通过正确处理空白,以下是如何使用 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 线程中最重要的观点——而且通过正确处理空白,它提供了更清洁和更可靠的错误处理,并且 始终是正确 的解决方案(尽管不一定是最简单的)。

总结建议的“正确”终止和阅读顺序如下:

 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 。因此,在循环之后,需要非常难看的额外逻辑来区分正确的输入(第一个)和不正确的输入(第二个和第三个)。

鉴于,采取以下措施:

 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 在设置 eofbitfailbit --- 时跳过流中的任何潜在(零或更多)尾随空间。因此,只要至少有一个数据要读取, 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) 无疑是一个更常见和简洁的习语,并且在简单(每个读取类型的单个数据)场景中可能是首选。

原文由 sly 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题