使用C++中的ifstream逐行读取文件

783

文件file.txt的内容为:

5 3
6 4
7 1
10 5
11 6
12 3
12 4

其中5 3是一组坐标对。 如何在C++中逐行处理此数据?

我能够获取第一行,但如何获取文件的下一行?

ifstream myfile;
myfile.open ("file.txt");

相关问题: 如何在C++中将整个文件读入std::string? - Stephen Ostermiller
8个回答

1141

首先,创建一个 ifstream

#include <fstream>
std::ifstream infile("thefile.txt");

两种标准方法是:

  1. 假设每一行都由两个数字组成,并逐个令牌进行读取:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
    
  2. 使用字符串流进行基于行的解析:

    #include <sstream>
    #include <string>
    
    std::string line;
    while (std::getline(infile, line))
    {
        std::istringstream iss(line);
        int a, b;
        if (!(iss >> a >> b)) { break; } // error
    
        // process pair (a,b)
    }
    

不应该混合使用(1)和(2),因为基于令牌的解析不会将换行符一起处理,所以如果你在基于令牌的提取结束之后使用getline(),可能会得到错误的空行。


1
@EdwardKarak:我不明白“逗号作为标记”是什么意思。逗号并不代表整数。 - Kerrek SB
8
OP使用空格来分隔两个整数。我想知道如果OP使用逗号作为分隔符,那么while(infile >> a >> b)是否会起作用,因为这是我自己程序中的情况。 - lost_in_the_source
36
@EdwardKarak:啊,所以当你说“token”时,你指的是“分隔符”。对了。用逗号,你会这样说:int a, b; char c; while ((infile >> a >> c >> b) && (c == ',')) - Kerrek SB
12
@KerrekSB: 哦,我错了。我不知道它可以做到那样。我可能需要重写一些自己的代码。 - Mark H
4
关于while(getline(f, line)) { }结构的解释和错误处理,请参考这篇(我的)文章:http://gehrcke.de/2011/06/reading-files-in-c-using-ifstream-dealing-correctly-with-badbit-failbit-eofbit-and-perror/ (我认为我没有必要因在这里发布而感到内疚,它甚至略早于这个答案)。 - Dr. Jan-Philip Gehrcke
显示剩余17条评论

221

使用ifstream从文件读取数据:

std::ifstream input( "filename.ext" );

如果你确实需要逐行读取,那么请这样做:

for( std::string line; getline( input, line ); )
{
    ...for each line in input...
}

但是你可能只需要提取坐标对:

int x, y;
input >> x >> y;

更新:

在你的代码中使用了ofstream myfile;,然而ofstream中的o代表着output(输出)。如果你想要从文件中读取数据(输入),请使用ifstream。如果你想要读写两个操作同时进行,请使用fstream


10
你的解决方案有所改进:与 Kerrek SB 的第二种解决方案相比,在文件读取后,你的 line 变量不再可见。 Kerrek SB 的第二种解决方案也是一个好且简单的解决方案。 - DanielTuzes
10
getline 函数在 string 中,详情请参见 链接,因此不要忘记添加 #include <string> - mxmlnkn

156

在C++中逐行读取文件有几种不同的方法。

[快速]使用std::getline()循环

最简单的方法是打开一个std::ifstream并使用std::getline()循环调用。 代码干净易懂。

#include <fstream>

std::ifstream file(FILENAME);
if (file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        // using printf() in all tests for consistency
        printf("%s", line.c_str());
    }
    file.close();
}

[快速] 使用Boost的file_description_source

另一种可能性是使用Boost库,但代码会变得更加冗长。性能与上述代码(使用std::getline()循环)相当。

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>

namespace io = boost::iostreams;

void readLineByLineBoost() {
    int fdr = open(FILENAME, O_RDONLY);
    if (fdr >= 0) {
        io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
        io::stream <io::file_descriptor_source> in(fdDevice);
        if (fdDevice.is_open()) {
            std::string line;
            while (std::getline(in, line)) {
                // using printf() in all tests for consistency
                printf("%s", line.c_str());
            }
            fdDevice.close();
        }
    }
}

[最快] 使用C代码

如果软件性能至关重要,您可以考虑使用C语言。这段代码可能比上面的C++版本快4-5倍,见下面的基准测试。

FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
    exit(EXIT_FAILURE);

char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
    // using printf() in all tests for consistency
    printf("%s", line);
}
fclose(fp);
if (line)
    free(line);

基准测试--哪个更快?

我对上面的代码进行了一些性能基准测试,结果很有趣。我使用包含100,000行、1,000,000行和10,000,000行文本的ASCII文件对代码进行了测试。每行文本平均包含10个单词。程序使用-O3优化编译,并将输出转发到/dev/null以从测量中删除日志时间变量。最后,每段代码都使用printf()函数记录每行以保持一致性。

结果显示了每段代码读取文件所需的时间(以毫秒为单位)。

C++两种方法之间的性能差异微不足道,在实践中不应该有任何区别。C代码的性能是使基准测试印象深刻并且在速度方面可以改变游戏规则的因素。

                             10K lines     100K lines     1000K lines
Loop with std::getline()         105ms          894ms          9773ms
Boost code                       106ms          968ms          9561ms
C code                            23ms          243ms          2397ms

