MFC六大关键技术(第四部分)——永久保存(串行化)

MFC 六大关键技术 ( 第四部分 ) ——永久保存(串行化)

先用一句话来说明永久保存的重要:弄懂它以后,你就越来越像个程序员了!

如果我们的程序不需要永久保存,那几乎可以肯定是一个小玩儿。那怕我们的记事本、画图等小程序,也需要保存才有真正的意义。

对于 MFC 的很多地方我不甚满意,总觉得它喜欢拿一组低能而神秘的宏来故弄玄虚,但对于它的连续存储( serialize )机制,却是我十分钟爱的地方。在此,可让大家感受到面向对象的幸福。

MFC 的连续存储( serialize )机制俗称串行化。“在你的程序中尽管有着各种各样的数据, serialize 机制会象流水一样按顺序存储到单一的文件中,而又能按顺序地取出,变成各种不同的对象数据。”不知我在说上面这一句话的时候,大家有什么反应,可能很多朋友直觉是一件很简单的事情,只是说了一个“爽”字就没有下文了。

要 实现象流水一样存储其实是一个很大的难题。试想,在我们的程序里有各式各样的对象数据。如画图程序中,里面设计了点类,矩形类,圆形类等等,它们的绘图方 式及对数据的处理各不相同,用它们实现了成百上千的对象之后,如何存储起来?不想由可,一想头都大了:我们要在程序中设计函数 store() ,在我们单击“文件 / 保存”时能把各对象往里存储。那么这个 store() 函数要神通广大,它能清楚地知道我们设计的是什么样的类,产生什么样的对象。大家可能并不觉得这是一件很困难的事情,程序有能力知道我们的类的样子,对象也不过是一块初始化了存储区域罢了。就把一大堆对象“转换”成磁盘文件就行了。

即使上面的存储能成立,但当我们单击“文件 / 打开”时,程序当然不能预测用户想打开哪个文件,并且当打开文件的时候,要根据你那一大堆垃圾数据 new 出数百个对象,还原为你原来存储时的样子,你又该怎么做呢?

试 想,要是我们有一个能容纳各种不同对象的容器,这样,用户用我们的应用程序打开一个磁盘文件时,就可以把文件的内容读进我们程序的容器中。把磁盘文件读进 内存,然后识别它“是什么对象”是一件很难的事情。首先,保存过程不像电影的胶片,把景物直接映射进去,然后,看一下胶片就知道那是什么内容。可能有朋友 说它象录像磁带,拿着录像带我们看不出里面变化的磁场信号,但经过录像机就能把它还原出来。

其实不是这样的,比如保存一个矩形,程序并不是把矩形本身按点阵存储到磁盘中,因为我们绘制矩形的整个过程只不过是调用一个 GDI 函数罢了。它保存只是坐标值、线宽和某些标记等。程序面对“ 00 FF ”这样的东西,当然不知道它是一个圆或是一个字符!

拿 刚才录像带的例子,我们之所以能最后放映出来,前提我们知道这对象是“录像带”,即确定了它是什么类对象。如果我们事先只知道它“里面保存有东西,但不知 道它是什么类型的东西”,这就导致我们无法把它读出来。拿录像带到录音机去放,对录音机来说,那完全是垃圾数据。即是说,要了解永久保存,要对动态创建有 深刻的认识。

现在大家可以知道困难的根源了吧。我们在写程序的时候,会不断创造新的类,构造新的对象。这些对象,当然是旧的类对象(如 MyDocument )从未见过的。那么,我们如何才能使文档对象可以保存自己新对象呢,又能动态创建自己新的类对象呢?

