在C++开发中,经常需要将结构体数据持久化存储到文件中,二进制文件相比文本文件占用空间更小、读写速度更快,因此将结构体序列化后存入二进制文件是常用的方案。序列化就是把结构体的数据按照特定格式转换为可存储的字节流,反序列化则是从字节流中还原出原来的结构体数据。

基础序列化与反序列化实现
对于不包含指针成员、没有虚函数的简单结构体,可以直接通过二进制读写的方式实现序列化与反序列化,核心是使用ofstream和ifstream的write和read方法。
简单结构体示例
首先定义一个不包含指针的基础结构体:
#include <iostream>
#include <fstream>
#include <cstring>
// 定义简单结构体
struct Student {
int id;
char name[20];
float score;
};
int main() {
// 原始数据
Student stu1;
stu1.id = 1001;
strcpy(stu1.name, "张三");
stu1.score = 92.5f;
// 序列化:写入二进制文件
std::ofstream outFile("student.dat", std::ios::binary);
if (!outFile) {
std::cout << "文件打开失败" << std::endl;
return 1;
}
// 直接写入整个结构体
outFile.write(reinterpret_cast<const char*>(&stu1), sizeof(Student));
outFile.close();
// 反序列化:从二进制文件读取
Student stu2;
std::ifstream inFile("student.dat", std::ios::binary);
if (!inFile) {
std::cout << "文件打开失败" << std::endl;
return 1;
}
inFile.read(reinterpret_cast<char*>(&stu2), sizeof(Student));
inFile.close();
// 输出还原后的数据
std::cout << "id: " << stu2.id << std::endl;
std::cout << "name: " << stu2.name << std::endl;
std::cout << "score: " << stu2.score << std::endl;
return 0;
}
注意事项
- 这种方式只适用于POD(Plain Old Data)类型结构体,即没有用户定义的构造函数、析构函数、虚函数,成员也没有指针或动态分配的内存。
- 不同平台的内存对齐规则可能不同,直接写入的结构体字节大小可能存在差异,跨平台使用时需要手动处理对齐问题,或者逐个成员读写。
包含动态成员的结构体处理
如果结构体中包含指针、动态数组等成员,直接写入结构体只会保存指针的地址,而不是指向的实际数据,反序列化后指针会指向无效内存,因此需要手动逐个成员序列化。
带动态成员的结构体示例
#include <iostream>
#include <fstream>
#include <cstring>
struct Person {
int age;
char* name; // 动态分配的姓名
int nameLen; // 姓名长度,用于反序列化时分配内存
};
// 序列化函数
void serializePerson(const Person& p, const char* fileName) {
std::ofstream outFile(fileName, std::ios::binary);
if (!outFile) {
std::cout << "文件打开失败" << std::endl;
return;
}
// 先写入age
outFile.write(reinterpret_cast<const char*>(&p.age), sizeof(int));
// 写入姓名长度
outFile.write(reinterpret_cast<const char*>(&p.nameLen), sizeof(int));
// 写入姓名实际内容
outFile.write(p.name, p.nameLen);
outFile.close();
}
// 反序列化函数
Person deserializePerson(const char* fileName) {
Person p;
std::ifstream inFile(fileName, std::ios::binary);
if (!inFile) {
std::cout << "文件打开失败" << std::endl;
p.age = 0;
p.name = nullptr;
p.nameLen = 0;
return p;
}
// 读取age
inFile.read(reinterpret_cast<char*>(&p.age), sizeof(int));
// 读取姓名长度
inFile.read(reinterpret_cast<char*>(&p.nameLen), sizeof(int));
// 分配内存
p.name = new char[p.nameLen + 1];
// 读取姓名内容
inFile.read(p.name, p.nameLen);
p.name[p.nameLen] = ' '; // 添加字符串结束符
inFile.close();
return p;
}
int main() {
// 构造原始数据
Person p1;
p1.age = 25;
const char* rawName = "李四";
p1.nameLen = strlen(rawName);
p1.name = new char[p1.nameLen + 1];
strcpy(p1.name, rawName);
// 序列化
serializePerson(p1, "person.dat");
// 反序列化
Person p2 = deserializePerson("person.dat");
// 输出结果
std::cout << "age: " << p2.age << std::endl;
std::cout << "name: " << p2.name << std::endl;
// 释放内存
delete[] p1.name;
delete[] p2.name;
return 0;
}
常见问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 跨平台读写数据不一致 | 统一使用小端或大端字节序,读写时做字节序转换,避免直接依赖平台默认对齐 |
| 结构体版本升级后旧数据无法读取 | 在序列化数据开头添加版本号字段,反序列化时根据版本号做兼容处理 |
| 动态成员内存泄漏 | 反序列化分配的内存需要在使用完成后手动释放,或者改用智能指针管理 |
总结
C++中实现结构体二进制文件的序列化与反序列化,核心是根据结构体的成员类型选择合适的读写方式。简单POD结构体可以直接整体读写,包含动态成员的结构体需要逐个成员处理。实际开发中还需要考虑跨平台、版本兼容、内存管理等问题,才能保证数据存储和读取的稳定性。