目录
文件操作相关类
序列化机制相关类
序列化机制使用
序列化机制执行过程
序列化类对象
CFile:文件操作类,封装了关于文件读写等操作,常见的方法:
代码如下:
#include #includeusing namespace std; void File( ){ CFile file; // 没有文件就创建,然后可读可写 file.Open( "E:/MFC/Day07/file.txt", CFile::modeCreate|CFile::modeReadWrite ); char str[] = "hello file"; file.Write( str, strlen(str) ); file.SeekToBegin( );// 设置文件读写位置 char buf[256] = { 0 }; long nLen = file.Read( buf, 255 ); // 返回值是实际读到的数据 cout << buf << ' ' << nLen << endl; file.Close(); } int main(){ File(); return 0; }
序列化作用:以二进制流形式读写硬盘文件,效率很高。
先把数据放到缓冲区,再放到硬盘上
序列化:往硬盘上写数据;
反序列化:从硬盘上读取数据。
代码如下:
#include #includeusing namespace std; void Store( ){//序列化(存储、写)数据 CFile file; file.Open( "E:/MFC/Day07/serial.txt", CFile::modeCreate | CFile::modeWrite ); CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区。 long age = 18; ar << age;//将18保存当前指向的位置,并向后移动当前指向,相应字节数。 float score = 88.5; ar << score;//将88.5保存当前指向的位置,并向后移动当前指向,相应字节数。 CString name = "zhangsan"; ar << name; ar.Close( ); file.Close( ); } void Load( ){//反序列化(加载/读) CFile file; file.Open( "E:/MFC/day07/serial.txt", CFile::modeRead ); CArchive ar( &file, CArchive::load, 4096 );//维护一个buff,大小4096字节 long age; ar >> age;//当反序列化第一个数据时候,内部将文件中所有数据读入ar维护的buff中 float score; ar >> score;//当反序列化后续数据时候,不需要到硬盘文件中读取,直接到ar维护的buff中读取 CString name; ar >> name;//当反序列化后续数据时候,不需要到硬盘文件中读取,直接到ar维护的buff中读取 ar.Close( ); file.Close( ); cout << age << ' ' << score << ' ' << name << endl; } int main(){ Store( ); Load( ); return 0; }
问题:数据一共16字节,为啥是17个字节
原因:首先排除\0,如果在内存中会有这个符号,但是再硬盘不会有。多出的一个字节是描述字符串大小的。前四个字节是Int,后四个字节是float,中间一个字节是字符串长度为8
Windows记事本解析文件是按照字符解析,所以前面的数字乱码了。
数据结构:
class CArchive { enum Mode; // {store = 0,load = 1……} BOOL m_nMode; // 访问方式 int m_nBufSize; // buff的大小 CFile* m_pFile; // 操作的文件对象 BYTE* m_lpBufCur; // 当前指向 BYTE* m_lpBufMax; // 终止指向 BYTE* m_lpBufStart; // 开始指向 }
CArchive ar(&file, CArchive::store, 4096); 构造函数伪代码如下:
CFile file; file.Open( "E:/MFC/Day07/serial.txt", CFile::modeCreate | CFile::modeWrite ); CArchive ar(&file, CArchive::store, 4096) === CArchive::CArchive(&file,0, 4096) { m_nMode = CArchive::store; // 0 m_pFile = &file;//“E:/....serial.txt” m_nBufSize = 4096; m_lpBufStart = new BYTE[m_nBufSize]; // 开辟一块堆内存,指向首地址 m_lpBufMax = m_lpBufStart + 4096; m_lpBufCur = m_lpBufStart; }
初始时
如何把数据存入缓冲区,伪代码如下:
long age = 18; ar << age === CArchive::operator<<(age)//函数内部this为&ar { if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) { Flush(); } *m_lpBufCur = age; m_lpBufCur += sizeof(LONG); } // 把18存入缓冲区,并且指针后移4个字节 float score = 88.5; ar << score === CArchive::operator<<(score)//函数内部this为&ar { if (m_lpBufCur + sizeof(float) > m_lpBufMax) { Flush(); } *m_lpBufCur = score;//88.5 m_lpBufCur += sizeof(float); } CString name = "zhangsan"; ar << name === CArchive::operator<<(name)//函数内部this为&ar { AfxWriteStringLength(ar, 8 ) { ar<<(unsigned char)nLength;//8 } Write(name, 8)//函数内部this为&ar { memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), name, 8); m_lpBufCur += 8; } }
序列化三个数据后的缓冲区:
关闭文档类对象,释放缓冲区伪代码:把当前数据导入硬盘上,再重置当前指向
ar.Close( )//函数内部this为&ar { Flush()//函数内部this为&ar { &file->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart)); // 往硬盘写数据 m_lpBufCur = m_lpBufStart;//重置当前指向 } }
如何需要写入4个字节,但是只剩3个字节的空间,怎么办?
会调用flush(),把缓冲区的数据写入到硬盘空间,再重置当前指针指向。再重新写入数据,相当于从头覆盖写入。
序列化执行过程总结:
反序列化执行过程总结:
序列化类对象的使用:
当类具备以上四个条件时,类对象就可以序列化到文件保存了。
序列化类对象,除了要把类对象成员变量还有类对象的信息,类对象大小和版本等。
完整测试代码如下:
#include #includeusing namespace std; class CMyDoc : public CDocument{ DECLARE_SERIAL( CMyDoc ) public: CMyDoc(int age=0, float score=0.0, CString name=""):m_age(age),m_score(score),m_name(name){} int m_age; float m_score; CString m_name; virtual void Serialize( CArchive& ar ); }; IMPLEMENT_SERIAL( CMyDoc, CDocument, 1 ) void CMyDoc::Serialize( CArchive& ar ){ if( ar.IsStoring() ){ ar << this->m_age << this->m_score << this->m_name; //序列化基本类型变量 }else{ ar >> m_age >> m_score >> m_name;//反序列化基本类型变量 } } void Store( ){//序列化(存储、写)数据 CFile file; file.Open("E:/MFC/Day08/serial.txt", CFile::modeCreate|CFile::modeWrite); CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区。 CMyDoc data(18, 88.5, "zhangsan"); ar << &data; //序列化对象,就是将对象的各个成员变量序列化。 ar.Close( ); file.Close( ); } void Load( ){//反序列化(加载/读) CFile file; file.Open( "E:/MFC/day08/serial.txt", CFile::modeRead ); CArchive ar( &file, CArchive::load, 4096 );//维护一个buff,大小4096字节 CMyDoc* pdata = NULL; ar >> pdata; ar.Close( ); file.Close( ); cout << pdata->m_age << ' ' << pdata->m_score << ' ' << pdata->m_name << endl; } int main(){ Store( ); Load( ); return 0; }
测试结果:CMyDoc后面是成员变量的数据,前面是类对象信息
把 DECLARE_SERIAL( CMyDoc ) 宏展开
再进一步展开宏 _DECLARE_DYNCREATE
更进一步进入 _DECLARE_DYNAMIC
所以可以认为就是动态创建机制宏
实现宏展开
相当于动态创建机制加上一个操作符重载
下断点,分析一下类对象序列化执行
伪代码如下:
CFile file; file.Open("E:/MFC/Day08/serial.txt", CFile::modeCreate|CFile::modeWrite); CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区。 CMyDoc data(18, 88.5, "zhangsan"); ar << &data === operator<<(ar, const &data) { ar.WriteObject(&data)//函数内部this为&ar { CRuntimeClass* pClassRef = &data->GetRuntimeClass();//文档类静态变量 WriteClass(pClassRef);//将类的相关信息(类名/类大小/类版本)存入ar维护的buff中 (&data)->Serialize(ar)//函数内部this为&data { ar << this->m_age << this->m_score << this->m_name; //序列化基本类型变量 } } } CFile file; file.Open( "E:/MFC/day08/serial.txt", CFile::modeRead ); CArchive ar( &file, CArchive::load, 4096 );//维护一个buff,大小4096字节 CMyDoc* pdata = NULL;//???????????? ar >> pdata === operator>>(ar, pdata) { pdata = ar.ReadObject(RUNTIME_CLASS(CMyDoc))//函数内部this为&ar { CRuntimeClass* pClassRef = ReadClass(RUNTIME_CLASS(CMyDoc),...); //从文件读取 类的相关信息,和 RUNTIME_CLASS(CMyDoc)中信息进行比对, //如果相同返回RUNTIME_CLASS(CMyDoc),如果不同返回NULL CObject*pOb = RUNTIME_CLASS(CMyDoc)->CreateObject(); //动态创建CMyDoc类的对象,并返回对象地址 pOb->Serialize(ar)//函数内部this为刚刚创建的CMyDoc类对象(pOb) { ar >> m_age >> m_score >> m_name;//反序列化基本类型变量 } return pOb; } }