许多朋友在这个时候想起了 CObject 这个类,也想到了虚函数的概念。于是以为自己“大致了解”串行化的概念。他们设想:“我们设计的 MyClass (我们想用于串行化的对象)全部从 CObject 类派生, CObject 类对象当然是 MyDocument 能认识的。”这样就实现了一个目的:本来 MyDocument 不能识别我们创建的 MyClass 对象,但它能识别CObject 类对象。由于 MyClass 从 CObject 类派生,我产的新类对象“是一个 CObject ”,所以 MyDocument 能把我们的新对象当作 CObiect 对象读出。或者根据书本上所说的:打开或保存文件的时候, MyDocument 会调用 Serialize (), MyDocument 的 Serialize ()函会呼叫我们创建类的 Serialize 函数 [ 即是在MyDocument Serialize ()中调用:m_pObject -> Serialize() ,注意:在此m_pObject 是CObject 类指针,它可以指向我们设计的类对象] 。最终结果是 MyDocument 的读出和保存变成了我们创建的类对象的读出和保存,这种认识是不明朗的。

有意思还有,在网上我遇到几位自以为懂了 Serialize 的朋友,居然不约而同的犯了一个很低级得让人不可思议的错误。他们说: Serialize 太简单了! Serialize ()是一个虚函数,虚函数的作用就是“优先派生类的操作”。所以 MyDocument 不实现 Serialize ()函数,留给我们自己的 MyClass 对象去调用 Serialize ()……真是哭笑不得,我们创建的类 MyClass 并不是由 MyDocument 类派生, Serialize ()函数为虚在 MyDocument 和 MyClass 之间没有任何意义。 MyClass 产生的 MyObject 对象仅仅是 MyDocument 的一个成员变量罢了。

话说回来,由于 MyClass 从 CObject 派生,所以CObject 类型指针能指向 MyClass 对象,并且能够让 MyClass 对象执行某些函数(特指重载的 CObject 虚函数),但前提必须在 MyClass 对象实例化了,即在内存中占领了一块存储区域之后。不过,我们的问题恰恰就是在应用程序随便打开一个文件,面对的是它不认识的 MyClass 类,当然实例化不了对象。

幸好我们在上一节课中懂得了动态创建。即想要从CObject 派生的MyClass 成为可以动态创建的对象只要用到DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC 宏就可以了(注意:最终可以Serialize 的对象仅仅用到了DECLARE_SERIAL/IMPLEMENT_SERIAL 宏,这是因为DECLARE_SERIAL/IMPLEMENT_SERIAL 包含了DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC 宏)。

从解决上面的问题中,我们可以分步理解了:

1、   Serialize 的目的:让 MyDocument 对象在执行打开 / 保存操作时,能读出(构造)和保存它不认的 MyClass 类对象。

2、   MyDocument 对象在执行打开 / 保存操作时会调用它本身的 Serialize ()函数。但不要指望它会自动保存和读出我们的 MyClass 类对象。这个问题很容易解决,就直接在 MyDocument:: Serialize (){

// 在此函数调用MyClass 类的Serialize ()就行了!即

MyObject. Serialize ();       

}

3、   我们希望 MyClass 对象为可以动态创建的对象,所以要求在MyClass 类中加上DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC 宏。

但目前的Serialize 机制还很抽象。我们仅仅知道了表面上的东西,实际又是如何的呢?下面作一个简单深刻的详解。

先看一下我们文档类的Serialize ()

void CMyDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        // TODO: add storing code here

    }

    else

    {

        // TODO: add loading code here

    }

}

目前这个子数什么也没做(没有数据的读出和写入),CMyDoc 类正等待着我们去改写这个函数。现在假设CMyDoc 有一个MFC 可识别的成员变量m_MyVar, 那么函数就可改写成如下形式:

void CMyDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())     // 读写判断

    {

        ar<<m_MyVar;        // 写

    }

    else

    {

        ar>>m_MyVar;        // 读

    }

}

许多网友问:自己写的类(即 MFC 未包含的类)为什么不行?我们在 CMyDoc 里包含自写类的头文件MyClass.h ,这样CMyDoc 就认识MyDoc 类对象了。这是一般常识性的错误,MyDoc 类认识MyClass 类对象与否并没有用,关键是CArchive 类,即对象ar 不认识MyClass (当然你梦想重写CArchive 类当别论)。“>> ”、“<< ”都是CArchive 重载的操作符。上面ar>>m_MyVar 说白即是在执行一个以ar 和m_MyVar 为参数的函数,类似于function(ar,m_MyVar) 罢了。我们当然不能传递一个它不认识的参数类型,也因此不会执行function(ar,m_MyObject) 了。

