it编程 > 编程语言 > C/C++

C++实现数据的序列化和反序列化详解

4人参与 2025-12-09 C/C++

1.简介

在c++应用程序中,经常会涉及到对一些数据进行序列化和反序列化的处理。序列化可以将一个对象转换为一串字节流,这样就可以将其存储在硬盘上或者通过网络传输到其他设备上。而反序列化则是将这些字节流解析成原始的对象。

在qt中,数据的序列化和反序列化可以使用qdatastream类来完成。qdatastream是一个方便的qt类,它可以将基本数据类型、qt数据类型以及用户定义的数据类型都进行序列化和反序列化。在使用qdatastream进行序列化时,需要指定一个qiodevice类的子类(例如qfile或qbuffer)来将数据写入到文件或者内存中。

示例如下:

//【1】结构体
typedef struct _stctrlcmdheader
{
	unsigned char	rctrlobj;				
	unsigned char	rctrlobjid;				
	unsigned char	rctrlcmd;			
	unsigned short	rdatalen;
public:
	_stctrlcmdheader()
	{
		memset(this, 0, sizeof(_stctrlcmdheader));
		rctrlobj = 0x01; 
		rctrlobjid = 0x00;
	}
	friend qdatastream& operator<<(qdatastream& datastream, const _stctrlcmdheader& data)  //序列化
	{
		datastream << data.rctrlobj;
		datastream << data.rctrlobjid;
		datastream << data.rctrlcmd;
		datastream << data.rdatalen;
		return datastream;
	}
	friend qdatastream& operator>>(qdatastream& datastream, _stctrlcmdheader& data)       //反序列化
	{
		datastream >> data.rctrlobj;
		datastream >> data.rctrlobjid;
		datastream >> data.rctrlcmd;
		datastream >> data.rdatalen;
		return datastream;
	}
}stctrlcmdheader;

//【2】序列化到qbytearray
qbytearray data;
qdatastream out(&data, qiodevice::writeonly); // we will serialize the data into the data
datastream.setbyteorder(qdatastream::littleendian); //设置小端对齐
out << qstring("the answer is");   // serialize a string
out << (qint32)42;        // serialize an integer

stctrlcmdheader header;
//...
out << header;     //serialize  struct or class

//【3】反序列化
char szbuffer[1024] = { 0 };
int len  = 0 ;
//...获取网络数据
qbytearray byarray(szbuffer, len);
qdatastream datastream(&byarray, qiodevice::readonly);
datastream.setbyteorder(qdatastream::littleendian);

char sztext[20] = { 0 };
datastream.readrawdata((char*)sztext, 10); //unserialize a string

qint32 number = 0;
datastream >> number ; //unserialize an integer

stctrlcmdheader header;
//...
out >> header;     //unserialize  struct or class

在qt中很方便地可以用到这些类,但是脱离qt,在标准的c++标准库就没有这么方便了,今天就模仿qbytearray和qdatastream类的接口,写了序列化和反序列化的类cbytearray和cdatastream,可以无缝替换qbytearray和qdatastream,下面就来介绍一下cbytearray和cdatastream的设计和实现。

2.cbytearray设计实现

设计要点

1) cbytearray可以理解是纯字节数组,可以存放任意格式的数据,于是后台用 std::basic_string<char>来存储实际的数据。

2)构造函数接收字符数组(char*+len)

3)  构造函数接受右值引用,实现快速的移动语义

4)重载符号=,接受右值引用,实现快速的移动语义

5)根据长度写入和读取原始数据接口

int writerawdata(const char* data, puint32 size) override;
int readrawdata(char* datsa, puint32 size) override;

6) 整个类采用pimpl技法实现

参考:c++中pimpl的惯用法详解

具体实现:

类型定义:datatype.h

#ifndef _data_type_h_
#define _data_type_h_

typedef  char               pint8;
typedef unsigned char       puint8;
typedef  short              pint16;
typedef unsigned short      puint16;
typedef  int                pint32;
typedef unsigned int        puint32;
typedef long long           pint64;
typedef unsigned long long  puint64;
typedef float               pfloat32;
typedef double              pfloat64;

#endif

抽象接口定义如下:idatabuffer.h

#ifndef _idata_buffer_h_
#define _idata_buffer_h_
#include "datatype.h"

