C++实现结构体序列化和反序列化

1.什么是结构体序列化和反序列化

        序列化:就是将对象转化成字节序列的过程。

        反序列化:就是讲字节序列转化成对象的过程。

        对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。

        简单的来说结构体序列化就是将这个结构体里的数据转成一段通用的数据,另一个同类型结构体拿到这段数据后可以反序列化拿到每个结构体成员的值。

#include <iostream>
#include <memory>
using namespace std;
struct A
{
    int a;
    int b;
};
void test(const char* s, int len)
{
    A b;
    memcpy((char *)&b, s, len);
    cout<<b.a<<" "<<b.b;
}
int main(int argc, char *argv[])
{
    A a;
    a.a = 20;
    a.b = 30;
    test((const char *)&a, sizeof(a));
    return 0;
}

        上述代码中将a的数据拷贝到b,当然像A这样简单的结构体直接=就能进行拷贝了。但是如果在程序中多处需要传递该结构体,就需要考虑传参过程中使用更通用的数据类型了。在上述代码中可以把数据先拷贝到c++的String容器中,然后再进行传递,在使用时再将String中的数据拷贝给结构体。这样就完成了一次序列化和反序列化。

2.为什么要结构体序列化和反序列化

2.1数据持久化

        通常我们创建的结构体实例的数据都保存再堆区或者栈区,栈区数据程序结束后会被自动释放,而为了避免内存泄漏,我们必须要手动释放掉申请的堆区内存。那么如果想要在新的程序中得到之前程序的结构体数据,就需要把数据序列化成字节序列,然后将它存入硬盘,再新的程序中用该字节序列就能反序列化出一个一摸一样的结构体实例。

2.2避免内存对齐导致数据拷贝失败

#include <iostream>
using namespace std;
#pragma pack (4)  //指定按多少字节进行内存对齐
struct A
{
    int a;
    double b;
    int c;

};
int main()
{
    cout<<sizeof(A);
    return 0;
}

指定变量强制按4字节对齐,则A的大小是16

#include <iostream>
using namespace std;
#pragma pack (8)  //指定按多少字节进行内存对齐
struct A
{
    int a;
    double b;
    int c;

};
int main(int argc, char *argv[])
{
    cout<<sizeof(A);
    return 0;
}

指定变量强制按8字节对齐,则A的大小变为24。

          为什么会出现这样的现象呢?这里面就涉及到内存对齐的问题。 本文不会重点介绍内存对齐原理,具体请看以下文章。 一文轻松理解内存对齐 - 知乎 (zhihu.com)

        如果在网络传输中,程序所在环境的内存对齐规则不一致的化,就会出现计算出的结构体所占内存大小不一致的情况。这样直接对整个结构体进行内存拷贝就会导致多拷贝或少拷贝几个字节,这实际上是很危险的,很容易导致内存崩溃。

        解决办法可以参考网络传输中常见的Json序列化和XM序列化,这两者都是将对象序列化成字符串然后进行传输,在反序列化时再按照一定规则去依次解析出相应数据。虽然转成字符串有很好的可读性,但是确大大的增加了传输数据的长度。实际上我们自己可以将每一个结构体成员变量依次拷贝到一段缓存去,然后再转换成其他通用数据结构进行传输。

#include <iostream>
#include <memory>
#include <stdlib.h>
#include <cstdio>
#include <cstring>
#include<string>
using namespace std;

class CSeArchive
{
public:
    CSeArchive(int a_buf_size = 1024 * 5 )
    {
        buf = new char[a_buf_size];
        memset(buf,0x00,a_buf_size);
        bufptr = buf;
        buf_size = a_buf_size;
        data_size = 0;

    }
    CSeArchive( const char *a_buf, int a_buf_size)
    {
        buf = new char[1024 * 5];
        memcpy(buf,a_buf, a_buf_size);
        bufptr = buf;
        buf_size = 1024 * 5;
        data_size = a_buf_size;
    }
    ~CSeArchive()
    {
        if(sizeof(buf) != 0)
        {
            delete[] buf;
            buf = nullptr;
            bufptr = nullptr;
        }

    }
    char* get_buf(std::string &strData) //得到缓存指针
    {
        strData = std::string(buf,data_size);
        return buf;
    }

    inline CSeArchive& operator<<(int i) //往缓存存中写入int数据
    {
        memcpy(bufptr,(char*)&i,sizeof(i));
        bufptr += sizeof(i);
        data_size += sizeof(i);
        return *this;
    }
    inline CSeArchive& operator<<(double i) //往缓存中double数据
    {
        memcpy(bufptr,(char*)&i,sizeof(i));
        bufptr += sizeof(i);
        data_size += sizeof(i);
        return *this;
    }
    inline CSeArchive& operator>>(int& i)   //从缓存中读出int数据
    {
        memcpy((char *)&i, bufptr, sizeof(i));
        bufptr += sizeof(i);
        return *this;
    };