输入图像描述


9
如果在C++中删除与C语言在控制台输出方面的同步,会发生什么?这可能会测量出std::cout相对于printf的默认行为的已知劣势。 - user4581301
6
谢谢您提出这个问题。我重新运行了测试,性能仍然相同。我编辑了代码,使用printf()函数以保持一致性。我也尝试在所有情况下使用std::cout,但是没有任何区别。正如我在文本中所描述的那样,程序的输出被发送到/dev/null,因此打印行的时间不会被计算。 - HugoTeixeira
8
好的,谢谢。不知道这个减速是出在哪里。 - user4581301
10
嗨 @HugoTeixeira,我知道这是一个旧帖子,我尝试复制你的结果,但在 c 和 c++ 之间没有看到任何显着的差异。https://github.com/simonsso/readfile_benchmarks - Simson
5
注意,在 C 语言中使用 getline 函数是 GNU 扩展(现已添加到 POSIX 标准),它不是标准的 C 函数。 - Dan M.
显示剩余11条评论

18

既然你的坐标是成对出现的,为什么不为它们编写一个结构体呢?

struct CoordinatePair
{
    int x;
    int y;
};

然后您可以为istream编写重载的提取运算符:

std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
    is >> coordinates.x >> coordinates.y;

    return is;
}

然后你可以像这样直接将一个坐标文件读入到向量中:

#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    char filename[] = "coordinates.txt";
    std::vector<CoordinatePair> v;
    std::ifstream ifs(filename);
    if (ifs) {
        std::copy(std::istream_iterator<CoordinatePair>(ifs), 
                std::istream_iterator<CoordinatePair>(),
                std::back_inserter(v));
    }
    else {
        std::cerr << "Couldn't open " << filename << " for reading\n";
    }
    // Now you can work with the contents of v
}

1
当在operator>>中无法从流中读取两个 int 标记时会发生什么?当使用回溯分析器(即当 operator>> 失败时,将流回滚到以前的位置并返回 false 或类似内容)时,如何使其正常工作? - fferri
如果无法读取两个 int 标记,则 is 流将评估为 false,并且读取循环将在该点终止。您可以通过检查各个读取的返回值来在 operator>> 中检测此情况。如果要回滚流,则应调用 is.clear() - user325117
operator>> 中,更正确的说法是 is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;,因为否则你假设输入流处于跳过空格模式。 - Darko Veberic

9

扩展接受的答案,如果输入是:

1,NYC
2,ABQ
...

你仍然可以使用相同的逻辑来应用它,代码如下:
#include <fstream>

std::ifstream infile("thefile.txt");
if (infile.is_open()) {
    int number;
    std::string str;
    char c;
    while (infile >> number >> c >> str && c == ',')
        std::cout << number << " " << str << "\n";
}
infile.close();

4

虽然没有必要手动关闭文件,但如果文件变量的作用域更大,手动关闭文件是一个好主意:

    ifstream infile(szFilePath);

    for (string line = ""; getline(infile, line); )
    {
        //do something with the line
    }

    if(infile.is_open())
        infile.close();

不确定这个回答应该被踩。OP要求一种获取每行的方法,这个回答做到了,并且给出了一个很好的提示,确保文件关闭。对于一个简单的程序来说,可能不需要,但至少是一个非常好的习惯。它可以通过添加一些代码来处理它提取的各个行,但总体上是OP问题的最简单的答案。 - Xandor
ifstream infile(szFilePath); 超出作用域时,文件不是已经关闭了吗? - KcFnMi

4
这个答案适用于Visual Studio 2017,如果您想要从相对于编译后的控制台应用程序的文本文件中读取内容,请执行以下步骤:
首先将您的文本文件(在本例中为test.txt)放置到解决方案文件夹中。编译后,将文本文件和applicationName.exe文件放置在同一个文件夹中。
路径为C:\Users\"用户名"\source\repos\"解决方案名称"\"解决方案名称"。
#include <iostream>
#include <fstream>

using namespace std;
int main()
{
    ifstream inFile;
    // open the file stream
    inFile.open(".\\test.txt");
    // check if opening a file failed
    if (inFile.fail()) {
        cerr << "Error opeing a file" << endl;
        inFile.close();
        exit(1);
    }
    string line;
    while (getline(inFile, line))
    {
        cout << line << endl;
    }
    // close the file stream
    inFile.close();
}

1
这是一种将数据加载到C++程序中的通用解决方案,使用了readline函数。这可以进行CSV文件的修改,但这里的分隔符是一个空格。
int n = 5, p = 2;

int X[n][p];

ifstream myfile;

myfile.open("data.txt");

string line;
string temp = "";
int a = 0; // row index 

while (getline(myfile, line)) { //while there is a line
     int b = 0; // column index
     for (int i = 0; i < line.size(); i++) { // for each character in rowstring
          if (!isblank(line[i])) { // if it is not blank, do this
              string d(1, line[i]); // convert character to string
              temp.append(d); // append the two strings
        } else {
              X[a][b] = stod(temp);  // convert string to double
              temp = ""; // reset the capture
              b++; // increment b cause we have a new number
        }
    }

  X[a][b] = stod(temp);
  temp = "";
  a++; // onto next row
}

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