class idatabuffer
{
public:
    virtual ~idatabuffer(){}
    virtual int writebytes(const char*, puint32) = 0;
    virtual int readbytes(char*, puint32) = 0;
    virtual int writerawdata(const char*, puint32) = 0;
    virtual int readrawdata(char*, puint32) = 0;
    virtual int bytesavailable() const = 0;
    virtual puint64 skip(puint64 maxsize) = 0;
};

#endif

bytearray.h

#ifndef _byte_array_h_
#define _byte_array_h_
#include <string>
#include <memory>
#include "idatabuffer.h"

class cbytearrayprivate;
class cbytearray : public idatabuffer
{
public:
    explicit cbytearray();
    cbytearray(const cbytearray& other);
    cbytearray(cbytearray&& other);
    cbytearray(const char* buffer, int size);
    cbytearray& operator=(cbytearray&& other);
    operator bool() const;
    virtual ~cbytearray();

public:
    int writerawdata(const char* data, puint32 size) override;
    int readrawdata(char* datsa, puint32 size) override;

    int writebytes(const char*, puint32) override;
    int readbytes(char*, puint32) override;

    const char* data() const;
    int  size() const;

    int bytesavailable() const override;

    puint64 skip(puint64 maxsize) override;

    cbytearray& append(const char* data, puint32 size);
    cbytearray& append(const char* data);

    void  clear();

private:
    std::unique_ptr<cbytearrayprivate> d_ptr;
};

#endif

bytearray.cpp

#include "bytearray.h"
#include <assert.h>
#include <string.h>
//#include <string_view>

class cbytearrayprivate
{
public:
    explicit cbytearrayprivate():m_readpos(0) {}
    cbytearrayprivate(const char* buffer, int size): m_buffer(buffer, size), m_readpos(0) {}

    //这个类,有点类似于golang语言中的切片slice。这就意味着std::string_view本身并不拥有内存本身,
    //它只是一个view,一个窗口,观看内存的窗口
    //std::string_view  m_buffer;
    std::basic_string<char>  m_buffer;
    int  m_readpos; //暂时没用
};

cbytearray::cbytearray()
    : d_ptr(new cbytearrayprivate)
{

}

cbytearray::cbytearray(const cbytearray& other)
    : d_ptr(new cbytearrayprivate)
{
    d_ptr->m_buffer = other.d_ptr->m_buffer;
    d_ptr->m_readpos = other.d_ptr->m_readpos;
}

cbytearray::cbytearray(cbytearray&& other)
    : d_ptr(other.d_ptr.release())
{
    other.d_ptr.reset(new cbytearrayprivate);
}

cbytearray::cbytearray(const char* buffer, int size)
    : d_ptr(new cbytearrayprivate(buffer,size))
{

}

cbytearray::operator bool() const
{
    return (0 ==size());
}

cbytearray& cbytearray::operator=(cbytearray&& other)
{
    if (d_ptr == other.d_ptr){
        return *this;
    }
    cbytearrayprivate* temp = d_ptr.release();
    d_ptr.reset(other.d_ptr.release());
    other.d_ptr.reset(temp);
    return *this;
}

cbytearray::~cbytearray()
{

}

int cbytearray::writerawdata(const char* data, puint32 size)
{
    d_ptr->m_buffer.append(data, size);
    return size;
}
int cbytearray::readrawdata(char* data, puint32 size)
{
    if (d_ptr->m_buffer.copy(data, size, 0) != size){
        assert(0);
        return 0;
    }
    d_ptr->m_buffer.erase(0, size);
    return size;
}
const char* cbytearray::data() const
{
    return d_ptr->m_buffer.data();
}
int  cbytearray::size() const
{
    return d_ptr->m_buffer.size();
}
int cbytearray::bytesavailable() const
{
    return size();
}
puint64 cbytearray::skip(puint64 maxsize)
{
    puint64 nskip = maxsize;
    if (maxsize >= d_ptr->m_buffer.size()){
        nskip = d_ptr->m_buffer.size();
        d_ptr->m_buffer.clear();
        return nskip;
    }else{
        d_ptr->m_buffer.erase(0, maxsize);
        return nskip;
    }
}
int cbytearray::writebytes(const char*, puint32)
{
    assert(0);
    return 0;
}
int cbytearray::readbytes(char*, puint32)
{
    assert(0);
    return 0;
}

