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

C++ 为什么循环条件(即,`while(!stream.eof())`)中的iostream::eof被认为是错误的?,c++,iostream,c++-faq,C++,Iostream,C++ Faq,我刚刚在回答中发现一条评论说,在循环条件中使用iostream::eof几乎肯定是错误的。我通常使用whilecin>>n之类的东西——我猜这会隐式地检查EOF 为什么检查eof显式使用while!怎么了 它与使用scanf有什么不同=我经常使用的C语言中的EOF没有问题?因为iostream::EOF只有在读取流的结尾后才会返回true。它并不表示下一次读取将是流的结束 考虑这一点,并假设下一次读取将在流的末尾: while(!inStream.eof()){ int data; //

我刚刚在回答中发现一条评论说,在循环条件中使用iostream::eof几乎肯定是错误的。我通常使用whilecin>>n之类的东西——我猜这会隐式地检查EOF

为什么检查eof显式使用while!怎么了

它与使用scanf有什么不同=我经常使用的C语言中的EOF没有问题?

因为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
而且和

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

因为如果程序员不在Stream>>n期间编写,他们可能会编写以下内容:

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}
这里的问题是,在没有首先检查流读取是否成功的情况下,您无法对n执行某些工作,因为如果读取不成功,您对n执行的某些工作将产生不希望的结果

关键在于,eofbit、badbit或failbit是在尝试从流中读取后设置的。因此,如果stream>>n失败,则会立即设置eofbit、badbit或failbit,因此,如果在stream>>n期间写入,则更为惯用,因为如果从流读取时出现一些故障,则返回的对象流将转换为false,从而循环停止。如果读取成功并且循环继续,它将转换为true。

底线顶部:通过正确处理空白,以下是如何使用eof,甚至在错误检查中比失败更可靠:

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 12345 A. 而在>>数据终止时,所有三个输入都有一个设置的故障位。在第一个和第三个中,还设置了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
}
设置eofbit时,ws跳过流中任何可能的零或更多尾随空间,而不是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不仅可行而且没有错误,而且允许在范围内对数据进行本地化,并提供了错误检查与日常业务的更清晰分离。尽管如此,但是!fail无疑是一个更常见、更简洁的习惯用法,在每种读取类型的简单单数据场景中可能更可取

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

为什么显式使用iostream::eof检查eof是错误的

一般来说,只检查eof是错误的,因为流提取>>可能会失败,而不会到达文件的末尾。如果您有,例如int n;cin>>n;并且流包含hello,那么h不是一个有效数字,因此提取将失败,而不会到达输入的末尾

此问题,再加上在尝试读取流状态之前检查流状态的一般逻辑错误,这意味着对于N个输入项,循环将运行N+1次,导致以下症状:

如果流为空,循环将运行一次。>>将失败—没有要读取的输入,所有本应由stream>>x设置的变量实际上都未初始化。这导致垃圾数据被处理,这可能表现为无意义的结果,通常是巨大的数字

如果您的标准库符合C+ +11,现在情况有点不同了:失败的>>现在将数值变量设置为0,而不是将它们除字符外未初始化

如果流不是空的,循环将在最后一个有效输入之后再次运行。因为在上一次迭代中,所有>>操作都失败了,所以变量可能会保留上一次迭代中的值。这可能表现为最后一行打印两次或最后一条输入记录处理两次

这应该与C++11(见上文)有所不同:现在您得到的是一个0的幻影记录,而不是重复的最后一行

如果流包含格式不正确的数据,但您只检查.eof,那么您将得到一个无限循环。>>将无法从流中提取任何数据,因此循环将原地旋转而不会到达终点


总而言之:解决方案是测试>>操作本身的成功,而不是使用单独的.eof方法:while stream>>n>>m{…},就像在C中测试scanf调用本身的成功一样:while scanf%d%d,&n,&m==2{…}。

循环中的iostream::eof被认为是错误的,因为我们还没有达到eof。因此,这并不意味着下一次读取将成功

我将用两个示例代码解释我的陈述,这肯定会帮助您更好地理解这个概念。比如说,当我们想用C++中的文件流读取文件时。当我们使用循环写入文件时,如果我们使用stream.eof检查文件的结尾,我们实际上是在检查文件是否已到达结尾

示例代码

当我们在循环中直接使用流时,我们不会再次检查条件

示例代码


扫描…!=EOF在C中也不起作用,因为scanf返回成功解析和分配的字段数。正确的条件是扫描…>data.eof也没有做任何有用的事情。谬误1:如果最后一条数据后没有空格,则不会进入条件。最后一条数据不会被处理。谬误2:即使读取数据失败,它也会进入条件,只要EOF没有达到无限循环,反复处理相同的旧数据。我认为值得指出的是,这个答案有点误导。在提取int或std::strings或类似内容时,当您在末尾之前提取一个且提取到达末尾时,将设置EOF位。你不必再读了。从文件读取时未设置它的原因是,末尾有一个额外的\n。我已经在这本书中讲过了。读取字符是另一回事,因为它一次只提取一个字符,不会继续到达末尾。主要问题是,仅仅因为我们还没有达到EOF,并不意味着下一次读取会成功。@sftrabbit:都是真的,但不是很有用。。。即使没有尾随“\n”,也可以合理地希望其他尾随空格与整个文件中的其他空格一致处理,即跳过。此外,当你在之前提取一个的时候,一个微妙的结果就是while!当输入完全为空时,eof对ints或std::字符串不起作用,因此即使知道没有尾随\n也需要小心。@TonyD完全同意。我之所以这么说,是因为我认为大多数人在阅读本文和类似答案时都会认为,如果流包含Hello,没有尾随空格或\n,并且提取了std::string,它将从H提取字母到o,停止提取,然后不设置EOF位。事实上,它会设置EOF位,因为正是EOF停止了提取。只是希望为人们澄清这一点。因此,通过循环,没有简单的方法来区分正确的输入和不正确的输入。除了在一种情况下同时设置eofbit和failbit外,在另一种情况下仅设置failbit。您只需要在循环结束后测试一次,而不是在每次迭代中;它只会离开循环一次,所以您只需要检查它为什么离开循环一次。在>>dat中时
a适用于所有空白流。您刚才说的是,格式错误的流可以被识别为!eof&失败通过循环。在有些情况下,我们不能依靠这一点。见上述评论。尽管如此,我并不建议eof支票作为更好的选择。我只是说,这是一种可能的,在某些情况下更合适的方法,而不是最肯定的错误!作为一个例子,请考虑如何检查错误,其中数据是一个重载操作符>一次读取多个字段的结构——一个更简单的案例支持您的观点是流> MyItIt,其中流包含例如:EFOBIT和FROBIT。这比operator>>场景更糟糕,在operator>>场景中,用户提供的重载至少可以选择在返回帮助支持之前清除eofbit,而s>>x使用。更一般地说,这个答案可能需要一个清理-只有在最后一段时间!在>>中,ws.eof通常是健壮的,它被隐藏在末尾。除了在未定义的n值上进行工作时所提到的不希望出现的结果外,如果失败的流操作不消耗任何输入,程序也可能陷入无限循环。这是最准确的答案,尽管从c++11开始,我不相信这些变量是未初始化的