为什么iostream :: eof处于循环条件(即`while(!stream.eof())`)内?


595

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

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

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


21
scanf(...) != EOF在C中也不起作用,因为scanf返回成功解析和分配的字段数。正确的条件是格式字符串中的字段数scanf(...) < n在哪里n
Ben Voigt 2012年

5
@Ben Voigt,如果达到EOF,它将返回一个负数(通常将EOF定义为负数)
Sebastian

19
@SebastianGodelet:实际上,EOF如果在第一次字段转换之前遇到文件结尾(成功与否),它将返回。如果在字段之间到达文件末尾,它将返回成功转换和存储的字段数。这就可以与EOF错误进行比较。
Ben Voigt 2012年

1
@SebastianGodelet:不,不是。当他说“过去时,没有(简便)的方法可以将适当的输入与不正确的输入区分开”,他会犯错误。实际上,就像.eof()在循环退出后检查一样容易。
Ben Voigt 2012年

2
@Ben是的,在这种情况下(读取一个简单的int)。但是,很容易想到一种情况,其中while(fail)循环会因实际故障和紧急情况而终止。考虑一下,如果每次迭代需要3个整数(例如,您正在读取xyz点之类的东西),但是错误地,流中只有两个整数。
狡猾的2012年

Answers:


544

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

12
值得一提的是if(!(inStream >> data).eof())也不起作用。谬误1:如果最后一条数据后没有空格,则不会输入条件(将不处理最后一个数据)。谬论2:只要没有达到EOF(即使无限循环,一次又一次地处理相同的旧数据),即使读取数据失败,它也会进入条件。
Tronic

4
我认为值得指出的是,这个答案有些误导。当提取intS或std::stringS或类似的EOF位当你提取结束前的一个正确的设置和提取命中结束。您无需再次阅读。从文件读取时未设置它的原因是因为最后还有一个额外\n的东西。我已经在另一个答案中对此进行了介绍。读chars是另一回事,因为它一次只提取一个,并且不会持续到最后。
约瑟夫·曼斯菲尔德

79
主要问题在于,仅因为我们尚未达到EOF,并不意味着下一次读取将成功
约瑟夫·曼斯菲尔德

1
@sftrabbit:完全正确,但不是很有用...即使没有尾随“ \ n”,也有理由希望其他尾随空白与整个文件中的其他空白保持一致(即跳过)。此外,“当您提取之前的一个字符时”的一个细微结果是,当输入完全为空时,while (!eof())将不会对ints或std::strings 起作用,因此即使知道不需要尾随\n处理也是如此。
Tony Delroy

2
@TonyD完全同意。我之所以这样说,是因为我认为大多数人在阅读此内容时都会得到类似的答案,并且会认为如果流包含"Hello"(没有尾随空格或\n)并且std::string提取了a ,它将从中提取字母Ho,停止提取,并且然后将EOF位置1。实际上,它将设置EOF位,因为是EOF阻止了提取。只是希望为人们解决这一问题。
约瑟夫·曼斯菲尔德

103

底线顶部: 在正确处理空白的情况下,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在设置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)无疑是一个更常见,更简洁的习惯用法,并且在简单(每种读取类型的单个数据)场景中可能是首选。


6
因此,在循环之后,没有(简便)的方法可以将正确的输入与不正确的输入区分开。 ”除了在一种情况下,同时设置了eofbit和,在另一种情况下也设置了。您只需要在循环终止后进行一次测试,而不必在每次迭代中进行测试;它只会离开循环一次,因此您只需检查为什么它离开循环一次。 适用于所有空白流。failbitfailbitwhile (in >> data)
Jonathan Wakely

3
您所说的(以及前面提到的一点)是,可以将格式错误的流识别为!eof & fail过去的循环。在某些情况下,不能依靠这一点。请参见上面的注释(goo.gl/9mXYX)。无论如何,我都不建议使用eof-check作为总是更好的选择。我只是说,这一种可能的方式(在某些情况下更合适),而不是“最肯定是错误的”!因为它倾向于在S​​O中在此处声明。
2013年

2
“举个例子,考虑你会如何检查其中的数据与重载运营商结构>>阅读一次多个字段的错误” -一个简单得多的情况下,支持你的观点是stream >> my_int,当流中包含如“ - ”:eofbitfailbit是组。这比operator>>情况更糟,在这种情况下,用户提供的重载至少可以选择清除,eofbit然后再返回以帮助while (s >> x)使用。更广泛地讲,此答案可以使用清理方法-只有最终结果while( !(in>>ws).eof() )通常是可靠的,并且最终将其掩埋。
托尼·德罗伊

74

因为如果程序员不编写while(stream >> n),他们可能会编写以下代码:

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

这里的问题是,您不能不some work on n首先检查流读取是否成功而做,因为如果不成功,some work on n则会产生不希望的结果。

整点是,eofbitbadbit,或failbit设置试图从流中读取后。所以,如果stream >> n失败,那么eofbitbadbitfailbit立即设置,所以其更地道,如果你写while (stream >> n)的,因为返回的对象stream转换到false,如果有一些故障在从流中读取,因此,循环停止。并转换为true读取是否成功并且循环继续。


1
如果失败的流操作不消耗任何输入,则除了提到的对的未定义值进行操作的“不必要的结果”之外n,程序还可能陷入无限循环
mastov '18

10

其他答案已经解释了为什么逻辑错误while (!stream.eof())以及如何解决它。我想专注于一些不同的东西:

为什么显式地使用iostream::eof错误检查eof ?

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

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

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

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

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

    (这与C ++ 11(见上文)相比应该有所不同:现在,您将获得零的“幻像记录”,而不是重复的最后一行。)

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


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


1
这是最准确的答案,尽管从c ++ 11开始,我不相信变量不再初始化(第一个项目符号pt)
csguy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.