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

我刚刚在这个答案表示,在循环条件下使用iostream::eof"几乎肯定是错误的"。我通常使用while(cin>>n)之类的东西-我猜它会隐式检查EOF。

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

它与在C语言中使用scanf("...",...)!=EOF有什么不同(我经常会毫无问题地使用它)?

标签:c++,iostream,c++-faq

回答1:

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

回答2:

底线顶部:通过正确处理空白,以下是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之外,没有简单的方法来区分成功的流和确实失败的流。采取以下流:

  • 12345
  • 12a345
  • a

while(in>>data) all 三个输入的设置的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在设置eofbit不是failbit <时,跳过流中任何潜在的(零个或多个)尾随空间. code> 。因此,只要有至少一个数据要读取,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)无疑是一个更常见,更简洁的习惯用法,在简单(每种读取类型的单个数据)场景中可能是首选。

回答3:

因为如果程序员不写while(stream>>n),他们可能会这样写:

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}

这里的问题是,如果不首先检查流读取是否成功,就不能对n执行某些代码,因为如果不成功,则您对n进行某些代码就不能工作。会产生不希望的结果。

整个问题是,在尝试从计算机中读取eofbitbadbitfailbit时,设置为因此,如果stream>>n失败,则eofbitbadbitfailbit立即设置,因此如果您编写while(stream>>n),则设置更为惯用,因为返回的对象stream如果存在,将转换为false从流中读取失败,因此循环停止。如果读取成功并且循环继续,它将转换为true

回答4:

其他答案已解释了为什么while(!stream.eof())中的逻辑错误以及如何解决该问题。我想专注于其他事情:

为什么使用iostream::eof明确检查eof是错误的?

一般而言,仅检查eof 是错误的,因为流提取(>> )可能会失败而不会到达文件末尾。如果您有intn;cin >> n; ,并且流包含hello,则h不是有效数字,因此提取将失败而未到达输入的结尾。 / p>

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

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

    (如果您的标准库符合C ++ 11,现在情况会有所不同:失败的>> 现在将数字变量设置为0,而不是不进行初始化(char s除外)。

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

    (这与C ++ 11以来的表现有所不同(见上文):现在您得到了零的"幻像记录",而不是重复的最后一行。)

  • 如果流中包含格式错误的数据,但仅检查.eof,则会导致无限循环。 >> 将无法从流中提取任何数据,因此循环旋转到位而不到达终点。


回顾:解决方案是测试>> 操作本身是否成功,而不是使用单独的.eof()方法:while(stream >> n >> m){...} ,就像在C中一样,您测试scanf调用自身是否成功:while(scanf("%d%d",&n,&m)== 2){...}

回到顶部