如何在二进制文件中读取/写入结构体?

22

我遇到了一个小问题。我有一个结构体,其中包含一个向量。请注意,每次迭代都会动态生成这个向量。现在,在特定的迭代中,如何将包含大小为n的向量的结构体存储到二进制文件中?

此外,在检索时,假设我知道向量的大小,那么如何从二进制文件中检索出包含所有存储元素向量的结构变量?

我能够将一些内容存储到二进制文件中(因为我可以看到在写入时大小在增加),但是当我尝试检索元素时,我得到的向量大小为零。

不幸的是,我必须使用标准STL来实现这一点,并且不能使用任何第三方库。


2
你是如何将结构体写入文件的?能否提供一些示例代码? - Dusty Campbell
1
你是否在向量的外部表示中添加了向量的“大小”? - Fred Foo
这个怎么样:https://dev59.com/XHE95IYBdhLWcg3wEpzo - Dusty Campbell
你可能只是保存了指向向量的指针... - n00b
发布一些代码。你是如何保存向量数据和大小的? - BЈовић
显示剩余3条评论
2个回答

27

您应该看一下Boost Serialization

如果您不能使用第三方库,那么您必须知道C++不支持直接序列化。这意味着您必须自己完成。

本文展示了一种将自定义对象序列化到磁盘并检索回来的好方法。而本教程则向您展示如何立即使用fstream开始。

这是我的尝试:

编辑:由于OP询问如何存储/检索多个记录,因此我决定更新原始代码。

那么,有什么变化吗?现在有一个数组student_t apprentice[3];用于存储3名学生的信息。整个数组被序列化到磁盘上,然后全部加载回RAM,从而可以读取/搜索特定记录。请注意,这是一个非常非常小的文件(84字节)。我不建议在巨大的文件中搜索记录时使用此方法。

#include <fstream>
#include <iostream>
#include <vector>
#include <string.h>

using namespace std;


typedef struct student
{
    char name[10];
    int age;
    vector<int> grades;
}student_t;

int main()
{
    student_t apprentice[3];  
    strcpy(apprentice[0].name, "john");
    apprentice[0].age = 21;
    apprentice[0].grades.push_back(1);
    apprentice[0].grades.push_back(3);
    apprentice[0].grades.push_back(5);    

    strcpy(apprentice[1].name, "jerry");
    apprentice[1].age = 22;
    apprentice[1].grades.push_back(2);
    apprentice[1].grades.push_back(4);
    apprentice[1].grades.push_back(6);

    strcpy(apprentice[2].name, "jimmy");
    apprentice[2].age = 23;
    apprentice[2].grades.push_back(8);
    apprentice[2].grades.push_back(9);
    apprentice[2].grades.push_back(10);

    // Serializing struct to student.data
    ofstream output_file("students.data", ios::binary);
    output_file.write((char*)&apprentice, sizeof(apprentice));
    output_file.close();

    // Reading from it
    ifstream input_file("students.data", ios::binary);
    student_t master[3];
    input_file.read((char*)&master, sizeof(master));         

    for (size_t idx = 0; idx < 3; idx++)
    {
        // If you wanted to search for specific records, 
        // you should do it here! if (idx == 2) ...

        cout << "Record #" << idx << endl;
        cout << "Name: " << master[idx].name << endl;
        cout << "Age: " << master[idx].age << endl;
        cout << "Grades: " << endl;
        for (size_t i = 0; i < master[idx].grades.size(); i++)
           cout << master[idx].grades[i] << " ";
        cout << endl << endl;
    }

    return 0;
}

输出:

Record #0
Name: john
Age: 21
Grades: 
1 3 5 

Record #1
Name: jerry
Age: 22
Grades: 
2 4 6 

Record #2
Name: jimmy
Age: 23
Grades: 
8 9 10

二进制文件的转储:

$ hexdump -c students.data 
0000000   j   o   h   n  \0 237   {  \0   �   �   {   � 025  \0  \0  \0
0000010   (   �   �  \b   4   �   �  \b   8   �   �  \b   j   e   r   r
0000020   y  \0   �  \0   �   �   |  \0 026  \0  \0  \0   @   �   �  \b
0000030   L   �   �  \b   P   �   �  \b   j   i   m   m   y  \0  \0  \0
0000040   �   6   �  \0 027  \0  \0  \0   X   �   �  \b   d   �   �  \b
0000050   h   �   �  \b                                                
0000054

7
通常情况下,对于 std::vector<> 的结构转储,你没有任何机会让它正常工作。(我猜想在某些实现中,对于非常小的向量,可能会起作用!) - Keith
1
嗨,非常感谢您的建议Karlphillip。对不起,早些时候因为您提到了Boost库,所以我认为如果我编辑了我的问题,其他人就不会给第三方库的建议了。 - Shankar Raju
8
这段代码严重有问题。之所以在测试中似乎能够工作,只是因为你将数据读回到相同的进程中,因此从磁盘读取的指针仍然指向相同的对象。实际上,对象数据从未被正确保存,当原始对象被销毁(可能是由于进程重启)时,从文件读回的数组将停止工作。 - Ben Voigt
2
@stackoverflowwww:vector确实是问题所在,它包含容量、计数和指向真实数据的指针。将该指针保存到磁盘上是没有意义的,您需要保存它所指向的数据。Jerry的答案解释了一个很好的方法来做到这一点。 - Ben Voigt
4
因为这种方式对一个std::vector进行序列化是完全无意义的,所以被踩了。 - Daniel Jour
显示剩余8条评论

20

通常,你可以通过先写入向量的长度,然后写入该数量的元素来序列化一个向量。当你读取它时,首先读取长度可以让你知道需要读取多少个元素作为该向量的一部分。作为一个简单的第一步近似,可以考虑以下内容:

template<class T>
std::ostream &operator<<(std::ostream &output, T const &input) {
    T::size_type size = input.size();

    output << size << "\n";
    std::copy(input.begin(), input.end(), 
         std::ostream_iterator<T::value_type>(output, "\n"));

    return output;
}

template<class T>
std::istream &operator>>(std::istream &input, T &output) {
    T::size_type size, i;

    input >> size;
    output.resize(size);
    std::copy_n(
        std::istream_iterator<t::value_type>(input),
        size,
        output.begin());

    return input;
}

这里可以进行大量的微调、改进和简单修改 -- 举个例子,目前我是通过引用传递向量(或其他容器,例如std::deque等),而不是通过迭代器传递。这可能会简化大多数用法,但不太适合其余库。

此外,它以文本格式序列化,每行一个数字。关于文本与二进制比较的讨论已经发生过,因此我不会在这里重复所有的论点 -- 我只想指出,同样的基本思想可以在二进制格式下同样有效地实现。


谢谢Jerry。我有一个类似的实现,其中我将包含在结构体中的向量序列化到二进制文件中,但是在读取时向量的大小被检索为零,无论如何,让我重新审视我的解决方案,如果需要澄清,我会再联系你。谢谢。 - Shankar Raju
将第二个函数中的 T 大写:std::istream_iterator<T::value_type>(input),(尝试编辑和修复,但无法接受。) - jmcarter9t

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