MFC 序列化机制

目录

文件操作相关类

序列化机制相关类

序列化机制使用

序列化机制执行过程

序列化类对象


文件操作相关类

CFile:文件操作类,封装了关于文件读写等操作,常见的方法:

  • CFile::Open:打开或者创建文件
  • CFile::Write/Read:写/读文件
  • CFile::Close:关闭文件
  • CFile::SeekToBegin/SeekToEnd/Seek:从 开始/结束/任意 位置设置文件读写位置

代码如下:

#include <afxwin.h>
#include <iostream>
using 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;
}

序列化机制相关类

序列化作用:以二进制流形式读写硬盘文件,效率很高。

  • CFile:文件操作类,完成硬盘文件的读写操作
  • CArchive:归档类,完成内存数据的读写操作,维护了一个缓冲区

先把数据放到缓冲区,再放到硬盘上

序列化机制使用

序列化:往硬盘上写数据;

  1. 创建或打开文件  CFile::Open
  2. 定义归档类对象  CArchive ar;
  3. 数据序列化(存储/写)  ar<<数据   把数据读入缓冲区
  4. 关闭归档类对象,释放缓冲区
  5. 关闭文件

反序列化:从硬盘上读取数据。

  1. 打开文件  CFile::Open
  2. 定义归档类  CArchive  ar;
  3. 数据反序列化(加载/读)  ar>>变量
  4. 关闭文档类对象   ar.close()
  5. 关闭文件    CFile::Close()

代码如下:

#include <afxwin.h>
#include <iostream>
using 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; // 0m_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(),把缓冲区的数据写入到硬盘空间,再重置当前指针指向。再重新写入数据,相当于从头覆盖写入。

序列化执行过程总结:

  1. ar对象维护一个缓冲区
  2. 将各个数据依次序列化(存储)到ar对象维护的缓冲区中,并将m_lpBufCur的指针指向移动相应字节
  3. 如果ar维护的缓冲区不足,则将ar维护的缓冲区的数据写入硬盘文件,并重置m_lpBufCur为开始指向
  4. 当关闭ar对象时,将ar对象维护的缓冲区数据写入硬盘文件,并释放ar对象维护的缓冲区。

反序列化执行过程总结:

  1. ar对象维护一个缓冲区
  2. 当反序列化第一个数据时,将文件数据全部读取到ar维护的缓冲区,并将第一个数据反序列化到第一个变量,并将m_lpBufCur移动相应的字节数
  3. 依次反序列化每个数据到变量中
  4. 当关闭ar对象时,释放ar维护的缓冲区

序列化类对象

序列化类对象的使用:

  • 类必须派生自CObject
  • 类内必须添加声明宏  DECLARE_SERIAL(theClass)
  • 类外必须添加实现宏   IMPLEMENT_SERIAL(theClass,baseClass,1)
  • 类必须重写虚函数 Serialize

当类具备以上四个条件时,类对象就可以序列化到文件保存了。

序列化类对象,除了要把类对象成员变量还有类对象的信息,类对象大小和版本等。

完整测试代码如下:

#include <afxwin.h>
#include <iostream>
using 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),如果不同返回NULLCObject*pOb = RUNTIME_CLASS(CMyDoc)->CreateObject();//动态创建CMyDoc类的对象,并返回对象地址pOb->Serialize(ar)//函数内部this为刚刚创建的CMyDoc类对象(pOb){ar >> m_age >> m_score >> m_name;//反序列化基本类型变量}return pOb;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/415629.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++学习笔记-STL案例-机房预约系统4-管理员模块

前言 衔接上一篇“c学习笔记-STL案例-机房预约系统3-登录模块”&#xff0c;本文主要设计管理员模块&#xff0c;从管理员登录和注销、添加账号、显示账号、查看机房、清空预约五个功能进行分析和实现。 目录 7 管理员模块 7.1 管理员登录和注销 7.1.1 构造函数 ​编辑7.1.2…

MySQL UPDATE 更新

昨天介绍了 MySQL 数据库 WHERE 子句的用法&#xff0c;今天来讲解下UPDATE 更新。 语法 在 MySQL 使用过程中&#xff0c;我们经常需要修改数据&#xff0c;这时就要用到UPDATE 命令。 老规矩&#xff0c;先介绍一下语法。以下是 UPDATE 命令修改 MySQL 数据表数据的通用 S…

如何使用idm下载百度网盘的资源

IDM是海内外都非常受欢迎的一款下载管理软件。它支持视频媒体嗅探和多线程下载&#xff0c;能够完美替代谷歌Chrome浏览器、Edge浏览器等浏览器的原生下载功能。在浏览器中单击下载链接时&#xff0c;idm将接管浏览器的原生下载工具并加快下载速度&#xff0c;支持HTTP&#xf…

机器人持续学习基准LIBERO系列6——获取并显示实际深度图

0.前置 机器人持续学习基准LIBERO系列1——基本介绍与安装测试机器人持续学习基准LIBERO系列2——路径与基准基本信息机器人持续学习基准LIBERO系列3——相机画面可视化及单步移动更新机器人持续学习基准LIBERO系列4——robosuite最基本demo机器人持续学习基准LIBERO系列5——…

Node.js基础知识点(四)

本节介绍一下最简单的http服务 一.http 可以使用Node 非常轻松的构建一个web服务器&#xff0c;在 Node 中专门提供了一个核心模块&#xff1a;http http 这个模块的就可以帮你创建编写服务器。 1. 加载 http 核心模块 var http require(http) 2. 使用 http.createServe…

Vue3 在 history 模式下通过 vite 打包部署白屏

Vue3 在 history 模式下通过 vite 打包部署后白屏; 起因 hash 模式 url 后面跟个 # 强迫症犯了改成了 history,就此一波拉锯战开始了 ... 期间 nigix 和 router 各种反复排查尝试最终一波三折后可算是成功了 ... Vue官方文档 具体配置可供参考如下: 先简要介绍下,当前项目打包…

vue 选择题 A B C D 全部默认 ABCD,最少 AB,最多ABCDE。支持增删改

需求&#xff1a;选项&#xff1a;单选题、单选题(英)、多选题。全部默认 ABCD&#xff0c;最少 AB&#xff0c;最多 ABCDE。支持增删改 假如有ABCD四个选项,删除选项B&#xff0c;剩余的之接更新变成ABC&#xff08;写个更新方法&#xff09; 添加的时候也是按照顺序添加 &a…

RabbitMQ安装和使用

简介 RabbitMQ是一套开源&#xff08;MPL&#xff09;的消息队列服务软件&#xff0c;是由LShift提供的一个Advanced Message Queuing Protocol (AMQP) 的开源实现&#xff0c;由以高性能、健壮以及可伸缩性出名的Erlang写成。所有主要的编程语言均有与代理接口通讯的客户端库…

React里面table组件的时间轴的位置计算

如上图的时间轴位置计算 计算时间轴位置倒是容易&#xff0c;主要是React里面的antd的table组件怎么监听滚动是个问题 /*** &#xff08;月台/时间&#xff09;为150&#xff0c;时间为100&#xff0c;每个格子为120&#xff0c;120px/30分钟4px/分钟* 00:00分为250px* 00:…

3Dmax灯光学习(Vray灯光应用)

渲染效果图可以用渲染100哦&#xff0c;最高支持max2024&#xff0c;cr11&#xff0c;vr6.2&#xff0c;支持LUT和Acescg工作流等常用插件&#xff0c;同时森林插件7.43也进行了支持&#xff0c;注册填邀请码【7788】即可免费测试&#xff01; 灯光应用 普通灯光/标准灯光(外景…

爬虫笔记(一):实战登录古诗文网站

需求&#xff1a;登录古诗文网站&#xff0c;账号&#xff0b;密码&#xff0b;图形验证码 第一&#xff1a;自己注册一个账号&#xff0b;密码哈 第二&#xff1a;图形验证码&#xff0c;需要一个打码平台&#xff08;充钱&#xff0c;超能力power&#xff01;&#xff09;或…