为什么循环条件中的iostream :: eof被认为是错误的?

MAK*_*MAK 564 c++ iostream c++-faq

我刚刚在这个答案中发现了一条评论说iostream::eof在循环条件下使用"几乎肯定是错误的".我通常使用类似的东西while(cin>>n)- 我猜是隐式检查EOF,为什么检查eof显式使用while (!cin.eof())错误?

它与scanf("...",...)!=EOF在C中使用有何不同(我经常使用没有问题)?

Xeo*_*Xeo 518

因为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
}
Run Code Online (Sandbox Code Playgroud)

反对这个:

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)
}
Run Code Online (Sandbox Code Playgroud)

关于你的第二个问题:因为

if(scanf("...",...)!=EOF)
Run Code Online (Sandbox Code Playgroud)

是相同的

if(!(inStream >> data).eof())
Run Code Online (Sandbox Code Playgroud)

一样的

if(!inStream.eof())
    inFile >> data
Run Code Online (Sandbox Code Playgroud)

  • 主要的问题是**只是因为我们没有达到EOF,并不意味着下一次读取会成功**. (74认同)
  • 值得一提的是,if(!(inStream >> data).eof())也没有做任何有用的事情.谬误1:如果最后一条数据之后没有空格,则不会进入条件(最后一个数据将不被处理).谬误2:即使读取数据失败也会进入条件,只要未达到EOF(无限循环,一遍又一遍地处理相同的旧数据). (12认同)
  • 我认为值得指出的是,这个答案有点误导.当提取`int`s或`std :: string`s或类似的东西时,当你在结束之前提取一个并且提取到达结束时,EOF位*被设置.你不需要再读一遍.从文件读取时没有设置它的原因是因为最后有一个额外的`\n`.我在[另一个答案](http://stackoverflow.com/a/14615673/150634)中介绍了这一点.阅读`char`s是另一回事,因为它一次只提取一个并且不会继续结束. (3认同)
  • @TonyD完全同意.我之所以这么说是因为我认为大多数人在阅读本文和类似的答案时会认为如果流包含"Hello"`(没有尾随空格或`\n`)和`std :: string`提取后,它会将字母从"H"提取到"o",停止提取,然后*不*设置EOF位.事实上,它会设置EOF位,因为它是EOF停止提取.只是希望为人们清楚. (2认同)
  • `// do stuff with (now uninitialized) data` 从 C++11 开始,这不再正确,请参阅 http://stackoverflow.com/a/13379073/3002139 (2认同)
  • @BaummitAugen 例如,如果流中没有更多字符,它仍然未初始化(请参阅我对链接答案的评论)。仅当成功提取字符并且无法按照预期格式解析时才会设置为零 (2认同)

sly*_*sly 98

底线: 正确处理空白区域,以下是如何eof使用(甚至比fail()错误检查更可靠):

while( !(in>>std::ws).eof() ) {  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    
Run Code Online (Sandbox Code Playgroud)

(感谢Tony D提出强调答案的建议.请参阅下面的评论,了解为什么这样做更为强大.)


反对使用的主要论点eof()似乎缺少关于白色空间作用的重要微妙之处.我的主张是,eof()明确地检查不仅不是" 总是错误的 " - 这似乎是在这个和类似的SO线程中的最重要的意见 - 但是通过适当处理白色空间,它提供了更清洁和更可靠的错误处理,并且始终是正确的解决方案(虽然,不一定是最简洁的).

总结一下建议的"正确"终止和阅读顺序如下:

int data;
while(in >> data) {  /* ... */ }

// which is equivalent to 
while( !(in >> data).fail() )  {  /* ... */ }
Run Code Online (Sandbox Code Playgroud)

由于超过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
}    
Run Code Online (Sandbox Code Playgroud)

在这里,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
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

简介:正确构造while(!eof)不仅可能而且没有错误,但允许数据在范围内进行本地化,并且可以像往常一样更清晰地将错误检查与业务分开.话虽如此,while(!fail)无疑是一种更常见和简洁的习惯用语,并且在简单(每种读取类型的单一数据)场景中可能是首选.

  • "_在循环之后,没有(简单的)方法来区分正确的输入和不正确的输入."除了在一种情况下设置了'eofbit`和`failbit`,在另一种情况下只设置`failbit`.您只需要在循环终止后测试_once_,而不是在每次迭代时测试; 它只会离开循环一次,所以你只需要检查_why_它离开循环一次.`while(in >> data)`适用于所有空白流. (6认同)
  • 你所说的(以及之前的观点)是一个错误的格式化流可以被识别为`!eof&fail`过去的循环.有些情况下,人们不能依赖于此.见上述评论(http://goo.gl/9mXYX).无论如何,我并不是建议将'eof`-check视为*总是更好的*替代方案.我只是说,这是*一种可能的(在某些情况下更合适)这样做的方式,而不是"肯定是错误的!" 因为它倾向于在S​​O这里声称. (3认同)
  • *“例如,考虑如何检查数据是带有重载运算符的结构的错误&gt;&gt; 一次读取多个字段”* - 支持您观点的更简单的情况是`stream &gt;&gt; my_int`,其中流包含例如“-”:设置了`eofbit` 和`failbit`。这比 `operator&gt;&gt;` 场景更糟糕,在这种情况下,用户提供的重载至少可以选择在返回之前清除 `eofbit` 以帮助支持 `while (s &gt;&gt; x)` 的使用。更一般地,这个答案可以使用清理 - 只有最后的`while( !(in&gt;&gt;ws).eof() )` 通常是健壮的,并且它被埋在最后。 (2认同)

Naw*_*waz 71

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

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是,你不能不some work on n先检查流读取是否成功,因为如果它不成功,你some work on n会产生不希望的结果.

整点是,eofbit,badbit,或failbit设置试图从流中读取后.因此,如果stream >> n失败,那么eofbit,badbit或者failbit立即设置,所以如果你写的话它更加惯用while (stream >> n),因为如果从流中读取有一些失败并且因此循环停止,则返回的对象stream转换为false.true如果读取成功并且循环继续,它将转换为.

  • 除了对 n 的未定义值进行工作时提到的“不希望的结果”之外,如果失败的流操作不消耗任何输入,程序也可能陷入**无限循环**。 (2认同)

mel*_*ene 5

其他答案已经解释了为什么逻辑错误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) { ... }


Dee*_*net 5

要记住的重要一点是,直到尝试读取失败inFile.eof(), \xe2\x80\x99 才会变为,因为\xe2\x80\x99 已到达文件末尾。因此,在此示例中,您\xe2\x80\x99 将收到错误。True

\n
while (!inFile.eof()){\n    inFile >> x;\n        process(x);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
while (inFile >> x)\xc2\xa0\n    process(x);\xc2\xa0\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

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

如果您碰巧遇到其他一些问题,导致您无法正确读取文件,您将无法达到eof()这样的目的。例如,让\xe2\x80\x99s 看看这样的东西

\n
int x;\xc2\xa0\nwhile (!inFile.eof()) {\xc2\xa0\n    inFile >> x;\xc2\xa0\n    process(x);\n}\xc2\xa0\n
Run Code Online (Sandbox Code Playgroud)\n

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

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

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

\n
while (inFile >> x)\n    process(x);\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,流False不仅会在文件结尾的情况下转换,而且在转换失败的情况下也会转换,例如a我们可以将 \xe2\x80\x99t 作为整数读取。

\n