644

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

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

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

4

6 回答 6

581

因为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
于 2011-04-09T12:58:49.007 回答
107

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

于 2012-11-23T23:30:45.903 回答
78

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

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

这里的问题是,您必须some work on n先检查流读取是否成功,因为如果不成功,您some work on n将产生不希望的结果。

重点是,eofbit, badbit, 或failbit在尝试从流中读取之后设置的。因此,如果stream >> n失败,则eofbit,badbitfailbit立即设置,因此如果您 write 则更惯用,因为如果从流中读取失败并因此循环停止while (stream >> n),则返回的对象stream将转换为。如果读取成功并且循环继续false,它会转换为。true

于 2011-04-09T12:59:27.743 回答
12

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

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

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

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

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

    (如果您的标准库符合 C++11,现在情况有些不同:A failed>>现在将数值变量设置为,0而不是让它们未初始化(chars 除外)。)

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

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

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


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

于 2019-05-04T09:52:18.203 回答
2

要记住的重要一点是,直到尝试读取失败inFile.eof()才会变为,因为您已经到达文件的末尾。所以,在这个例子中,你会得到一个错误。True

while (!inFile.eof()){
    inFile >> x;
        process(x);
}

使这个循环正确的方法是将读取和检查结合到一个操作中,就像这样

while (inFile >> x) 
    process(x); 

按照惯例,operator>>返回我们从中读取的流,False当流失败(例如到达文件末尾)时,返回对流的布尔测试。

所以这给了我们正确的顺序:

  • 测试读取是否成功
  • 当且仅当测试成功时,处理我们读到的内容

如果您碰巧遇到一些其他问题阻止您正确读取文件,您将无法访问eof()。例如,让我们看一下这样的东西

int x; 
while (!inFile.eof()) { 
    inFile >> x; 
    process(x);
} 
    

让我们通过一个例子来追溯上述代码的工作原理

  • 假设文件内容为'1', '2', '3', 'a', 'b'.
  • 循环将正确读取 1、2 和 3。
  • 然后它会到达a
  • 当它试图提取a为 int 时,它会失败。
  • 该流现在处于失败状态,直到或除非我们clear使用该流,否则所有读取它的尝试都将失败。
  • 但是,当我们测试 eof() 时,它会返回False,因为我们不在文件末尾,因为还有a等待读取。
  • 循环将继续尝试从文件中读取,并且每次都失败,因此它永远不会到达文件末尾。
  • 因此,上面的循环将永远运行。

但是,如果我们使用这样的循环,我们将获得所需的输出。

while (inFile >> x)
    process(x);

在这种情况下,流False不仅会在文件结束的情况下转换为,而且还会在转换失败的情况下转换,例如a我们无法读取为整数的情况。

于 2022-02-19T17:21:41.273 回答
0

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

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

示例代码

#include<iostream>
#include<fstream>
using namespace std;
int main() {
   ifstream myFile("myfile.txt");
   string x;
   while(!myFile.eof()) {
      myFile >> x;
     // Need to check again if x is valid or eof
     if(x) {
        // Do something with x
     }
   }
}

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

示例代码

#include<iostream>
#include<fstream>
using namespace std;
int main() {
   ifstream myFile("myfile.txt");
   string x;
   while(myFile >> x) {
      // Do something with x
      // No checks needed!
   }
}
于 2021-04-25T05:36:16.460 回答