【问题标题】:Read from file in C++在 C++ 中从文件中读取
【发布时间】:2021-12-18 20:52:55
【问题描述】:

我正在尝试读取以下格式的文件

id1 1 2 3
id2 2 4 6
id3 5 6 7
...

使用此代码

Dataset::Dataset(ifstream &file) {
    string token;
    int i = 0;
    while (!file.eof() && (file >> token)){
        // read line tokens one-by-one
        string ID = token;
        vector<int> coords;
        while ((file.peek()!='\n') && (!file.eof()) && (file >> token)) {
            coords.push_back(atoi(token.c_str()));
        }
        points.push_back(new Point(ID, coords));
        i++;
    }
    cout << "Loaded " << i << " points." << endl;
}

但它告诉我我读了 0 分。我做错了什么?

编辑:我使用input_stream.open(input_file) 打开它,file.good() 返回true

编辑 #2:实际上 .good() 第一次返回 true,然后返回 false。这是怎么回事?

编辑#3:伙计们。真是太棒了。当我通过 cin 将路径设置为 Dataset/test.txt 时,它可以工作,而当我通过命令行将路径设置为 Dataset\test.txt 时,它不会...

现在的问题是它似乎并没有止步于新行!

编辑#4:又是吓人的窗户!它偷看的是 '\r' 而不是 '\n'。

【问题讨论】:

  • 听起来你可能需要学习如何使用调试器来单步调试你的代码。使用好的调试器,您可以逐行执行您的程序,并查看它与您期望的偏差在哪里。如果您要进行任何编程,这是必不可少的工具。进一步阅读:How to debug small programsDebugging Guide
  • 可能文件没有打开。也许您将数据文件放在错误的位置或错误地命名。也许该文件没有您期望的内容。调试应该对您有所帮助。在Dataset::Dataset(ifstream &amp;file) 中设置断点并在断点被命中后一次调试代码 1 行。查看每条语句执行后的变量和流程。
  • 为什么在向量中存储Point* 而不是Point
  • @Michael "我希望这些点在堆上一次,并且在通过值传递它们时不会被复制" - 为什么需要向量中的指针来避免复制?您将获得的唯一副本是矢量调整大小 - 但您几乎不会注意到这一点。
  • 有vector::emplace_back

标签: c++ stream


【解决方案1】:

您不应在循环条件中使用eof()。有关详细信息,请参阅Why is iostream::eof inside a loop condition considered wrong?。您可以改为使用以下程序读取 Point* 的向量。


#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
class Point 
{
    public:
        std::string ID = 0;
        std::vector<int> coords;
    Point(std::string id, std::vector<int> coord): ID(id), coords(coord)
    {
        
    }
};
int main()
{
    
    std::vector<Point*> points;
    std::ifstream file("input.txt");
    std::string line;
    int var = 0;
    while (std::getline(file, line, '\n'))//read line by line
    {
        int j = 0;
        std::istringstream ss(line);
        
        std::string ID;
        ss >> ID;
        
        std::vector<int> coords(3);//create vector of size 3 since we already know only 3 elements needed
        
        while (ss >> var) { 
            coords.at(j) = var;
            
            ++j;
        }
        
        points.push_back(new Point(ID, coords));

    }
    std::cout<<points.size()<<std::endl;

    //...also don't forget to free the memory using `delete` or use smart pointer instead
    return 0;
}

以上程序的输出可见here

请注意,如果您使用的是new,那么您必须使用delete 来释放您分配的内存。这在我给出的上述程序中没有完成,因为我只想展示如何以所需的方式读取数据。