cbytearray& cbytearray::append(const char* data, puint32 size)
{
    d_ptr->m_buffer.append(data, size);
    return *this;
}

cbytearray& cbytearray::append(const char* data)
{
    if (!data){
        return *this;
    }

    int len = strlen(data);
    return append(data, len);
}

void  cbytearray::clear() {
    d_ptr->m_buffer.clear();
}

3.cdatastream设计实现

设计要点:

1)  支持所有支持idatabuffer的实现类

2)支持c++的基本数据类型序列化和反序列化,重载operator<<和operator>>

3)  支持自定义数据类型的序列化和反序列化,如结构体或类

4)反序列化时,可以忽略指定的字节

5)反序列化可变字节长度时,可以获取未序列化的数据长度,用户获取长度申请空间并反序列化到指定的内存中

具体实现:

shortwavetoolfun.h

#ifndef _short_wave_tool_func_h_
#define _short_wave_tool_func_h_
#include <string>
#include "datatype.h"
#include <functional>
#include <memory>
#include <string.h>

#define work_msg_id(link, type, sub)  ( (link<<16) | (type<<8) | sub )
#define get_work_type(id)   ((id >> 8) & 0xff)
#define get_work_cmd(id)    (id & 0xff)
#define get_work_b_type(id)   ((id >> 16) & 0xff)

#define member_off_set(type, member) ((size_t) &(static_cast<type*>(0))->member)

#define container_of(ptr, type, member) ({ \
       const typeof(((type*)0)->member) * __mptr = (ptr);   \
       (type*)((char*)__mptr - member_off_set(type,member)); })

template <typename t, class = typename std::enable_if<sizeof(t)%2==0>::type>
t  reversenumber(t value)
{
    static_assert(sizeof(t)%2==0, "t type is invalid!!!");

    union x{
        t val1;
        char val2[sizeof(t)];
    };

    int i = 0;
    int size = sizeof(t);
    char temp = 0;
    x x;
    x.val1 = value;

    for (i = 0; i < sizeof(t)/2; i++){
        temp = x.val2[i];
        x.val2[i] = x.val2[size-i-1];
        x.val2[size-i-1] = temp;
    }
    return x.val1;
}

template <typename t, class = typename std::enable_if<sizeof(t)<=sizeof(puint64)>::type>
puint64  transttopuint64(t value)
{
    static_assert(sizeof(t)<=sizeof(puint64), "t type is invalid!!!");

    union x{
        t val1;
        puint64 val2;
    };
    x x;
    x.val2 = 0; //将高位置0
    x.val1 = value;
    return x.val2;
}

template <typename t, class = typename std::enable_if<sizeof(t)<=sizeof(puint64)>::type>
t  transpuint64tot(puint64 value)
{
    static_assert(sizeof(t)<=sizeof(puint64), "t type is invalid!!!");

    union x{
        t val1;
        puint64 val2;
    };
    x x;
    x.val2 = value;
    return x.val1;
}

#endif

datastream.h

#ifndef _data_stream_h_
#define _data_stream_h_
#include "datatype.h"

class idatabuffer;
class cbytearray;
class cdatastream
{
public:
    explicit cdatastream(idatabuffer* pbuffer);
    ~cdatastream();

    bool bigendian() const;
    void setbigendian(bool newbigendian);

    cdatastream& operator<<(bool i);
    cdatastream& operator<<(pint8 i);
    cdatastream& operator<<(puint8 i);
    cdatastream& operator<<(pint16 i);
    cdatastream& operator<<(puint16 i);
    cdatastream& operator<<(pint32 i);
    cdatastream& operator<<(puint32 i);
    cdatastream& operator<<(pint64 i);
    cdatastream& operator<<(puint64 i);
    cdatastream& operator<<(pfloat32 i);
    cdatastream& operator<<(pfloat64 i);
    cdatastream& operator<<(const char* i);
    cdatastream& operator<<(const cbytearray& data);
    template <typename t>
    cdatastream& operator<<(const t& t)
    {
        (*this) << t;
        return *this;
    }

