简体   繁体   中英

(C++) How do i read all files of a directory and put their contents in a vector/std::list?

The contents of the files are line by line and go in a "Schedule" struct. My objective is to stock these schedules in.txt files so they don't disappear after the end of execution, and to stock the structs in a vector or a list when i execute it again, by reading all files of a separated folder. I have no idea how to do this.

I supposed i could use getline() for a folder but even if it worked it would probably just give me the file names. That could work in a way, but getline() doesn't work like that.

I would use astd::filesystem::directory_iterator .

But first things first. The first thing you could do is to define an operator>> to read one Schedule from an std::istream (like a std::ifstream ).

#include <istream>

class Schedule {
private:
    friend std::istream& operator>>(std::istream& is, Schedule& s) {
        // extract _one_ Schedule from the istream:
        return is >> s.a >> s.b >> s.c;
    }
    friend std::ostream& operator<<(std::ostream& os, const Schedule& s) {
        return os << s.a << ' ' << s.b << ' ' << s.c << '\n';
    }

    // whatever is stored in a Schedule:
    int a, b, c;
};

... and in your container containing all the schedules you take two iterators and/or a range in order to populate it:

#include <ranges>
#include <vector>

class Schedules {
public:
    // populating via iterators
    template<class It, class EndIt>
    Schedules(It begin, EndIt end) : schedules(begin, end) {}

    // or via a range:
    Schedules(std::ranges::input_range auto&& range) :
        Schedules(std::ranges::begin(range), std::ranges::end(range)) {}

    auto begin() { return schedules.begin(); }
    auto end() { return schedules.end(); }

private:
    std::vector<Schedule> schedules;
};

With those in place, you can create a class to open a file and provide iterators to populate your Schedules container. It can be a rather thin wrapper around a std::ifstream with the begin() and end() iterator type being std::istream_iterator<Schedule> which can then be used to populate the Schedules container.

#include <fstream>
#include <iterator>

class ScheduleFileLoader {
public:
    ScheduleFileLoader() = default;

    ScheduleFileLoader(const std::filesystem::path& path) : ifs(path) {}

    ScheduleFileLoader(const std::filesystem::directory_entry& dent) :
        ScheduleFileLoader(dent.path()) {}

    operator bool() const { return ifs.good(); }

    void open(const std::filesystem::path& path) {
        if(ifs.is_open()) ifs.close();
        ifs.open(path);
    }

    void open(const std::filesystem::directory_entry& dent) {
        open(dent.path());
    }

    using iterator = std::istream_iterator<Schedule>;

    iterator begin() { return iterator{ifs}; }
    iterator end() { return iterator{}; }

private:
    std::ifstream ifs{};
};

and you could use it to load the schedules from one file:

Schedules schedules(ScheduleFileLoader("data_directory/schedules01.txt"));

But you want to take it further by loading all files in a directory into your Schedules container. You need to

  • Iterate over all the files in the directory where the files containing Schedule s are stored.
  • Open each file using a ScheduleFileLoader
  • Provide iterators to treat the stream of Schedule s as one range.

You can then encapsulate std::filesystem::directory_iterator and a ScheduleFileLoader in yet another class, ScheduleDirectoryLoader . When its ScheduleFileLoader has depleated a file, open the next file - and keep going until the std::filesystem::directory_iterator reaches the end of the directory. This requires a custom iterator.

#include <filesystem>

class ScheduleDirectoryLoader {
public:
    ScheduleDirectoryLoader(const std::filesystem::path& dir) : m_dir(dir) {}
    ScheduleDirectoryLoader(const std::filesystem::directory_entry& dent) :
        ScheduleDirectoryLoader(dent.path()) {}

    struct iterator {                         // the custom iterator
        using difference_type = std::intptr_t;
        using value_type = Schedule;
        using pointer = value_type*;
        using reference = value_type&;
        using iterator_category = std::input_iterator_tag;

        iterator() = default; // end iterator

        iterator(const iterator& other) : dit(other.dit), fit(other.fit) {}
        iterator(iterator&&) = default;
        iterator& operator=(const iterator& other) {
            dit = other.dit;
            // the ScheduleFileLoader is not copyable because ifstream is not
            fit = other.fit;
            return *this;
        }
        iterator& operator=(iterator&&) = default;

        iterator(const std::filesystem::path& dir) : dit(dir) {
            if(dit != std::filesystem::directory_iterator{}) {
                sfl.open(*dit);
                if(sfl) fit = sfl.begin();
            }
        }

        const Schedule& operator*() const { return *fit; }

        // The advancement of this iterator:
        // 1. step the ScheduleFileLoader::iterator
        // 2. if it reaches the end of the file, ...
        // 3. step the directory iterator
        // 4. if it doesn't reach the end of the directory, ...
        // 5. open the new file
        // 6. if opening the file succeeds, get a new begin() iterator
        iterator& operator++() {
            if(++fit == ScheduleFileLoader::iterator{}) { // end of this file
                if(++dit != std::filesystem::directory_iterator{}) {
                    // not last in directory
                    sfl.open(*dit);
                    if(sfl) fit = sfl.begin();
                }
            }
            return *this;
        }
        iterator operator++(int) {
            iterator copy(*this);
            ++(*this);
            return copy;
        }

        bool operator==(const iterator& rhs) const { return fit == rhs.fit; }
        bool operator!=(const iterator& rhs) const { return !(*this == rhs); }

    private:
        std::filesystem::directory_iterator dit{};
        ScheduleFileLoader sfl;
        ScheduleFileLoader::iterator fit;
    };

    iterator begin() { return m_dir; }
    iterator end() { return {}; }

private:
    std::filesystem::path m_dir;
};

With the ScheduleFileLoader and ScheduleDirectoryLoader in place, you can load a single file or all the files. If you need to be able to load from other sources than files, you create another adapter that provides iterators that dereferences into something that can be used to construct a Schedule (like a std::istream_iterator<Schedule> ). It's sometimes as easy as with the single source ScheduleFileLoader and sometimes a bit more tricky as with the ScheduleDirectoryLoader .

Usage example:

#include <iostream>

int main() {
    Schedules schedules(ScheduleDirectoryLoader("data_directory"));

    for(const Schedule& s : schedules) {
        std::cout << s;
    }
}

The same as @Ted above.

But we can simplify a bit:

#include <iostream>
#include <filesystem>
#include <vector>

int main()
{
    namespace fs = std::filesystem;
    std::vector<fs::directory_entry> dirents{fs::directory_iterator("."), fs::directory_iterator{}};

    for (auto const& dir: dirents) {
        std::cout << dir.path() << "\n";
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM