MFC序列化

文章介绍了序列化和反序列化的概念,用于解决对象在内存、文件或网络间传输时遇到的引用问题。通过序列号解决对象引用的方案,确保数据一致性。在MFC中,利用CArchive和CFile进行对象的序列化操作,包括存储和加载数据,以及处理对象间的引用关系。文章还给出了Person类的序列化示例代码。
摘要由CSDN通过智能技术生成

序列化和反序列化


概述

  • 本文仅为本人学习过程笔记,仅用法,实际实现在于封装重载函数,尚未研究

  • 编写应用程序的时候需要将程序的某些数据存储在内存中,将其写入某个文件或加ing其传输到网络中另一台计算机上实现通讯

  • 过程称为序列化

  • 逆过程成为反序列化
    +

  • 常见几种序列化

    • MFC Serialization
    • .Net Framework
  • 原因,概述

    简单来说序列化就是一种用来处理对象流的机制。所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O)。我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!
    在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!
    -----------------------------------------------------------------------------------------
    问题的引出:
    如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?
    别急,其中一个最大的问题就是对象引用!
    举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); }。这时在内存中实际上分配
    了两个空间,一个存储对象a,一个存储对象b。接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a
    的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a
    的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!
    -----------------------------------------------------------------------------------------
    以下序列化机制的解决方案:
    1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)
    2.当要保存一个对象时,先检查该对象是否被保存了
    3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象通过以上的步骤序列化机制解决了对象引用的问题!
    
  • 为什么要实现序列化

    • 场景
      • 当你想把的内存中的对象保存到一个文件中或者数据库中时候;
      • 当你想用套接字在网络上传送对象的时候;
      • 当你想通过RMI传输对象的时候;
    • 注意事项
      • a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
      • b)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
      • c)并非所有的对象都可以序列化,原因:
      	1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI(远程方法调用)传输等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
      
      	1. 资源分配方面的原因,比如socket,thread类,如果可以序列化	进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
      
      • d) 没有实现Serializable接口的父类,编写一个能够序列化的子类
      	1, 父类要有一个无参的constructor;
      
  1. CArray 用法

  2. MFC 控制台测试


CArchive

/*显示创建CArchive*/
CArchive archive(&theFile, CArchive::store);
  1. 为 CArchive 构造函数的第二个参数是指定的枚举值存档是否为存储或加载数据将使用来回文件

  2. 对象的 Serialize 功能通过调用 IsStoring() 功能检查此状态存档对象

    1. ar.isStoring()的实现,重载符号的方向与文件操作输入输出相同
  3. 序列化使用的时候,会自动将所有的内容序列化,包括私有变量,只要输入了一个,其他类中的所有变量都会进行序列化

    1. 但是具体顺序还不知道是什么
  4. 使用完毕之后需要关闭

    /*先关闭archive,再关闭文件*/
    archive.Close();
    theFile.Close();
    
  5. CFile.Open

    • 字符串存在多种存储方式

类的编写

  1. 头文件

    #pragma once
    #define _AFXDLL
    
    #include <afxwin.h>
    #include <afx.h>
    
  2. 继承与CObject

  3. 声明DECLARE_SERIAL(Class) & IMPLEMENT_SERIAL(Father, CObject, 1)

    1. DECLARE_SERIAL(Class)必须在序列化的类体内部

    2. IMPLEMENT_SERIAL(Father, CObject, 1)必须在序列化的类体外部

      	class_name 类的实际名字(不用引号括起来)。
      	base_class_name 基类的名字(不用引号括起来)。
      	wSchema 一个UINT类型的版本号,将被用在存档中,使得解串行程序能够识别并处理早期版本的程序所生成的数据。它的值不能是-1。
      
    3. 两种定义方式取决于define里的写法

  4. 重写serialize()方法

    virtual void Serialize(CArchive &ar);
    
    void Asd::Serialize(CArchive& ar)
    {
    	CObject::Serialize(ar);
    	// 内存为标准
    
    	// Is Storing
    	if (ar.IsStoring())
    	{
    		// Store
    
    		ar << a << b << c ;
    	}
    	else
    	{
    		// Load
    
    		ar >> a >> b >> c;
    	}
    }
    
  5. 封装类型的序列化需要主动调用

  6. 序列化的时候,需要主动调用父类的序列化

  7. 当父类实现序列化的时候,子类如果没实现,也可以直接调用父类的序列化,但是反序列化会丢失子类的属性

    • 子类序列化,父类没实现也同样,会丢失父类属性
    • 没有被序列化的数据,在反序列化的时候都会丢失

工程样例

#pragma once
/**
* 定义数据对象
*/