[ 注:这里我们可以用指针。让MyClass 从Cobject 派生,一切又起了质的变化,假设我们定义了:MyClass *pMyClass = new MyClass; 因为MyClass 从CObject 派生,根据虚函数原理,pMyClass 也是一个CObject* ,即pMyClass 指针是CArchive 类可认识的。所以执行上述function(ar, pMyClass) ,即ar << pMyClass 是没有太多的问题(在保证了MyClass 对象可以动态创建的前提下)。]

 

回过头来,如果想让 MyClass 类对象能 Serialize ,就得让MyClass 从CObject 派生,Serialize ()函数在CObject 里为虚,MyClass 从CObject 派生之后就可以根据自己的要求去改写它,象上面改写CMyDoc::Serialize ()方法一样。这样MyClass 就得到了属于MyClass 自己特有的Serialize ()函数。

现在,程序就可以这样写:

……

#include “MyClass.h”

……

void CMyDoc::Serialize(CArchive& ar)

{

    // 在此调用 MyClass 重写过的 Serialize()

    m_MyObject. Serialize(ar);      // m_MyObject 为 MyClass 实例

}

至此,串行化工作就算完成了,一即简单直观:从 CObject 派生自己的类,重写 Serialize () 。在此过程中,我刻意安排:在没有用到 DECLARE_SERIAL/IMPLEMENT_SERIAL 宏,也没有用到CArray 等模板类的前提下就完成了串行化的工作。我看过某些书,总是一开始就讲DECLARE_SERIAL/IMPLEMENT_SERIAL 宏或马上用CArray 模板,让读者觉得串行化就是这两个东西,导致许多朋友因此找不着北。

大家看到了,没有DECLARE_SERIAL/IMPLEMENT_SERIAL 宏和CArray 等数据结构模板也依然可以完成串行化工作。

 

现在可以腾出时间讲一下大家觉得十分抽象的 CArchive 。我们先看以下程序(注:以下程序包含动态创建等,请包含DECLARE_SERIAL/IMPLEMENT_SERIAL 宏)

void MyClass ::Serialize(CArchive& ar)

{

    if (ar.IsStoring())     // 读写判断

    {

        ar<< m_pMyVar;      // 问题:ar 如何把m_pMyVar 所指的对象变量保存到磁盘?

    }

    else

    {

        pMyClass = new MyClass; // 准备存储空间

        ar>> m_pMyVar;     

    }

}

要回答上面的问题,即“ ar<<XXX ”的问题。和 我们得看一下模拟 CArchive 的代码。

“ar<<XXX ”是执行CArchive 对运算符“<< ”的重载动作。ar 和XXX 都是该重载函数中的一参数而已。函数大致如下:

CArchive& operator<<( CArchive& ar, const CObject* pOb)

{

    …………

        // 以下为CRuntimeClass 链表中找到、识别pOb 资料。

        CRuntimeClass* pClassRef = pOb->GetRuntimeClass();

        // 保存pClassRef 即类信息(略)

       

        ((CObject*)pOb)->Serialize();// 保存MyClass 数据

    …………

}

从上面可以看出,因为 Serialize() 为虚函数,即“ar<<XXX ”的结果是执行了XXX 所指向对象本身的Serialize() 。对于“ar>>XXX ”,虽然不是“ar<<XXX ”逆过程,大家可能根据动态创建和虚函数的原理料想到它。

至此,永久保存算是写完了。在此过程中,我一直努力用最少的代码,详尽的解释来说明问题。以前我为本课题写过一个版本,并在几个论坛上发表过,但不知怎么在网上遗失(可能被删除)。所以这篇文章是我重写的版本。记得第一个版本中,我是对DECLARE_SERIAL/IMPLEMENT_SERIAL 和可串行化的数组及链表对象说了许多。这个版本中我对DECLARE_SERIAL/IMPLEMENT_SERIAL 其中奥秘几乎一句不提,目的是让大家能找到中心,有更简洁的永久保存的概念,我觉得这种感觉很好!

