提问者:小点点

在文件上实现迭代器


我想用加载的文件创建一个类,并为给定的类实现一个迭代器,以便用范围迭代器对它进行迭代

这是我的课

class Csv
{

  public:
    explicit Csv(std::string filepath);
    ~Csv();
    void load_new_file(std::string filepath) {}

  private:
    std::ifstream file;
};

下面是我想实现的一个行为

Csv csv("path");
for (auto line : csv ){
    std::cout<<line<<std::endl;
}

我怎么能这么做呢?


共3个答案

匿名用户

使用std::getLine的最小示例:

class End_iterator {};

class Iterator {
public:
    Iterator(std::ifstream& file) : file_{file} {
        next();
    }

    const std::string& operator*() const {
        return str_;
    }

    Iterator& operator++() {
        next();
        return *this;
    }

    bool operator!=(End_iterator) const {
        return !!file_;
    }

private:
    void next() {
        std::getline(file_, str_);
    }

    std::ifstream& file_;
    std::string str_;
};

class Csv {
public:
    explicit Csv(std::string filepath) {
        file_.open(filepath);
    }

    auto begin() {
        return Iterator{file_};
    }

    auto end() {
        return End_iterator{};
    }

private:
    std::ifstream file_;
};

C++17允许我们在基于范围的for循环中使用不同类型的beginend迭代器。 使end_iterator成为不同类型(sentinel)更容易(也更优雅)。

匿名用户

这是一个满足LegacInPutIterator需求的自定义迭代器的简单示例:

class LineFileIterator
{
public:
    using value_type = std::string;
    using difference_type = std::ptrdiff_t;
    using reference = const std::string&;
    using pointer = const std::string*;
    using iterator_category = std::input_iterator_tag;
    LineFileIterator(): file(nullptr) {}
    explicit LineFileIterator(std::ifstream& f): file(&f) {}
    
    LineFileIterator& operator++()
    {
        read();
        return *this;
    }
    LineFileIterator operator++(int)
    {
        LineFileIterator tmp = *this;
        read();
        return tmp;
    }
    
    const std::string& operator*() const
    {
        return currentLine;
    }
    
    friend bool operator!=(const LineFileIterator& a, const LineFileIterator& b)
    {
        return not a.equal(b);
    }
    
    friend bool operator==(const LineFileIterator& a, const LineFileIterator& b)
    {
        return a.equal(b);
    }
private:
    bool equal(const LineFileIterator& it) const
    {
        return file == it.file;
    }
    
    void read()
    {
        if(not std::getline(*file, currentLine))
        {
            file = nullptr;
        }
    }

    std::ifstream* file;
    std::string currentLine;
};

begin方法应返回LineFileIterator{file},end方法应返回默认构造的LineFileIterator。

编辑:这个实现从C++11开始工作。

匿名用户

在C++17中,基于for的范围是这样实现的

{
  auto && __range = range_expression ;
  auto __begin = begin_expr;
  auto __end = end_expr;
  for (;__begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

你可以在这里查一下

如果你想让你的类适合使用基于范围的for循环,你应该定义5个函数:

  • 开始()
  • 结束()
  • 运算符*()
  • 无效运算符++()
  • 布尔运算符!=(...&other)

因为您希望您的类有一个迭代器,而不是一个迭代器,所以我们将简单地为底层数据结构定义一个内部迭代器,并用这个迭代器执行所有操作。

这与其他答案不同,在其他答案中,整个类都是一个迭代器。

这让生活变得很轻松。

读取完整的文件是一个单行程序。

顺便说一下,通过使用std::regex_token_iterator解析文件也是一个单行。 不需要助推什么的。 读取文件并将其拆分成令牌是典型的单行程序。

请看下面的简单程序:

#include <iostream>
#include <sstream>
#include <vector>
#include <fstream>
#include <regex>
#include <iterator>
#include <algorithm>

using Lines = std::vector<std::string>;
std::regex re{ "," };

class Csv {
    // Here we staore all lines of the file
    Lines lines{};

    // Local iterator to this lines
    Lines::iterator lineIterator{};

    // Function to read all lines of the file
    void readData(const std::string& fileName);

public:
    // Simple constructor
    explicit Csv(std::string fileName) { readData(fileName); }
    ~Csv() {};

    // Iterators to access the lines data
    Lines::iterator begin() { lineIterator = lines.begin(); return lineIterator;  };
    Lines::iterator end() { lineIterator = lines.end(); return lineIterator; };
    std::string& operator *() { return *lineIterator; }
    void operator ++() { ++lineIterator; }
    bool operator != (const Lines::iterator& other) { return other != lineIterator;  }
};

void Csv::readData(const std::string& fileName) {

    // Open File and check, if it could be opened
    if (std::ifstream fileStream{ fileName }; fileStream) {

        // Clear old existing data
        lines.clear(); 

        // Read all lines
        for (std::string line{}; std::getline(fileStream, line); lines.push_back(line))
            ;
    }
    else {
        std::cerr << "\n*** Error: Could not open file '" << fileName << "'\n";
    }
}

int main() {

    // Open file and read all lines
    Csv csv("r:\\text.txt");

    // Range based for for iterating over the lines in the file
    for (const std::string& line : csv) {
        std::cout << "\n\n" << line << "   ->\n";

        std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {}, std::ostream_iterator<std::string>(std::cout, "\t"));
    }

    return 0;
}