    cdatastream& operator>>(bool& i);
    cdatastream& operator>>(pint8& i);
    cdatastream& operator>>(puint8& i);
    cdatastream& operator>>(pint16& i);
    cdatastream& operator>>(puint16& i);
    cdatastream& operator>>(pint32& i);
    cdatastream& operator>>(puint32& i);
    cdatastream& operator>>(pint64& i);
    cdatastream& operator>>(puint64& i);
    cdatastream& operator>>(pfloat32& i);
    cdatastream& operator>>(pfloat64& i);
    cdatastream& operator>>(char*& i);
    cdatastream& operator>>(cbytearray& data);
    template <typename t>
    cdatastream& operator>>(t& t)
    {
        (*this) >> t;
        return *this;
    }

    int writerawdata(const char* data, puint32 size);
    int readrawdata(char* data, puint32 size);

    bool  atend() const;
    int   bytesavailable() const;

    int skiprawdata(int len);

private:
    bool  m_bigendian;
    idatabuffer* m_buffer;
};

#endif

datastream.cpp

#include "idatabuffer.h"
#include "datastream.h"
#include "shortwavetoolfun.h"
#include <string.h>
#include "bytearray.h"

cdatastream::cdatastream(idatabuffer* pbuffer)
    : m_bigendian(false)
    , m_buffer(pbuffer)
{

}

cdatastream::~cdatastream()
{

}

bool cdatastream::bigendian() const
{
    return m_bigendian;
}

void cdatastream::setbigendian(bool newbigendian)
{
    m_bigendian = newbigendian;
}
cdatastream& cdatastream::operator<<(bool i)
{
    *this << (pint8)i;
    return *this;
}
cdatastream& cdatastream::operator<<(pint8 i)
{
    pint8 ch = i;
    m_buffer->writerawdata(&ch, sizeof(ch));
    return *this;
}
cdatastream& cdatastream::operator<<(puint8 i)
{
    pint8 ch = i;
    m_buffer->writerawdata(&ch, sizeof(ch));
    return *this;
}
cdatastream& cdatastream::operator<<(pint16 i)
{
    pint16 temp = i;
    if (m_bigendian){
        temp = reversenumber(i);
    }
    m_buffer->writerawdata((char*)&temp, sizeof(temp));
    return *this;
}
cdatastream& cdatastream::operator<<(puint16 i)
{
    puint16 temp = i;
    if (m_bigendian){
        temp = reversenumber(i);
    }
    m_buffer->writerawdata((char*)&temp, sizeof(temp));
    return *this;
}
cdatastream& cdatastream::operator<<(pint32 i)
{
    pint32 temp = i;
    if (m_bigendian){
        temp = reversenumber(i);
    }
    m_buffer->writerawdata((char*)&temp, sizeof(temp));
    return *this;
}
cdatastream& cdatastream::operator<<(puint32 i)
{
    puint32 temp = i;
    if (m_bigendian){
        temp = reversenumber(i);
    }
    m_buffer->writerawdata((char*)&temp, sizeof(temp));
    return *this;
}
cdatastream& cdatastream::operator<<(pint64 i)
{
    pint64 temp = i;
    if (m_bigendian){
        temp = reversenumber(i);
    }
    m_buffer->writerawdata((char*)&temp, sizeof(temp));
    return *this;
}
cdatastream& cdatastream::operator<<(puint64 i)
{
    puint64 temp = i;
    if (m_bigendian){
        temp = reversenumber(i);
    }
    m_buffer->writerawdata((char*)&temp, sizeof(temp));
    return *this;
}
cdatastream& cdatastream::operator<<(pfloat32 i)
{
    pfloat32 temp = i;
    if (m_bigendian){
        union {
            float val1;
            puint32 val2;
        } x;
        x.val1 = i;
        x.val2 = reversenumber(x.val2);
        m_buffer->writerawdata((char*)&x.val2, sizeof(i));
    }else{
        m_buffer->writerawdata((char*)&temp, sizeof(i));
    }
    return *this;
}
cdatastream& cdatastream::operator<<(pfloat64 i)
{
    pfloat64 temp = i;
    if (m_bigendian){
        union {
            double val1;
            puint64 val2;
        } x;
        x.val1 = i;
        x.val2 = reversenumber(x.val2);
        m_buffer->writerawdata((char*)&x.val2, sizeof(i));
    }else{
        m_buffer->writerawdata((char*)&temp, sizeof(i));
    }
    return *this;
}