    inline CSeArchive& operator>>(double &i)    //从缓存中读出double数据
    {
        memcpy((char *)&i, bufptr, sizeof(i));
        bufptr += sizeof(i);
        return *this;
    }
private:
    char *buf, *bufptr;
    int buf_size, data_size;
};

struct A
{
    int a;
    double b;
    int c;
    string Serialize()
    {
        CSeArchive cs;
        cs<<a<<b<<c;
        string ret;
        cs.get_buf(ret);
        return ret;
    }

    void UnSerialize(string s)
    {
        CSeArchive cs(s.c_str(),s.size());
        cs>>a>>b>>c;
    }
};

int main()
{
    A a;
    a.a = 10;
    a.b = 20.5;
    a.c = 30;
    A b;
    string s = a.Serialize();
    b.UnSerialize(s);
    cout<<b.a<<" "<<b.b<<" "<<b.c;
    return 0;
}

2.3进行复杂的结构体的拷贝

        当结构体中含有对象或容器时是无法直接进行内存拷贝的,这时我们就可以通过序列化和反序列化的手段进行拷贝。实现方法也很简单只需重载<<运算符将数据写入缓存,重载>>运算符将数据读出即可。

        2.3.1支持数组

template<typename T, size_t N>    //写入数组
inline CSeArchive& operator<<(T(&m)[N])

{
        return wr_array(m, N);
}

template<typename T>

inline CSeArchive& wr_array(T arData[], uint32 uCount) //写数组
{
        for(int i = 0;i < uCount; i++)
        {
            memcpy(bufptr,arData+i,sizeof(arData[0]));
            bufptr += sizeof(arData[0]);
            data_size += sizeof(arData[0]);
        }
        return *this;
}  

 template<typename T, size_t N>           //读出数组

 inline CSeArchive& operator>>(T(&m)[N]) {return rd_array(m, N);}

 template<typename T>
 inline CSeArchive& rd_array(T arData[], uint32 uCount)

 {
        for(int i = 0;i < uCount; i++)
        {
            memcpy(arData + i,bufptr, sizeof(arData[0]));
            bufptr += sizeof(arData[0]);
        }
        return *this;

}

        2.3.2支持string容器

int CSeArchive::getStrlen(char *buf)        //找到当前指针到下一个'\0'之间的距离
{
        if(buf == nullptr)
           return 0;
        int count = 0;
        while(*buf != '\0' && buf != nullptr && count < data_size)
        {
            buf++;
            count++;
        }
        return count;
}

CSeArchive& operator <<(const std::string& strValue) //重载运算符(写入string)

 {
        memcpy(bufptr,strValue.data(),strValue.size());
        bufptr += strValue.size()+1;
        data_size += strValue.size()+1;
        return *this;
}

CSeArchive& operator >> (std::string& strValue) //重载运算符(读出string)

 {

        int len = getStrlen(bufptr);

        strValue = std::string(bufptr,len);
        bufptr += len + 1;
        return *this;
}

        2.3.3支持vector容器(其他容器可举一反三)

//写入vector<string>类型

inline CSeArchive& operator<<(vector<string> &vtData) {return  wr_array(vtData);} 

inline CSeArchive& wr_array(vector<string> &vtData)
{
        (*this)<<(int)vtData.size();

        for(int i = 0;i < vtData.size();i++)
             (*this)<<vtData[i];
        return *this;
}

//读出vector<string>类型 

inline CSeArchive& operator>>(vector<string> &vtData) {return rd_array(vtData);}

 inline CSeArchive& rd_array(vector<string> &vtData)
 {
        int size = 0;
        (*this)>>size;
        for(int i = 0;i < size;i++)
        {
            string data = "";
            (*this)>> data;
            vtData.push_back(data);
        }
        return *this;
 }

        2.3.4支持对象

  template<typename T, std::enable_if_t < !std::is_enum<T>::value > * = nullptr >
    inline CSeArchive& operator<<(T tParam)   //传入对象
    {
        std::string tString = tParam.Serialize();       //对象进行序列化并存入缓存
        wr_array((char*)tString.c_str(), tString.length());
        return *this;
    }
    template<typename T, std::enable_if_t < !std::is_enum<T>::value > * = nullptr >
    inline CSeArchive& operator>>(T& tParam)  //传出对象(初始化)
    {
        tParam.T::UnSerialize(*this);
        return *this;
    }

3.具体实现

        具体实现代码请自取。https://gitee.com/Lanlvzzz/serialize.git

        

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
结构体序列化反序列化是将结构体数据转换为字节流,以便在网络传输或存储中使用,然后再将字节流还原为原始结构体数据的过程。 在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; } ``` 以上是两种常见的结构体序列化反序列化的方式,具体选择哪种方式取决于你的需求和场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值