c++ 为什么循环条件中的iostream::eof(例如'while(!stream.eof())')被认为是错误的?

cbjzeqam  于 2023-02-01  发布在  iOS
关注(0)|答案(5)|浏览(251)

我刚刚在这个答案中发现了一个评论,说在循环条件中使用iostream::eof“几乎肯定是错误的”。我一般使用类似while(cin>>n)的东西-我猜它会隐式检查EOF。
为什么显式使用while (!cin.eof())检查eof是错误的?
它与在C语言中使用scanf("...",...)!=EOF(我经常使用它,没有任何问题)有何不同?

a2mppw5e

a2mppw5e1#

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

iq3niunx2#

**底线顶端:**通过正确处理白色,下面是eof的使用方法(甚至在错误检查方面比fail()更可靠):

while( !(in>>std::ws).eof() ) {
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   // Now use data
}

(* 感谢托尼·D提出的突出显示答案的建议。请看他下面的评论,其中有一个例子说明为什么这样做更有效。
反对使用eof()的主要论点似乎忽略了白色的一个重要的微妙之处,我的主张是,显式检查eof()不仅不是“
总是错误的 *"--这似乎是这个问题和类似的堆栈溢出问题中压倒一切的观点--而且通过正确处理空格,它提供了一个更干净、更可靠的错误处理。并且是“总是正确的”解决方案(尽管不一定是最简洁的)。
以下是建议的“适当”终止和读取顺序的总结:

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 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)无疑是一个更常见、更简洁的习惯用法,在简单(每个读取类型一个数据)的场景中可能是首选。

o7jaxewo

o7jaxewo3#

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

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

这里的问题是,如果不首先检查流读取是否成功,就不能执行some work on n,因为如果不成功,some work on n将产生不希望的结果。
关键在于,eofbitbadbitfailbit是在**尝试从流中读取之后设置的。**因此,如果stream >> n失败,则立即设置eofbitbadbitfailbit,因此,如果您将while (stream >> n)因为如果从流中阅读时出现故障,则返回的对象stream转换为false,因此循环停止;如果读取成功,则返回的对象stream转换为true,循环继续。

1l5u6lss

1l5u6lss4#

其他答案已经解释了为什么while (!stream.eof())中的逻辑是错误的以及如何修复它。
为什么显式使用iostream::eof检查eof是错误的?
一般来说,检查eofonly 是错误的,因为流提取(>>)可能会失败,而不会到达文件末尾。如果您有int n; cin >> n;,而流包含hello,则h不是有效数字,因此提取将失败,而不会到达输入末尾。
此问题与尝试读取流之前检查流状态的一般逻辑错误(这意味着对于N个输入项,循环将运行N+1次)结合在一起,导致以下症状:

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

(If您的标准库符合C++11,现在情况有点不同:失败的>>现在会将数值变量设置为0,而不是让它们处于未初始化状态(char除外)。

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

(This C++11之后应该表现得有点不同(见上文):现在你得到了一个零的“幻影记录”,而不是重复的最后一行。)

  • 如果流中包含格式错误的数据,但您只检查.eof,则会以无限循环结束。>>将无法从流中提取任何数据,因此循环会原地旋转,而不会到达结尾。

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

xt0899hw

xt0899hw5#

需要记住的重要一点是,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,我们不能读取为整数。

相关问题