结构体序列化和反序列化

0. 背景

将内存中的数据持久化到硬盘上,即将某些对象的属性(类或结构体成员变量的值)等信息保存为文件,并在需要使用这些数据时加载文件中记录的信息,重新构造出所需对象。

  • 序列化 Serialize:对象(内存) ===> 文件(硬盘)
  • 反序列化 Deserialize:文件(硬盘) ===> 对象(内存)

1. 序列化方式

存储类别

  • 字节(二进制文件)
  • 字符(文本文件)

“简单”结构体序列化

这里的“简单”特指平凡数据 POD(Plain Old Data),具体特征是布局有序,即成员变量在内存上是连续的,可以直接使用memcpy()memset等函数操作数据。

“复杂”结构体序列化

这里的“复杂”特指成员变量在内存上不连续,如:成员变量同时包含基本数据类型和指针,数据分别存储在栈和堆内存上,因此无法通过memcpy()memset函数直接操作。

2. 代码实现

常规版

本节提供成员变量中包含vector的结构体的序列化和反序列化方式。

#include <iostream>
#include <fstream>
#include <vector>
#include <string.h>
using namespace std;

struct Point2f {
    float x, y;
};

struct Data {
    int a, b;
    vector<Point2f> points;
};

bool serialize(const string& filename, const Data& data) {
    ofstream out;
    out.open(filename, ios::binary);
    if (!out.is_open()) {
        cerr << "Open file failed." << endl;
        return false;
    }

    out.write((const char*)(&data), sizeof(int) * 2); // a, b
    size_t size = data.points.size();
    out.write((const char*)(&size), sizeof(size_t));                // points.size()
    out.write((const char*)(data.points.data()), size * sizeof(Point2f)); // points.data

    out.close();
    return true;
}

bool deserialize(const string& filename, Data& data) {
    ifstream in;
    in.open(filename, ios::binary);
    if (!in.is_open()) {
        cerr << "Open file failed." << endl;
        return false;
    }

    in.read((char*)(&data), sizeof(int) * 2); // a, b
    size_t size;
    in.read((char*)(&size), sizeof(size_t)); // points.size()
    data.points.resize(size);
    in.read((char*)(data.points.data()), size * sizeof(Point2f)); // points.data

    in.close();
    return true;
}

int main() {
    auto print = [](const Data& data) {
        cout << data.a << " " << data.b << " { ";
        for (const auto& p : data.points) {
            cout << "(" << p.x << ", " << p.y << ") ";
        }
        cout << "}" << endl;
    };

    Data d = { 1, 2, { { 1.1, 1.1 }, { 2.2, 2.2 } } };
    print(d);

    string filename = "data.bin";
    serialize(filename, d);
    
    Data new_d = {};
    print(new_d);
    
    deserialize(filename, new_d);
    print(new_d);
    
	return 0;
}

进阶版

本节提供嵌套vector的序列化和反序列化方式,同时考虑内存对齐情况。

在这个示例中,结构体内存对齐到8,其中的 int 变量会被额外填充4个字节,因此计算变量长度(字节数)时必须考虑这部分内存。

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

#pragma pack(8)
struct Point2f {
    float x, y;
};

struct Data {
    int a; // 内存对齐到8,int会被额外填充4个字节,此变量所占内存为8字节
    long int b;
    vector<Point2f> points;
};
#pragma unpack(pop)

bool serialize(const string& filename, const vector<Data>& vec) {
    ofstream out;
    out.open(filename, ios::binary);
    auto size = vec.size();
    out.write((const char*)(&size), sizeof(size));
    for (const auto& item : vec) {
        // out.write((const char*)(&item), sizeof(int) * 2);
        // 由于内存对齐到8,item.a的实际长度为8字节,sizeof(int)结果为4,盲目使用(如上)会出现偏移量计算错误
        // 为了规避内存对齐带来的风向,推荐使用以下写法:
        // 1. 逐变量序列化,且sizeof参数为实际变量;
        // 2. 如果存在指针,需要手动计算数据长度(字节数);
        // 3. 如果存在模板类型,则通过valie_type获取类型,并使用decltype帮助计算长度。
        out.write((const char*)(&item.a), sizeof(item.a));
        out.write((const char*)(&item.b), sizeof(item.b));
        size = item.points.size();
        out.write((const char*)(&size), sizeof(size));
        out.write((const char*)(item.points.data()), size * sizeof(decltype(item.points)::value_type));
    }
    out.close();
    return true;
}