cdatastream& cdatastream::operator<<(const char* i)
{
    if (!i) {
        *this << (puint32)0;
        return *this;
    }
    int len = strlen(i) + 1;
    *this << (puint32)len;
    m_buffer->writerawdata(i, len);
    return *this;
}
cdatastream& cdatastream::operator>>(bool& i)
{
    pint8 v;
    *this >> v;
    i = !!v;
    return *this;
}
cdatastream& cdatastream::operator>>(pint8& i)
{
    m_buffer->readrawdata(&i, 1);
    return *this;
}
cdatastream& cdatastream::operator>>(puint8& i)
{
    m_buffer->readrawdata(reinterpret_cast<char *>(&i), sizeof(i));
    return *this;
}
cdatastream& cdatastream::operator>>(pint16& i)
{
    m_buffer->readrawdata((char *)(&i), sizeof(i));
    if (m_bigendian){
        i = reversenumber(i);
    }
    return *this;
}
cdatastream& cdatastream::operator>>(puint16& i)
{
    m_buffer->readrawdata(reinterpret_cast<char *>(&i), sizeof(i));
    if (m_bigendian){
        i = reversenumber(i);
    }
    return *this;
}
cdatastream& cdatastream::operator>>(pint32& i)
{
    m_buffer->readrawdata((char *)(&i), sizeof(i));
    return *this;
}
cdatastream& cdatastream::operator>>(puint32& i)
{
    m_buffer->readrawdata(reinterpret_cast<char *>(&i), sizeof(i));
    if (m_bigendian){
        i = reversenumber(i);
    }
    return *this;
}
cdatastream& cdatastream::operator>>(pint64& i)
{
    m_buffer->readrawdata((char *)(&i), sizeof(i));
    if (m_bigendian){
        i = reversenumber(i);
    }
    return *this;
}
cdatastream& cdatastream::operator>>(puint64& i)
{
    m_buffer->readrawdata(reinterpret_cast<char *>(&i), sizeof(i));
    if (m_bigendian){
        i = reversenumber(i);
    }
    return *this;
}
cdatastream& cdatastream::operator>>(pfloat32& i)
{
    i = 0.0f;
    m_buffer->readrawdata(reinterpret_cast<char *>(&i), sizeof(i));
    if (m_bigendian){
        union {
            float val1;
            puint32 val2;
        } x;
        x.val2 = reversenumber(*reinterpret_cast<puint32 *>(&i));
        i = x.val1;
    }
    return *this;
}
cdatastream& cdatastream::operator>>(pfloat64& i)
{
    i = 0.0f;
    m_buffer->readrawdata(reinterpret_cast<char *>(&i), sizeof(i));
    if (m_bigendian){
        union {
            double val1;
            puint64 val2;
        } x;
        x.val2 = reversenumber(*reinterpret_cast<puint64 *>(&i));
        i = x.val1;
    }
    return *this;
}
cdatastream& cdatastream::operator>>(char*& i)
{
    puint32 len = 0;
    *this >> len;
    if (len == 0)
        return *this;

    m_buffer->readrawdata(i, len);

    i[len] = '\0';
    return *this;
}

cdatastream& cdatastream::operator<<(const cbytearray& data)
{
    m_buffer->writerawdata(data.data(), data.size());
    return *this;
}

cdatastream& cdatastream::operator>>(cbytearray& data)
{
    puint32 len = 0;
    *this >> len;
    if (len == 0)
        return *this;

    std::unique_ptr<char[]> pbuffer(new char[len]);
    m_buffer->readrawdata(pbuffer.get(), len);

    data.append(pbuffer.get(), len);
    return *this;
}

int cdatastream::writerawdata(const char* data, puint32 size)
{
    return m_buffer->writerawdata(data, size);
}
int cdatastream::readrawdata(char* data, puint32 size)
{
    return m_buffer->readrawdata(data, size);
}

bool  cdatastream::atend() const
{
    return m_buffer->bytesavailable() <= 0;
}

int   cdatastream::bytesavailable() const
{
    return m_buffer->bytesavailable();
}

int cdatastream::skiprawdata(int len)
{
    return m_buffer->skip(len);
}

4.使用实例

结构体定义:

//传输的整个报文
typedef  struct  _stshortwavepacket
{
	//共用
	puint32            contextid;
	void* pappdata; //正文数据

	puint16            frameindex;
	stobjectflag        srcobjmark; 
	stobjectflag        destobjmark; 
	puint8             ismustresponse; 
	puint8			   responsestatus; 

public:
	_stshortwavepacket() {
	}

}stshortwavepacket;

