首页 文章

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

提问于
浏览
510

我刚刚在this回答中发现一条评论说在循环条件下使用 iostream::eof 是"almost certainly wrong" . 我通常使用类似 while(cin>>n) 的东西 - 我猜这是隐式检查EOF,为什么使用 iostream::eof 错误地检查eof?

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

4 回答

  • 92

    Bottom-line top: 通过正确处理空白区域,以下是如何使用 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 在设置 eofbitnot the 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
    }
    

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

  • 62
    1 while (!read.fail()) {
    2     cout << ch;
    3     read.get(ch);
    4 }
    

    如果您使用3行中的第2行和2中的第3行,您将获得 ch 两次打印 . 所以cout在阅读之前 .

  • 462

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

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

    问题在于,如果没有先检查流读取是否成功,则无法执行 some work on n ,因为如果不成功,则 some work on n 将产生不需要的结果 .

    整点是, eofbitbadbitfailbit 设置为 after an attempt is made to read from the stream. 所以如果 stream >> n 失败,那么 eofbitbadbitfailbit 会立即设置,所以如果你写 while (stream >> n) 会更加惯用,因为返回的对象 stream 转换为 false 如果有从流中读取有些失败,因此循环停止 . 如果读取成功并且循环继续,它将转换为 true .

  • -3

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

    not 一样

    if(!inStream.eof())
        inFile >> data
    

相关问题