如何在C++中正确地将向量写入二进制文件?

30

首先抱歉我的英语不好。我刚加入这个论坛,并且正在搜索如何正确地将向量写入二进制文件。我从这个论坛得到了一个答案,像这样(我稍微修改了一下):

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

using namespace std;

class Student
{
public:
char m_name[30];
int m_score;

public:
Student()
{

}
Student(const char name[], const int &score)
:m_score(score)
{
    strcpy(m_name, name);
}
void print() const
{
    cout.setf(ios::left);
    cout.width(20);
    cout << m_name << " " << m_score << endl;
}
};


int main()
{
    vector<Student> student;
    student.push_back(Student("Alex",19));
    student.push_back(Student("Maria",20));
    student.push_back(Student("muhamed",20));
    student.push_back(Student("Jeniffer",20));
    student.push_back(Student("Alex",20));
    student.push_back(Student("Maria",21));

    ofstream fout("data.dat", ios::out | ios::binary);
    fout.write((char*) &student, sizeof(student));
    fout.close();

    vector<Student> student2;

    ifstream fin("data.dat", ios::in | ios::binary);
    fin.seekg(0, ifstream::end);
    int size = fin.tellg() / sizeof (student2);
    student2.resize(size);
    fin.seekg(0, ifstream::beg);
    fin.read((char*)&student2, sizeof(student2));
    vector<Student>::const_iterator itr = student2.begin();
    while(itr != student2.end())
    {
            itr->print();
            ++itr;
    }
    fin.close();
    return 0;
}

但是当我在我的Linux Mint上运行它时,我得到了这个结果:

Alex                 19
Maria                20
muhamed              20
Jeniffer             20
Alex                 20
Maria                21
*** glibc detected *** ./from vector to binary: corrupted double-linked list: 0x0000000000633030 ***

我是c++的新手。 有人可以帮帮我吗?这个问题困扰了我两周了。 提前感谢你的回答。


4
标准提示:使用 g++ -Wall -g 进行编译,在消除所有警告之前改善您的代码,学会使用 gdbvalgrind 进行调试。 - Basile Starynkevitch
4个回答

29
您正在将向量结构写入文件,而不是其数据缓冲区。尝试更改写入过程为:
ofstream fout("data.dat", ios::out | ios::binary);
fout.write((char*)&student[0], student.size() * sizeof(Student));
fout.close();

而不是根据文件大小计算向量的大小,最好在之前写入向量的大小(对象数量)。这样一来,您可以将其他数据写入同一个文件中。
size_t size = student.size();
fout.write((char*)&size, sizeof(size));

只是一个提示,使用已经继承文件流输出的ofstream时不需要使用ios::out - therealanshuman

27

要将一个 vector<T> 中的PODs 存储到文件中,您需要写入该向量的内容,而不是向量本身。您可以使用 &vector[0] 来访问原始数据,即第一个元素的地址(假设它至少包含一个元素)。要获取原始数据长度,请将向量中的元素数量与一个元素的大小相乘:

strm.write(reinterpret_cast<const char*>(&vec[0]), vec.size()*sizeof(T));

当您从文件中读取向量时,同样适用此规则;元素数量等于文件总大小除以一个元素的大小(假设您只在文件中存储一种POD类型):
const size_t count = filesize / sizeof(T);
std::vector<T> vec(count);
strm.read(reinterpret_cast<char*>(&vec[0]), count*sizeof(T));

只有在文件大小可计算元素数量的情况下才有效(如果只存储一种POD类型,或者所有向量包含相同数量的元素)。如果向量包含不同长度的不同POD,则必须在写入原始数据之前将向量中元素的数量写入文件。

此外,在不同系统之间以二进制形式传输数字类型时,请注意endianness


4
您可能无法以这种方式编写二进制代码来操作 std::vector,因为该模板包含内部指针,对其进行写入和重新读取是没有意义的。
以下是一些通用建议:
  • 不要直接写二进制文件来存储STL模板容器(例如std::vectorstd::map),它们肯定包含你不想直接写入的内部指针。如果你真的需要写入它们,请实现自己的写入和读取例程(例如使用STL迭代器)。

  • 避免不加注意地使用strcpy。如果名称超过30个字符,你的代码将会崩溃。至少使用strncpy(m_name, name, sizeof(m_name));(但即使对于一个30个字符的名称,这也可能效果不佳)。实际上,m_name应该是一个std::string

  • 显式序列化你的容器类(通过处理每个有意义的成员数据)。你可以考虑使用JSON表示法(或者也许是YAML,或者甚至是我认为太复杂而不推荐的XML)进行序列化。它给你一个文本转储格式,你可以很容易地用标准编辑器(例如emacsgedit)检查它。你会发现有很多序列化免费库,例如jsoncpp等。

  • 学会用g++ -Wall -g编译,并使用gdb调试器和valgrind内存泄漏检测器;还要学会使用make和编写你的Makefile

  • 利用Linux是自由软件的优势,所以你可以查看它的源代码(即使STL头文件很复杂,你可能也想研究stdc++实现)。


4
对于函数read()和write(),你需要使用所谓的“POD”或“plain old data”。这基本上意味着类或结构体内部不能有指针,也不能有虚函数。vector的实现当然有指针——至于是否有虚函数就不确定了。
你需要编写一个函数,一次存储一个学生(或将一堆学生转换为字节数组等等)。你不能将非POD数据(尤其是指针)写入二进制文件,因为当你再次读取数据时,内存布局几乎肯定已经发生了改变。这有点像试图在商店的同一停车位停车——下次你来时,其他人可能已经把车停在了入口处第三个位置,所以你必须选择另一个位置。将编译器分配的内存视为停车位,将学生信息视为汽车。
[技术上来说,在这种情况下,情况甚至更糟——你的vector实际上并没有包含类内部的学生信息,而你要写入文件的正是这些信息,因此你甚至没有保存学生信息,只保存了它们所在的位置(停车位编号)]

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