using stchannelworkparamdata = kvdata<puint16, puint64>;
using channelworkparamdatacontainer = std::vector<std::shared_ptr<stchannelworkparamdata>>;

//结构体定义
template <bool ishavetype>
struct stchannelworkparam
{
	puint16    signaltype; //类别
    channelworkparamdatacontainer workparamdata;

public:
	stchannelworkparam() {
		signaltype = 0;
	}
	stchannelworkparam(const stchannelworkparam&) = delete;
	stchannelworkparam& operator=(const stchannelworkparam&) = delete;
	puint16  getdatasize() {
		puint16 size = ishavetype ? 2 : 0;
		for (auto& it : workparamdata) {
			size += it->getsize();
		}
		return size;
	}
	std::string  tostring() const {
		return std::string("type:") + numconvertstring(signaltype);
	}
	friend cdatastream& operator<<(cdatastream& datastream, const stchannelworkparam& data)
	{
		if (ishavetype) {
			datastream << data.signaltype;
		}
		for (auto& it : data.workparamdata) {
			it->serializedata(datastream);
		}
		return datastream;
	}
	friend cdatastream& operator>>(cdatastream& datastream, stchannelworkparam& data)
	{
		if (ishavetype) {
			datastream >> data.signaltype;
		}
		while (datastream.bytesavailable() > 6) {
			std::shared_ptr<stchannelworkparamdata> pdata = std::make_shared<stchannelworkparamdata>();
			pdata->unserializedata(datastream);
			data.workparamdata.push_back(pdata);
		}
		return datastream;
	}
};
using  stsendchannelworkparam = stchannelworkparam<true>;
using  stuploadregisterstatus = stchannelworkparam<false>;

序列化数据:

template <puint32 type, typename sttype>
int  encodedata(char* data, int& len, const stshortwavepacket* ppacket)
{
    puint16 crccode = 0;
    cbytearray  byarray;
    cdatastream datastream(&byarray);

    sttype* pmessage = reinterpret_cast<sttype*>(ppacket->pappdata);
    if (pmessage == nullptr)
        return 1;
    //[1]
    datastream.writerawdata((const char*)m_startflag, sizeof(m_startflag));
    datastream << getframeno();
    datastream << ppacket->srcobjmark.data;
    datastream << ppacket->destobjmark.data;
    datastream << ppacket->ismustresponse;
    datastream << (puint8)get_work_type(type);
    datastream << pmessage->getdatasize();

    //[2]
    datastream << (*pmessage);

    crccode = getcrccode(byarray.data(), byarray.size());
    datastream << crccode;
    datastream.writerawdata((const char*)m_endflag, sizeof(m_endflag));
    len = byarray.size();
    memcpy(data, byarray.data(), len);
    return 0;
}

接收到网络数据,反序列化数据:

template <puint32 type, typename sttype>
int  parsedata(const char* data, int len, stshortwavepacket* ppacket)
{
    cbytearray byarray(data, len);
    cdatastream datastream(&byarray);

    puint16 length = 0;

    ppacket->contextid = type;
    datastream.skiprawdata(4);   //忽略4个字节
    datastream >> ppacket->frameindex;
    m_frameno = ppacket->frameindex;
    datastream >> ppacket->srcobjmark.data;
    datastream >> ppacket->destobjmark.data;
    datastream >> ppacket->ismustresponse; //是否应答
    datastream.skiprawdata(1); //忽略功能指令,1个字节
    datastream >> length;
    if (length != sttype::getdatasize())
            return -1;

    std::unique_ptr<sttype> pappdata = std::make_unique<sttype>();
    datastream >> (*pappdata);

    ppacket->pappdata = pappdata.release();
    return 0;
}

到此这篇关于c++实现数据的序列化和反序列化详解的文章就介绍到这了,更多相关c++数据序列化和反序列化内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

RabbitMQ从入门到原理再到实战应用

12-08

C++多态详解之从静态多态到动态多态

12-08

C++ move 的作用详解及陷阱最佳实践

12-05

用c++写控制台贪吃蛇游戏完整步骤

12-05

C++构造函数中explicit详解

12-03

详解C++ 存储二进制数据容器的几种方法

12-03

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论