在C++中用于缓冲读取的类似于Python生成器的东西是什么?

3

Guido Van Rossum在这篇文章中展示了Python的简单性,并使用这个函数来缓冲读取长度未知的文件:

def intsfromfile(f):
    while True:
        a = array.array('i')
        a.fromstring(f.read(4000))
        if not a:
            break
        for x in a:
            yield x

我需要出于速度原因在C++中做相同的事情!我有许多包含已排序的64位无符号整数列表的文件需要合并。我找到了这个很好的代码用于合并向量。
我不知道如何将未知长度的文件的ifstream表示为可以愉快地迭代直到到达文件末尾的vector。有什么建议吗?我是否正确运用了istreambuf_iterator

文件中的整数是否由换行符、空格等分隔?如果是,那么 istream_iterator 是您的好朋友。 - Dawson
@Toolbox感谢您的回复和解决方案!文件的格式是二进制的,64位小端无符号整数,没有分隔符。所以看起来istreambuf_iterator是正确的选择?您能帮忙将istreambuf_iterator伪装成vector<unsigned long>吗? - Donny
当然!给我一些时间来编写代码(并确保它可以编译)。 - Dawson
对于未来的参考,生成器可以被视为C++中的输入迭代器。语法显然不太好看,因为你必须自己维护状态,但它确实有效 :) - Matthieu M.
抱歉!我完全忘记了发布“istreambuf_iterator”的用法示例。我已经在我的答案中进行了编辑,但我并不完全满意它的表现。 - Dawson
1个回答

7
为了将ifstream(或实际上是任何输入流)伪装成行为类似于迭代器的形式,您需要使用istream_iteratoristreambuf_iterator模板类。前者对于需要考虑格式的文件非常有用。例如,一个充满空格分隔整数的文件可以按以下方式读入vector的迭代器范围构造函数中:
#include <fstream>
#include <vector>
#include <iterator> // needed for istream_iterator

using namespace std;

int main(int argc, char** argv)
{
    ifstream infile("my-file.txt");

    // It isn't customary to declare these as standalone variables,
    // but see below for why it's necessary when working with
    // initializing containers.
    istream_iterator<int> infile_begin(infile);
    istream_iterator<int> infile_end;

    vector<int> my_ints(infile_begin, infile_end);

    // You can also do stuff with the istream_iterator objects directly:
    // Careful! If you run this program as is, this won't work because we
    // used up the input stream already with the vector.

    int total = 0;
    while (infile_begin != infile_end) {
        total += *infile_begin;
        ++infile_begin;
    }

    return 0;
}

istreambuf_iterator用于逐个字符读取文件,忽略输入的格式。也就是说,它会返回所有字符,包括空格、换行符等等。根据您的应用程序,这可能更为合适。

注:Scott Meyers在《Effective STL》中解释了为什么需要单独声明istream_iterator变量。通常,您会像这样做:

ifstream infile("my-file.txt");
vector<int> my_ints(istream_iterator<int>(infile), istream_iterator<int>());

然而,C++实际上以一种非常奇怪的方式解析第二行。它将其视为一个名为my_ints的函数声明,该函数接受两个参数并返回一个vector<int>。第一个参数是类型为istream_iterator<int>且命名为infile的参数(括号被忽略)。第二个参数是一个没有名称的函数指针,它不带任何参数(因为有括号),并返回一个类型为istream_iterator<int>的对象。

这很酷,但如果你没有注意到它,也会让人相当恼火。


编辑

以下是使用istreambuf_iterator读取端对端布置的64位数字文件的示例:

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

using namespace std;

int main(int argc, char** argv)
{
    ifstream input("my-file.txt");
    istreambuf_iterator<char> input_begin(input);
    istreambuf_iterator<char> input_end;

    // Fill a char vector with input file's contents:
    vector<char> char_input(input_begin, input_end);
    input.close();

    // Convert it to an array of unsigned long with a cast:
    unsigned long* converted = reinterpret_cast<unsigned long*>(&char_input[0]);
    size_t num_long_elements = char_input.size() * sizeof(char) / sizeof(unsigned long);

    // Put that information into a vector:
    vector<unsigned long> long_input(converted, converted + num_long_elements);

    return 0;
}

现在,我个人不太喜欢这种解决方案(使用reinterpret_cast,暴露char_input的数组),但我对istreambuf_iterator不够熟悉,无法舒适地使用一个模板化为64位字符的迭代器,这将使这个问题变得更容易。


1
我今天的投票已经用完了,但如果还有剩余的话,我会点赞这篇文章的。 :-) 这是一篇很好的解释。 - templatetypedef
@templatetypedef 不用担心,我不会的 :P - wheaties
1
如果我没记错的话,还有一些方法可以添加额外的括号来解决解析歧义,但这样做很丑陋,并且在明确性方面失败。 - Karl Knechtel
因为我喜欢给事物命名 --> 你问题的最后一部分涉及到所谓的“最令人烦恼的解析”。顺便说一句,回答得很好。 - Matthieu M.

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