关于c ++:为什么iostream :: eof在循环条件中(即`while(!stream.eof())`)被认为是错误的?

Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?

我刚在回答中发现一条评论,说在循环条件中使用iostream::eof几乎肯定是错误的。我通常使用类似于while(cin>>n)的东西——我猜这暗含地检查了EOF。

为什么明确使用while (!cin.eof())检查eof是错误的?

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


因为iostream::eof只在读取流的末尾之后返回true。它并不表示下一次读取将是流的结尾。

考虑这一点(然后假设下一次读取将在流的末尾):

1
2
3
4
5
6
7
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
}

反对这一点:

1
2
3
4
5
6
7
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)
}

关于第二个问题:因为

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

是一样的

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

和不一样

1
2
if(!inStream.eof())
    inFile >> data


底线顶端:正确处理空白,可以使用eof(甚至比fail()更可靠)进行错误检查:

1
2
3
4
5
6
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线程中这似乎是一个压倒一切的观点,而且通过适当地处理空白,它提供了一个更干净、更可靠的错误处理,并且始终是正确的解决方案(尽管,不一定是最简单的)。

总结建议的"适当"终止和阅读顺序如下:

1
2
3
4
5
int data;
while(in >> data) {  /* ... */ }

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

超出EOF的读取尝试失败被视为终止条件。这意味着,要区分一个成功的流和一个真正失败的流,除了EOF,没有一个简单的方法。采用以下流程:

  • 1 2 3 4 5
  • 1 2 a 3 4 5
  • a

while(in>>data)以一组failbit终止,用于所有三个输入。在第一和第三阶段,也设置了eofbit。因此,在循环之后,需要非常丑陋的额外逻辑来区分正确的输入(第一个)和不正确的输入(第二个和第三个)。

鉴于,采取以下措施:

1
2
3
4
5
6
7
while( !in.eof() )
{  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */;
   // now use data
}

在这里,in.fail()确认只要有东西要读,它就是正确的。它的目的不仅仅是一个while循环终止符。

到目前为止还不错,但如果流中有尾随空间,会发生什么情况——这听起来像是对作为终结符的eof()的主要关注?

我们不需要放弃我们的错误处理;只需占用空白:

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

当设置eofbit而不是failbit时,std::ws跳过流中的任何潜在(零或更多)尾随空间。因此,只要至少有一个数据需要读取,in.fail()就可以按预期工作。如果所有空白流也可以接受,那么正确的形式是:

1
2
3
4
5
6
7
8
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),他们可能会写:

1
2
3
4
5
while(!stream.eof())
{
    stream >> n;
    //some work on n;
}

这里的问题是,如果不首先检查流读取是否成功,您就不能执行some work on n,因为如果失败,您的some work on n将产生不希望的结果。

关键是,在试图从流中读取数据后,设置了eofbitbadbitfailbit。因此,如果stream >> n失败,那么eofbitbadbitfailbit将立即设置,因此,如果编写while (stream >> n)则更为惯用,因为如果从流中读取失败,返回的对象stream将转换为false而导致循环停止。如果读取成功并且循环继续,则转换为true


其他的答案解释了为什么while (!stream.eof())中的逻辑是错误的,以及如何修复它。我想关注一些不同的事情:

why is checking for eof explicitly using iostream::eof wrong?

一般来说,检查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) { ... }的成功一样。