从文本文件读取直到EOF,重复最后一行。

132

以下的C++代码使用一个ifstream对象从文本文件中读取整数(每行一个)直到遇到EOF。为什么它会在最后一行读取两次整数?如何修复这个问题?

代码:

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
    ifstream iFile("input.txt");    // input.txt has integers, one per line

    while (!iFile.eof())
    {
        int x;
        iFile >> x;
        cerr << x << endl;
    }

    return 0;
}

input.txt:

->

input.txt:

10  
20  
30

输出:

10  
20  
30  
30

注意:为了使代码更加简洁,我省略了所有的错误检查代码。以上行为在Windows(Visual C ++),cygwin(gcc)和Linux(gcc)上均可见。

7个回答

135

紧跟事件链。

  • 取10
  • 取20
  • 取30
  • 取EOF

看看倒数第二次迭代。你取了30,然后继续检查EOF。因为EOF标记还没有被读取(从“二进制”角度来看,它的概念位置就在30行之后),所以你继续下一次迭代。x仍然是上一次迭代的30。现在你从流中读取并得到EOF。x保持30,ios::eofbit被触发。你输出x到stderr(与上一次迭代一样为30)。接下来你检查循环条件是否为EOF,这一次你已经退出了循环。

试试这个:

while (true) {
    int x;
    iFile >> x;
    if( iFile.eof() ) break;
    cerr << x << endl;
}

顺便说一下,你的代码中还有一个错误。你试过在空文件上运行它吗?你得到的结果是因为相同的原因。


49
使用'while (iFile >> x)'。这将读取整数并返回流对象。当将流对象作为布尔值使用时,它会检查流对象的有效性。有效意味着eof()和bad()都为false。请参见https://dev59.com/QHVD5IYBdhLWcg3wTZ1m。 - Martin York
3
与上一个评论的精神相同:与其写while(true),似乎更好的写法是while(file.good()) - PHF
2
如果您不确定如何读取文件直到错误或文件结束,请阅读本文(非常好的解释)https://gehrcke.de/2011/06/reading-files-in-c-using-ifstream-dealing-correctly-with-badbit-failbit-eofbit-and-perror/。 - sfelber

43

我喜欢这个例子,目前为止它在while循环块中没有加入检查:

ifstream iFile("input.txt");        // input.txt has integers, one per line
int x;

while (iFile >> x) 
{
    cerr << x << endl;
}

我不确定它的安全性如何...


如果0是一个有效的值,例如x==0,那该怎么办? - harryngh
6
如果我理解正确,iFile >> x 返回的是流本身,而不是 x。然后,该流会被隐式转换为 bool 类型,用于检测是否到达文件末尾(EOF)。 - wchargin
非常酷的模式,因为它也适用于从流中读取多个东西,这些东西应该组成一个实体。例如:while (file >> x_coord >> y_coord) { } - Martijn Courteaux

15

这个问题有另外一种解决方法:

#include <iterator>
#include <algorithm>

// ...

    copy(istream_iterator<int>(iFile), istream_iterator<int>(),
         ostream_iterator<int>(cerr, "\n"));

7

EOF模式需要一个主读取来“引导”EOF检查过程。考虑到空文件在第一次读取之前不会有EOF设置,主读取将在这种情况下捕获EOF并完全跳过循环。

在这里需要记住的是,在第一次尝试读取超出文件可用数据时才会得到EOF。读取完全相同数量的数据不会标志EOF。

我应该指出,如果文件为空,则给定代码将打印,因为EOF将防止在进入循环时将值设置为x。

  • 0

因此,添加一个主读取并将循环的读取移动到最后:

int x;

iFile >> x; // prime read here
while (!iFile.eof()) {
    cerr << x << endl;
    iFile >> x;
}

7

在不做太多修改的情况下,它可以变成:

while (!iFile.eof())
{  
    int x;
    iFile >> x;
    if (!iFile.eof()) break;
    cerr << x << endl;
}

但总的来说,我更喜欢前面提到的另外两种解决方案。


1
我想这应该是 if (iFile.eof()) break;,没有 ! - Bouke Versteegh

3
在最后一行末尾,您有一个新的换行符号,它不会被 >> 运算符读取,并且也不是文件的结尾。请进行实验并删除新的换行符(文件中的最后一个字符)- 您将不会得到重复。为了拥有灵活的代码并避免不必要的影响,请应用其他用户提供的任何解决方案。

2
int x;
ifile >> x

while (!iFile.eof())
{  
    cerr << x << endl;        
    iFile >> x;      
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接