bool deserialize(const string& filename, vector<Data>& vec) {
    ifstream in;
    in.open(filename, ios::binary);
    if (!in.is_open()) {
        cerr << "Open file failed." << endl;
        return false;
    }

	// Tips: 处理未知类型的变量时,可以使用decltype类型推断来让编译器自己判断类型,避免出错
    decltype(Data::points.size()) size;
    in.read((char*)(&size), sizeof(size));
    vec.resize(size);
    for (auto& item : vec) {
        in.read((char*)(&item.a), sizeof(item.a));
        in.read((char*)(&item.b), sizeof(item.b));
        in.read((char*)(&size), sizeof(size));
        cout << size << endl;
        item.points.resize(size);
        in.read((char*)(item.points.data()), size * sizeof(decltype(item.points)::value_type));
    }
    in.close();
    return true;
}

int main() {
    auto print = [](const vector<Data>& data) {
        for (const auto& item : data) {
            cout << item.a << ", " << item.b << ", { ";
            for (const auto& p : item.points) {
                cout << "(" << p.x << "," << p.y << ") ";
            }
            cout << "}" << endl;
        }
    };

    vector<Data> d = {
        { 1, 1, { { 1.0, 1.0 }, { 1.1, 1.1 } } },
        { 2, 2, { { 2.0, 2.0 }, { 2.1, 2.1 } } },
        { 3, 3, { { 3.0, 3.0 }, { 3.1, 3.1 }, { 3.2, 3.2 } } }
    };
    print(d);

    string filename = "data.bin";
    serialize(filename, d);

    d.clear();
    deserialize(filename, d);
    print(d);

    return 0;
}

3. 总结

将结构体序列化为二进制字节文件时,最容易产生错误的环节是计算变量的字节数,重点注意事项:

  • 内存对齐规则
  • 成员变量在内存中是否连续
  • 系统架构是否一致(不同系统架构下基本数据类型的字节数可能不同)

因此,在设计序列化算法之前,请务必对结构体成员变量的内存布局有清晰的认识。

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
结构体序列化反序列化是将结构体数据转换为字节流,以便在网络传输或存储中使用,然后再将字节流还原为原始结构体数据的过程。 在C++中,可以通过以下几种方式实现结构体序列化反序列化: 1. 使用二进制流进行序列化反序列化:可以使用std::ofstream和std::ifstream类来将结构体数据写入文件或从文件读取结构体数据。例如: ```cpp struct MyStruct { int a; float b; char c; }; void serialize(const MyStruct& data, const std::string& filename) { std::ofstream file(filename, std::ios::binary); file.write(reinterpret_cast<const char*>(&data), sizeof(MyStruct)); file.close(); } void deserialize(MyStruct& data, const std::string& filename) { std::ifstream file(filename, std::ios::binary); file.read(reinterpret_cast<char*>(&data), sizeof(MyStruct)); file.close(); } ``` 2. 使用JSON进行序列化反序列化:可以使用第三方库(如RapidJSON、nlohmann/json等)将结构体数据转换为JSON格式的字符串,然后再将JSON字符串转换回结构体数据。例如: ```cpp #include <iostream> #include <string> #include <nlohmann/json.hpp> struct MyStruct { int a; float b; char c; }; void serialize(const MyStruct& data, const std::string& filename) { nlohmann::json json_data; json_data["a"] = data.a; json_data["b"] = data.b; json_data["c"] = data.c; std::ofstream file(filename); file << json_data.dump(4); // 4为缩进级别 file.close(); } void deserialize(MyStruct& data, const std::string& filename) { std::ifstream file(filename); nlohmann::json json_data; file >> json_data; file.close(); data.a = json_data["a"]; data.b = json_data["b"]; data.c = json_data["c"]; } int main() { MyStruct original_data {42, 3.14f, 'A'}; serialize(original_data, "data.json"); MyStruct restored_data; deserialize(restored_data, "data.json"); std::cout << "Restored data: a = " << restored_data.a << ", b = " << restored_data.b << ", c = " << restored_data.c << std::endl; return 0; } ``` 以上是两种常见的结构体序列化反序列化的方式,具体选择哪种方式取决于你的需求和场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值