我刚刚在这个答案中发现了一条评论,说在循环条件中使用iostream::eof“几乎肯定是错误的”。我通常使用像while(cin>>n)这样的东西——我猜它隐式地检查EOF。
为什么显式使用while (!cin.eof())检查eof是错误的?
它与使用scanf("…",…)有何不同!=EOF在C(我经常使用没有问题)?
我刚刚在这个答案中发现了一条评论,说在循环条件中使用iostream::eof“几乎肯定是错误的”。我通常使用像while(cin>>n)这样的东西——我猜它隐式地检查EOF。
为什么显式使用while (!cin.eof())检查eof是错误的?
它与使用scanf("…",…)有何不同!=EOF在C(我经常使用没有问题)?
当前回答
因为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
其他回答
因为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
因为如果程序员不写while(stream >> n),他们可能会这样写:
while(!stream.eof())
{
stream >> n;
//some work on n;
}
这里的问题是,如果不首先检查流读取是否成功,您就不能在n上做一些工作,因为如果它不成功,您在n上的一些工作将产生不希望看到的结果。
重点是,eofbit、badbit或failbit是在尝试从流中读取之后设置的。因此,如果流>> n失败,则立即设置eofbit, badbit或failbit,因此,如果您写入while(流>> n),则更符合习惯,因为如果从流中读取失败,则返回的对象流将转换为false,从而停止循环。如果读取成功,循环继续,则转换为true。
底线顶部:通过正确处理空白,下面是如何使用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()不仅不是“总是错误的”——这似乎是这个和类似Stack Overflow问题中最重要的观点——,而且通过适当处理空白,它提供了一个更干净和更可靠的错误处理,并且是始终正确的解决方案(尽管不一定是最简洁的)。
总结一下建议的“适当的”终止和阅读顺序如下:
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> < eof >
While(在>>数据中)以所有三个输入的设置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 white space 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)无疑是一种更常见、更简洁的习惯用法,可能更适合于简单的(每种读取类型只有一个数据)场景。
其他答案解释了为什么while (!stream.eof())中的逻辑是错误的以及如何修复它。我想关注一些不同的东西:
为什么显式使用iostream::eof检查eof错误?
一般来说,只检查eof是错误的,因为流提取(>>)可能在没有到达文件末尾的情况下失败。如果你有int n;Cin >> n;如果流中包含hello,则h不是有效数字,因此在未到达输入末尾时提取将失败。
此问题与试图从流中读取之前检查流状态的一般逻辑错误(这意味着对于N个输入项,循环将运行N+1次)结合在一起,导致以下症状:
If the stream is empty, the loop will run once. >> will fail (there is no input to be read) and all variables that were supposed to be set (by stream >> x) are actually uninitialized. This leads to garbage data being processed, which can manifest as nonsensical results (often huge numbers). (If your standard library conforms to C++11, things are a bit different now: A failed >> now sets numeric variables to 0 instead of leaving them uninitialized (except for chars).) If the stream is not empty, the loop will run again after the last valid input. Since in the last iteration all >> operations fail, variables are likely to keep their value from the previous iteration. This can manifest as "the last line is printed twice" or "the last input record is processed twice". (This should manifest a bit differently since C++11 (see above): Now you get a "phantom record" of zeroes instead of a repeated last line.) If the stream contains malformed data but you only check for .eof, you end up with an infinite loop. >> will fail to extract any data from the stream, so the loop spins in place without ever reaching the end.
概括一下:解决方案是测试>>操作本身的成功,而不是使用单独的.eof()方法:while (stream >> n >> m){…},就像在C中测试scanf调用本身是否成功一样:while (scanf("%d%d", &n, &m) == 2){…}。
需要记住的重要事情是,inFile.eof()直到尝试读取失败后才会变成True,因为您已经到达了文件的末尾。在这个例子中,你会得到一个错误。
while (!inFile.eof()){
inFile >> x;
process(x);
}
使这个循环正确的方法是将读取和检查合并到一个操作中,就像这样
while (inFile >> x)
process(x);
按照惯例,操作符>>返回我们从中读取的流,当流失败(比如到达文件末尾)时,对流进行布尔测试返回False。
所以这给出了正确的顺序:
读 测试读取是否成功 当且仅当测试成功时,处理我们读到的内容
如果您碰巧遇到一些其他问题,阻止您正确地从文件中读取,那么您将无法访问eof()。例如,让我们看看这样的东西
int x;
while (!inFile.eof()) {
inFile >> x;
process(x);
}
让我们通过一个示例跟踪上述代码的工作过程
Assume the contents of the file are '1', '2', '3', 'a', 'b'. The loop will read the 1, 2, and 3 correctly. Then it’ll get to a. When it tries to extract a as an int, it’ll fail. The stream is now in a failed state, until or unless we clear the stream, all attempts at reading from it will fail. But, when we test for eof(), it’ll return False, because we’re not at the end of the file, because there’s still a waiting to be read. The loop will keep trying to read from the file, and fail every time, so it never reaches the end of the file. So, the loop above will run forever.
但是,如果我们使用这样的循环,我们将得到所需的输出。
while (inFile >> x)
process(x);
在这种情况下,流不仅在文件结束时转换为False,而且在转换失败时也会转换为False,例如我们不能将a读取为整数。