摘自:http://blog.csdn.net/liyi268/archive/2006/03/13/623367.aspx

转载于:https://www.cnblogs.com/lzjsky/archive/2010/11/24/1886503.html

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

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

相关文章

在网络中配置思科交换机

By default, all ports of a switch are enabled. As we are talking about layer 2 switching, there is no need to configure IP address or any routing protocol on the switch. In such a situation, the configuration is not focused on the switch. 缺省情况下&#…

黑色背景下,描绘照片的轮廓形状并保存

描绘照片的轮廓形状并保存 import cv2 from matplotlib import pyplot as plt # 1.先找到轮廓 img cv2.imread(E:\Python-workspace\OpenCV\OpenCV/beyond.png, 0) _, thresh cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) image, conturs, hierarchy c…

java pdf合并_Java 合并、拆分PDF文档

本文将介绍如何在Java程序中合并及拆分PDF文档&#xff0c;合并文档时&#xff0c;包括合并多个不同PDF文档为一个文档&#xff0c;以及合并PDF文档的不同页面为一页&#xff1b;拆分文档是&#xff0c;包括将PDF文档按每一页拆分&#xff0c;以及按指定页数范围来拆分。下面将…

HDU4405 期望

对于期望&#xff0c;首先&#xff0c;对于这个公式中p表示概率&#xff0c;x表示随机变量 展开则为 ex p1*x1p2*x2p3*x3....... 对于本题 假设 ex[ i ]表示当前 i 走到 n 的期望值。所以若 i 处没有飞机&#xff0c;ex[ i ]sigma(1/6*ex[ik])1 其中(k1...6) &#xff08;1表示…

调用本地电脑摄像头并进行按P进行捕获照片并保存,按下Q退出

调用本地电脑摄像头并进行按P进行捕获照片并保存&#xff0c;按下Q退出 灰度摄像头显示&#xff1a; import cv2 cap cv2.VideoCapture(0) if not cap.isOpened():print("Cannot open camera")exit() while True:# 逐帧捕获ret, frame cap.read()# 如果正确读取帧…

intersect函数_PHP array_intersect()函数与示例

intersect函数PHP array_intersect()函数 (PHP array_intersect() Function ) array_intersect() function is used to find the matched elements from two or more elements. Function “array_intersect()” compares the values of the first array with the other arrays …

很全的SQL注入语句

1、返回的是连接的数据库名and db_name()>02、作用是获取连接用户名and user>03、将数据库备份到Web目录下面;backup database 数据库名 to diskc:\inetpub\wwwroot\1.db;--4、显示SQL系统版本and 1(select VERSION) 或and 1convert(int,version)--5、判断xp_cmdshell扩展…

使用DataTable更新数据库

1、修改数据 DataRow dr hRDataSet.Tables["emp"].Rows.Find(textBox3.Text);//DataRow dr hRDataSet.Tables["emp"].Select("id"textBox3.Text)[0];dr.BeginEdit();dr["name"] textBox1.Text;dr.EndEdit();SqlCommandBuilder cmdn…

java异常体系_JAVA异常体系结构详解

一、什么是异常异常&#xff1a;程序在运行过程中发生由于硬件设备问题、软件设计错误等导致的程序异常事件。(在Java等面向对象的编程语言中)异常本身是一个对象&#xff0c;产生异常就是产生了一个异常对象。 ——百度百科二、异常体系Java把异常当作对象来处理&#xf…

对照片质量进行压缩

对照片质量进行压缩 其实无论是jpg还是png都是已经压缩编码化的格式罢了&#xff0c;原图片的大小要远远大于压缩编码后的格式 1&#xff0c;像素&#xff1a;图片放大到一定程度之后的一个个的小方块 2&#xff0c;RGB&#xff1a;每一个像素&#xff08;小方块&#xff09;都…

Silverlight访问 Apache服务器(Tomcat,Geronimo)中部署的Webservice

