C++读/写二进制文件的类

7
我需要编写一个类到二进制文件中,然后需要读取它。
我有Triangle和BinaryFile类,以及其他一些类。我不确定我是在错误地写入还是错误地读取。读取时发生错误。调试后,我认为对于我的私有变量,它获取了不适当的数据。如果有人能给我一些关于如何使其正常工作的建议,我将非常高兴。
我不确定是否应该粘贴整个代码,因此我将为您提供一小段代码片段。 以防万一,这是我的源代码的下载链接:

https://my.pcloud.com/publink/show?code=XZJ7CYZbsLWLglqV5p83csijcEUTFqqpM3k

我是一个编程新手,我的英语不太好,所以提前为我的错误道歉。

class Point
{
private:
    int x;
    int y;
};

class Figure
{
private:
    string name;
    string type;
};

class Triangle: public Figure
{
private:
    Point p1, p2, p3;
};

class BinaryFile
{
private:
    string FileName;
    fstream File;
public:
    //...
    void AddNewFigure(istream& stream)
    {       
        File.open(this->FileName, ios::binary | ios::app);
        if(!this->File)
        {
            cerr<<"File error <"<<this->FileName<<">\n";
            exit(1);
        }
        Triangle fig;
        fig.MakeNewFigure(stream);
        File.write((char*)&fig, sizeof(Triangle));
        File.close();
    }

    Triangle GetTriangle()
    {
        Triangle trig;
        Point p;
        string str(""); int x(0);
        File.open(this->FileName, ios::binary | ios::in);
        if(!this->File)
        {
            cerr<<"File error <"<<this->FileName<<">\n";
            exit(1);
        }
        File.read((char*)&trig, sizeof(Triangle));
        File.close();
        return trig;
    }
};

6
请注意,你不能通过直接写入结构体来将包含std::string的结构体写入文件。对于int类型可以这样做,因为它们存储在结构体中,但是std::string具有指向动态分配内存的指针,该内存存储在结构体外部。为避免出现问题,请使用其他方法将std::string写入文件。 - Jerry Jeremiah
好的,我需要用其他东西来替换字符串吗? - Nikolay Tashev
1
不,你只需要在每个结构体中编写一个write()和read()函数,以便从流中写入和读取结构体的内容,因为结构体知道它的成员是存储在结构体内部(如int)还是外部(如std::string)。我会提供一个答案... - Jerry Jeremiah
3个回答

8
答案取决于您是为了学习文件如何工作而进行此操作,还是保存到文件只是附带的,您不关心它是如何工作的。
如果您只想获取保存和恢复的内容,而且不关心它是如何工作的,则使用第三方库。有许多这样的库。
如果您想学习如何读写文件,则需要编写自己的读写函数。我已经编写了一个示例程序来解释它是如何工作的:
#include <string>
#include <fstream>
#include <iostream>

class Point
{
private:
    int x;
    int y;
public:
    Point():x(0),y(0){}
    Point(int x,int y):x(x),y(y){}

    void write(std::ostream& f)
    {
        // We can just write out the bytes for x and y because
        // they are primitive types stored in the class
        f.write( (char*)&x, sizeof(x) );
        f.write( (char*)&y, sizeof(y) );
    }
    void read(std::istream& f)
    {
        // We can just read the bytes directly into x and y because
        // they are primitive types stored in the class
        f.read( (char*)&x, sizeof(x) );
        f.read( (char*)&y, sizeof(y) );
    }
};

class Figure
{
private:
    std::string name;
    std::string type;
public:
    Figure(){}
    Figure(std::string name,std::string type):name(name),type(type){}

    void write(std::ostream& f)
    {
        size_t size;

        // we need to store the data from the string along with the size
        // because to restore it we need to temporarily read it somewhere
        // before storing it in the std::string (istream::read() doesn't
        // read directly to std::string)

        size = name.size();
        f.write( (char*)&size, sizeof(size_t) );
        f.write( (char*)name.c_str(), size );

        size = type.size();
        f.write( (char*)&size, sizeof(size_t) );
        f.write( (char*)type.c_str(), size );
    }
    void read(std::istream& f)
    {
        size_t size;
        char *data;

        // when we read the string data we need somewhere to store it
        // because we std::string isn't a primitive type.  So we read
        // the size, allocate an array, read the data into the array,
        // load the std::string, and delete the array

        f.read( (char*)&size, sizeof(size) );
        data = new char[size+1];
        f.read( data, size );
        data[size]='\0';
        name = data;
        delete data;

        f.read( (char*)&size, sizeof(size) );
        data = new char[size+1];
        f.read( data, size );
        data[size]='\0';
        type = data;
        delete data;
    }
};

class Triangle: public Figure
{
private:
    Point p1, p2, p3;
public:
    Triangle(){}
    Triangle(Point x,Point y,Point z,Figure f):p1(x),p2(y),p3(z),Figure(f){}


    void write(std::ostream& f)
    {
        // First write the base class then write the members of this class
        Figure::write(f);
        p1.write(f);
        p2.write(f);
        p3.write(f);
    }
    void read(std::istream& f)
    {
        // First read the base class then read the members of this class
        Figure::read(f);
        p1.read(f);
        p2.read(f);
        p3.read(f);
    }
};

class BinaryFile
{
private:
    std::string FileName;
    std::fstream File;
public:
    BinaryFile(std::string FileName) : FileName(FileName) {};
    void WriteTriangle()
    {
        File.open(FileName, std::ios::binary | std::ios::out);
        if(!File)
        {
            std::cerr<<"File error <"<<FileName<<">\n";
            exit(1);
        }
        Triangle trig({1,2},{3,4},{5,6},{"name","type"}); // something new
        trig.write(File);
        File.close();
    }

    Triangle ReadTriangle()
    {
        File.open(FileName, std::ios::binary | std::ios::in);
        if(!File)
        {
            std::cerr<<"File error <"<<FileName<<">\n";
            exit(1);
        }
        Triangle trig; // default values
        trig.read(File);
        File.close();
        return trig;
    }
};

main()
{
    BinaryFile bin("file.bin");
    bin.WriteTriangle();
    Triangle trig = bin.ReadTriangle();
    // at this point trig has the values we stored
    return 0;
}

我按照你的方法做了,并且现在它可以正常工作了。非常感谢你的帮助! - Nikolay Tashev
你不需要中间的字符数组,可以直接读入字符串。 - MaxNoe

1
由于您的源代码较大且缺少数据文件,因此很难重现错误。但是快速检查显示您正在使用块操作读写二进制数据。
    Triangle trig; 
    ...
    File.read((char*)&trig, sizeof(Triangle));

不幸的是,只有当你想要保存/加载的对象属于可平凡复制的类时,这种方法才有效,如下面的代码所示:

if (is_trivially_copyable<Triangle>::value) 
    cout << "Triangle is  trivially copyable" << endl; 
else cout << "Triangle is not trivially copyable" << endl; 

所以,您需要逐个字段地序列化对象内容,而不是使用块操作。这个序列化常见问题解答应该帮助您考虑替代方案。

0

您需要做的是将应该保存到文件中的类/数据序列化。有几个库已经针对时间和内存消耗进行了优化。您介意使用第三方库吗?

如果不介意,可以看看例如boost serializationcereal或甚至Google的ProtoBuf。如果您使用C++11,我认为Cereal是一个很好的开始。

如果您想编写自己的序列化程序,您需要考虑每个具有动态大小(例如字符串)的对象,您还需要将对象的大小保存到文件中。有关更多信息,请参见此处:

https://dev59.com/0WTWa4cB1Zd3GeqPHuTU#11003590


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