【讨论】:

    【解决方案2】:

    这里有个想法:重载operator&gt;&gt;:

    struct Point
    {
        int x, y, z;
        friend std::istream& operator>>(std::istream& input, Point& p);
    };
    
    std::istream& operator>>(std::istream& input, Point& p)
    {
        input >> p.x;
        input >> p.y;
        input >> p.z;
        input.ignore(10000, '\n'); // eat chars until end of line.
        return input;
    }
    
    struct Point_With_ID 
      : public Point
    {
        std::string id;
        friend std::istream& operator>>(std::istream& input, Point_With_ID& p);
    };
    
    
    std::istream& operator>>(std::istream& input, Point_With_ID& p)
    {
        input >> p.id;
        input >> static_cast<Point&>(p);  // Read in the parent items.
        return input;
    }
    

    您的输入可能如下所示:

    std::vector<Point_With_ID> database;
    Point_With_ID p;
    while (file >> p)
    {
        database.push_back(p);
    }
    

    我分离了 Point 类,以便它可以在其他程序或作业中使用。

    【讨论】:

    • 感谢指正。
    【解决方案3】:

    我设法通过考虑 '\r' 和 '\n' 结尾并忽略尾随空格来使其工作,如下所示:

    Dataset::Dataset(ifstream &file) {
      string token;
      int i = 0;
      while (file >> token){
          // read line tokens one-by-one
          string ID = token;
          vector<int> coords;
          while ((file.peek()!='\n' && file.peek()!='\r') && (file >> token)) {   // '\r' for windows, '\n' for unix
            coords.push_back(atoi(token.c_str()));
            if (file.peek() == '\t' || file.peek() == ' ') {   // ignore these
                file.ignore(1);
            }
          }
          Point p(ID, coords);
          points.emplace_back(p);
          i++;
          // ignore anything until '\n'
          file.ignore(32, '\n');
       }
       cout << "Loaded " << i << " points." << endl;
    }
    

    可能不是建议的最佳解决方案,但它确实有效!

    【讨论】:

    • 这看起来你并没有太注意你得到的建议和答案。您也没有正确使用emplace_back。该用法复制Point。您不需要临时的Point。只需:points.emplace_back(std::move(ID), std::move(coords));
    • 这很公平。我只是习惯于使用动态分配的对象来避免复制。 Uni 对 C++98 很严格,所以那里可能没有移动构造函数。
    • "Uni 对 C++98 很严格" - 好的,这会留下一个凹痕 - 不,C++98 中不存在移动语义。
    【解决方案4】:

    您已经在一个复杂的反序列化构造函数中烘焙了所有内容。这使得代码难以理解和维护。

    • 你有一个坐标,所以为它创建类,我们可以称之为Coord,它能够进行自己的反序列化。
    • 您有一个 Point,它由一个 ID 和一个坐标组成,因此请为此创建一个能够自行反序列化的类。
    • 然后Dataset 将只使用Point 的反序列化功能。
    • 不要将反序列化限制为ifstreams。让它与 any istream 一起工作。

    反序列化通常通过重载所涉及类型的operator&gt;&gt;operator&lt;&lt; 来完成。这是将问题分解为更易于理解的较小部分的一种方法:

    struct Coord {
        std::vector<int> data;
    
        // read one Coord
        friend std::istream& operator>>(std::istream& is, Coord& c) {        
            if(std::string line; std::getline(is, line)) { // read until end of line
                c.data.clear();
                std::istringstream iss(line); // put it in an istringstream
                // ... and extract the values:
                for(int tmp; iss >> tmp;) c.data.push_back(tmp);
            }
            return is;
        }
        // write one Coord
        friend std::ostream& operator<<(std::ostream& os, const Coord& c) {
            if(not c.data.empty()) {
                auto it = c.data.begin();
                os << *it;
                for(++it; it != c.data.end(); ++it) os << ' ' << *it;
            }
            return os;
        }
    };
    
    struct Point {
        std::string ID;
        Coord coord;
    
        // read one Point
        friend std::istream& operator>>(std::istream& is, Point& p) {
            return is >> p.ID >> p.coord;
        }
        // write one Point
        friend std::ostream& operator<<(std::ostream& os, const Point& p) {
            return os << p.ID << ' ' << p.coord;
        }
    };
    
    struct Dataset {
        std::vector<Point> points;
    
        // read one Dataset
        friend std::istream& operator>>(std::istream& is, Dataset& ds) {
            ds.points.clear();
            for(Point tmp; is >> tmp;) ds.points.push_back(std::move(tmp));
    
            if(!ds.points.empty()) is.clear();
            return is;
        }
        // write one Dataset
        friend std::ostream& operator<<(std::ostream& os, const Dataset& ds) {
            for(auto& p : ds.points) os << p << '\n';
            return os;
        }
    };
    

    如果你真的想要Dataset 中的反序列化构造函数,你只需要添加这些:

        Dataset() = default;
        Dataset(std::istream& is) { 
            if(!(is >> *this))
                throw std::runtime_error("Failed reading Dataset");
        }
    

    然后您可以打开您的文件并使用operator&gt;&gt; 填充Datasetoperator&lt;&lt; 以在屏幕上打印Dataset - 如果您愿意,也可以打印到另一个文件。

    int main() {
        if(std::ifstream file("datafile.dat"); file) {
            if(Dataset ds; file >> ds) {      // populate the Dataset
                std::cout << ds;              // print the result to screen
            }
        }
    }
    

    Demo

    【讨论】:

    • 3 就是一个例子。不同输入的维度不同,这就是我使用向量的原因。
    • @Michael 我确实问过它是否总是 3,你说是的。我要求确认,但您没有回答,所以我使用了可用的信息。时间允许时我会调整答案。
    • 对不起,我误会了。
    • @Michael 我终于有时间更新答案了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-02
    • 2012-05-15
    • 2016-06-18
    • 2016-08-02
    • 2020-11-28
    • 2011-01-07
    • 1970-01-01
    相关资源
    最近更新 更多