Silverlight 访问 Apache服务器中的Webservice 开发环境 Vs2010 、 Silverlight4 、 Java Jdk1.6 U 21 、 Apache-tomcat-6.0.20 、 Myeclipse8.5 、 Apache-ant-1.8.1 、 Axis2 、 Geronimo-tomcat6-javaee5-2.2. 下载地址&#xff1a; Apache-tomcat &#xff1a; http://apa…

那些帮助你成为优秀前端工程师的讲座——《性能篇》

这篇文章是前端优秀讲座和讨论列表系列连载第七篇&#xff0c;介绍前端性能优化技巧。前端领域发展迅速&#xff0c;只有时刻掌握前端发展趋势和技术动态&#xff0c;学习前沿的开发思想和理念才能让自己跟上时代的步伐&#xff0c;保持自己的技术优势。 您可能感兴趣的相关文章…

mca终端_MCA的完整形式是什么?

mca终端1)MCA&#xff1a;计算机应用硕士 (1) MCA: Master of Computer Application) MCA is an abbreviation of Master of Computer Application. It is a masters degree program for post-graduation in Computer applications. This post-graduate course duration is abo…

钢铁侠java_现代版“钢铁侠”,无所不能的程序员,java工程师实现人造器官!...

一位名叫利亚姆泽贝迪(Liam Zebedee)的软件工程师已经厌倦了糖尿病患者的生活挑战&#xff0c;因此他决定入侵他的胰岛素泵&#xff0c;并将其转变成一种崭新的高科技胰腺胰腺。Zebedee详细介绍了查找和订购零件的过程&#xff0c;为智能胰岛素泵编写软件的代码以及在其博客中组…

Windows下的Memcache安装 (转)

Windows下的Memcache安装&#xff1a;1. 下载memcache的windows稳定版&#xff0c;解压放某个盘下面&#xff0c;比如在c:\memcached2. 在终端&#xff08;也即cmd命令界面&#xff09;下输入 ‘c:\memcached\memcached.exe -d install’ 安装3. 再输入&#xff1a; ‘c:\memca…

C#中实现js中的eval函数功能

在js中有eval函数&#xff0c;比如 eval&#xff08;‘33*4’&#xff09;结果为15&#xff1b; 但C#中想要完成这样的功能&#xff0c;却没有相应的函数&#xff0c;可以用sql语句的方式实现&#xff0c;比如&#xff0c;执行 select 33*4 的方式。 可以先构造公式 Formula …

查看照片的指定位置的像素点值,并在照片中绘制一条指定像素颜色的线段

查看照片的指定位置的像素点值&#xff0c;并在照片中绘制一条指定像素的线段 import cv2 img cv2.imread(E:\Python-workspace\OpenCV\yanyu/beyond.png,1)#1为彩色图片&#xff0c;0为灰度图片 (b,g,r) img[20,20]#取照片的(20,20)处的像素点&#xff0c;左上角为(0,0)&am…

大数据和云计算涉及的技术_云计算涉及的风险

大数据和云计算涉及的技术In todays life using of cloud is very common among people, we use different clouds like Google cloud, cloud Azure etc. to store our photos, Videos, documents, data etc. to save space as well as we think that we will be able to retri…

int 转interger java_Java中Integer和int之间的转换

int到Integer:int a3;Integer Anew Integer(a);或:Integer AInteger.valueOf(a);Integer到int:Integer Anew Integer(5);int aA.intValue();至于Integer.parseInt(String str)则是将String类型转为int类型。int类型是放在栈空间的&#xff0c;Integer是作为对象放在堆空间的;in…

图像分割-二阶导数零交叉点的含义

已知&#xff1a; 二阶导数在灰度斜坡和灰度台阶过渡处会产生双边缘响应。 二阶导数的符号可以用于确定边缘的过渡是从亮到暗还是暗到亮。 斜坡开始处&#xff0c;二阶导数为负&#xff0c;斜坡结束二阶导数为正&#xff0c;斜坡上&#xff0c;二阶导数为0.&#xff08;亮到暗…