#define _AFXDLL //MFC程序的宏定义
#include <afxwin.h> //MFC程序头文件
#include <afx.h> //MFC程序头文件
class Person : public CObject
{
   DECLARE_SERIAL(Person)    //第一个宏的位置,参数为当前类名
public:
   Person();
   ~Person();
public:
   //重写了Serialize成员函数,实现对象序列化读写操作
   virtual void Serialize(CArchive& ar);
   //重写操作符友元函数
   //friend CArchive& AFXAPI operator<<(CArchive& ar, Person& src);
   //friend CArchive& AFXAPI operator>>(CArchive& ar, Person& src);
   //定义对象数据
public:
   long m_id;
   CString m_name;
   int m_age;

};



#include "People.h"

//关键宏定义:第二个宏的位置,
//第一个参数为当前类名,
//第二个参数为父类类名,
//第三个参数为该类的特定整型标识,该标识将用来解序(重新实例化),最小为0
IMPLEMENT_SERIAL(Person, CObject, 1)
Person::Person() :CObject()
{
   m_id = 0;
   m_age = 0;

}

Person::~Person()
{

}

void Person::Serialize(CArchive& ar)
{
   //序列化读写基类对象数据
   CObject::Serialize(ar);
   //序列化读写当前对象数据
   if (ar.IsStoring())//判断读写操作,写操作,将数据写入到文件,加载为ar.IsLoading()
   {
       ar << m_id << m_name << m_age; //功能类似cout、cin的IO数据流操作
   }
   else //读操作,将文件读入到内存
   {
       ar >> m_id >> m_name >> m_age;
   }
}



CArchive& AFXAPI operator>>(CArchive& ar, Person& src)
{
   return ar >> src.m_id >> src.m_name >> src.m_age;
}

CArchive& AFXAPI operator<<(CArchive& ar, Person& src)
{
   return ar << src.m_id << src.m_name << src.m_age;
}



		ar << a;
		ar << a << b << c;
		ar << a << b << c << e;
		ar << a << e << b << c;

		ar >> a;
		ar >> a >> b >> c;
		ar >> a >> b >> c >> e;
		ar >> a >> e >> b >> c;



// TSTMFC序列化读写.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include <iostream>
#include "People.h"
using namespace std;
//#define WRITE //读写控制宏 读:READ 写:WRITE
#define READ
//测试入口函数
int main()
{
#ifdef WRITE
    cout << "执行写文件操作\n" << endl;
    CFile file(_T("../person.txt"), CFile::modeCreate | CFile::modeWrite); //定义一个文件流对象
    CArchive ar(&file, CArchive::store);  //定义一个序列化对象和文件流对象绑定并指定归档方式为储存,加载的方式为CArchive::load
    Person person;
    person.m_id = 12345;
    person.m_name = "zhangsan";
    person.m_age = 12;
    person.Serialize(ar);
    ar.Close();
    file.Close();
#endif
#ifdef READ
    cout << "执行读文件操作\n" << endl;
    CFile file(_T("../person.txt"), CFile::modeRead); //定义一个文件流对象
    CArchive ar(&file, CArchive::load);  //定义一个序列化对象和文件流对象绑定并指定归档方式为储存,加载的方式为CArchive::load
    Person person;
    person.Serialize(ar);
    cout << person.m_id << "|" << person.m_name << "|" << person.m_age << endl;
    ar.Close();
    file.Close();
    cin.get();
#endif // DEBUG

2023/5/7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MFC序列化类对象时,可以使用对象的Runtime Class(运行时类)来判断类的类型。Runtime Class是一个MFC中的类,用于描述一个类的类型信息,包括类的名称、父类、成员变量、成员函数等信息。每个MFC类都有一个对应的Runtime Class对象,可以通过调用类的GetRuntimeClass函数来获取该对象。 在序列化过程中,可以使用对象的Runtime Class来判断对象的类型,例如: ``` void CMyDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // 写入数据 if (m_pShape->GetRuntimeClass() == RUNTIME_CLASS(CMyLine)) { // 如果是CMyLine对象,则写入线段相关数据 ar << (BYTE)1; // 标识为线段类型 ar << (CMyLine*)m_pShape; } else if (m_pShape->GetRuntimeClass() == RUNTIME_CLASS(CMyRectangle)) { // 如果是CMyRectangle对象,则写入矩形相关数据 ar << (BYTE)2; // 标识为矩形类型 ar << (CMyRectangle*)m_pShape; } // ... } else { // 读取数据 BYTE type; ar >> type; // 读取类型标识 if (type == 1) { // 如果是线段类型,则读取线段数据并创建CMyLine对象 m_pShape = new CMyLine; ar >> (CMyLine*)m_pShape; } else if (type == 2) { // 如果是矩形类型,则读取矩形数据并创建CMyRectangle对象 m_pShape = new CMyRectangle; ar >> (CMyRectangle*)m_pShape; } // ... } } ``` 在上述代码中,通过判断对象的Runtime Class来确定对象的类型,然后分别进行序列化操作。注意,在读取数据时,需要先读取类型标识,然后根据标识来创建相应类型的对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值