VC++培训教程草稿(2000年撰写)张孝祥、袁龙友著网址:章文档序列化大多数应用程序都为用户提供了数据的保存功能,这些数据可能是电子表格、字处理文档、一组数据或图形等等。从磁盘存储器上存取这些数据的工作往往是通过文件操作或者数据库操作来完成的。关于数据库操作的内容,我们将在后面的章节中进行详细的介绍,在本章的内容中,我们主要讨论如何通过文件操作实现一般意义上的数据存取工作。通过文件操作来实现数据的存取工作通常有两种工作方式:一种是使对象具有序列化;另一种方法就是直接使用CFile对象处理文件。本章就将对这两部分内容分别做出详解。8.1序列化在MFC当中,对象的序列化功能主要是通过文档/视图结构中特有的文档对象的序列化机制来实现的。本节,我们将详细介绍如何使用序列化机制来实现对象的序列化。序列化,简单地说就是向一个持久性的存储媒体——如磁盘文件保存对象或读取对象的过程。序列化分为两部分,当把应用程序数据以文件形式存储在系统磁盘中时,叫做序列化;当从磁盘文件中恢复应用程序数据的状态时,叫做反序列化,这两个部分的组合构成了VisualC++中的应用程序对象的序列化。8.1.1CArchive类和Serialize函数VisualC++应用程序中的序列化是通过CArchive类来实现的。CArchive类总是与一个CFile对象相关联,CArchive类是作为CFile对象的输入输出流而设计的,如图8-00所示,它使用经过重载的C++流入()和流出()操作符从存储应用程序数据的文件中实现读取和写入数据,而将这些数据保存到磁盘文件中的工作由CArchive对象指示CFile对象来完成。序列化函数图8-00CArchive类和CFile类可以实现序列化的类——即从CObject继承而来的类,有一个叫做Serialize的成员函数,序列化工作主要是在这个函数当中进行的。当应用程序读取或写入文件时,文档对象的Serialize函数被调用,并传递用于从文件读取或向文件写入数据的CArchive对象。在Serialize函数中,要遵循的典型逻辑是通过调用CArchive类的IsStoring或IsLoading函数来判定当前行为是正在对文件写入还是读取。根据这两个函数中任何一个的返回值即可判定应用程序需要从CArchive类的I/O流中读取还是向其写入。当用户在打开或保存拥有文档对象数据的文件或者使用文档对象的Open、Save、SaveAs应用程序框架应用程序对象CArchive类CFileVC++培训教程草稿(2000年撰写)张孝祥、袁龙友著网址:菜单命令时,MFC便会自动调用Serialize函数,一个典型的Serialize函数如下所示:voidCAge::Serialize(CArchive&ar){CObject::Serialize(ar);if(ar.IsStoring())arm_years;elsearm_years;}其中,ar是一个指明应用程序序列化对象的CArchive引用参数。CArchive::Serialize成员函数可以告诉用户序列化对象当前是否用来存储或加载。可以将Serialize函数放置在所创建的任何类中,以便文档的Serialize函数中调用这些类的Serialize函数。8.1.2使自己的类支持序列化在前几章讲过的例子中使用CString类的字符串来保存文本行,由于它是MFC类,因此可以串行化自己,将自己写入磁盘或从磁盘文件中读取二进制数据来建立对象。那么,如果不是标准的MFC类,比如用户自己定义的类,如何让它支持序列化呢?要让用户定义的类支持序列化,一般分为五步:1.从CObject或其派生类派生出用户的类2.在类声明文件中,加入DECLARE_SERIAL宏。编译时,编译器将扩充该宏,这是串行化对象所必需的。3.重载Serialize()成员函数,加入必要的代码,用以保存对象的数据成员到CArchive对象以及从CArchive对象载入对象的数据成员状态。4.定义一个不带参数的构造函数。5.在实现文件中加入IMPLEMENT_SERIAL宏。下面将通过一个实例来演示如何让用户定义的类支持序列化功能。8.1.3实例:保存和显示图形还记得第6章的绘图程序吗,用户画好的图形不仅不能保存下来,而且当窗口发生重绘时,图形也就不见了,本实例就将解决这两个问题,不仅使所画的图形在窗口重绘时依然保留,而且还给它添加了保存及再显示功能。我们在第6章绘图程序上加的内容够多了,这里为了更清晰的讲述本章的重点——序列化,将新建一个工程,当然,这个工程所要实现的功能还是和第6章绘图程序一样,只不过给它加个序列化,完整例程请参见光盘中的例子代码EX08_00,具体操作步骤如下:步骤1:新建一个MFC单文档应用程序,工程名为EX08_00或用户自定义。步骤2:为新建的工程先实现第6章的简单绘图功能(详细步骤请参见第6章)。1.在资源面板中修改原来的标准菜单,新插入一个菜单名为“绘图”,下面有四个菜单项“点”、“线”、“矩形”、“椭圆”,修改它们的ID分别为:ID_GRAPH_DOT、ID_GRAPH_LINE、ID_GRAPH_RECTANGLE、ID_GRAPH_ELLIPSE。2.在CEX08_00View类中添加两个成员变量CPointm_ptOrigin和intm_nType,分别表示绘图的起点和绘图的类型,并在构造函数中初始化为0和-1。3.在CEX08_00View中加入四个菜单项“点”、“线”、“矩形”和“椭圆”的WM_COMMAND消息的响应函数OnGraphDot、OnGraphLine、OnGraphRectangle、OnGraphEllipse,VC++培训教程草稿(2000年撰写)张孝祥、袁龙友著网址:的值。m_nType为0,表示画点;m_nType为1,表示画线;m_nType为2,表示画矩形;m_nType为3,表示画椭圆。4.在CEX08_00View类中加入WM_LBUTTONDOWN和WM_LBUTTONUP的消息响应函数OnLButtonDown和OnLButtonUp。在OnLButtonDown中保存鼠标按下的点;在OnLButtonUp中,根据m_nType的值画相应的图形。步骤3:给工程添加一个可序列化的类CGraph。1.新建一个类CGraph,从CObject派生。打开工作台ClassView页面,鼠标右击最顶层的EX08_00classes,在弹出的快捷方式菜单中选择NewClass,在弹出的NewClass对话框上,在Classtype中要选GenericClass,在类名Name中输入CGraph,单击Baseclass下面列表框中DerivedFrom下面高亮显示的第一栏,输入将要派生的基类CObject,后面类型为publice,如图8-01所示,然后单击OK。图8-01添加新类CGraph当单击OK按钮添加该类时,会弹出一个如图8-02所示的对话框,该对话框提示ClassWizard无法为从CObject派生出的CGraph类找到合适的头文件。我们不用理会它,在此消息框中单击确定按钮即可,因为合适的头文件已经包含在CGraph类中。2.在CGraph类中重载Serialize()成员函数。我们要实现序列化,先对其进行改造,在工作台的ClassView页面中选择CGraph类,单击鼠标右键,选择AddMemberFunction增加一个成员函数,在弹出的对话框中VC++培训教程草稿(2000年撰写)张孝祥、袁龙友著网址:,在FunctionDeclaretion编辑框中输入Serialize(CArchive&ar),然后选择Public,按OK即可。然后在ClassView中可以看到这个函数。图8-02创建新类时的警告信息3.在类CGraph的头文件中,加入DECLARE_SERIAL宏,代码如下:classCGraph:publicCObject{public:voidSerialize(CArchive&ar);CGraph();virtual~CGraph();DECLARE_SERIAL(CGraph)};要对CGraph类实现序列化,需要在类的.h文件中加入宏DECLARE_SERIAL的调用,这个宏不需要加分号,并且后面有一个参数表示添加序列化特性的类名。4.定义一个不带参数的构造函数。打开工作台的CGraph类,可以看到,不带参数的构造函数已经存在于类中了,这是我们最开始创建CGraph这个新类时自动添加的。MFC在从磁盘文件载入对象状态并重建对象时,需要有一个缺省的不带任何参数的构造函数。序列化对象将用该构造函数生成一个对象,然后调用Serialize()函数,用重建对象所需的值来填充对象的所有数据成员变量。5.在类CGraph实现文件中加入IMPLEMENT_SERIAL宏。打开类CGraph的实现文件,在该类的构造函数前添加MPLEMENT_SERIAL宏,代码如下://Graph.cpp:implementationoftheCGraphclass.////////////////////////////////////////////////////////////////////////#includestdafx.h#includeEX08_00.h#includeGraph.h#ifdef_DEBUG#undefTHIS_FILEstaticcharTHIS_FILE[]=__FILE__;#definenewDEBUG_NEWVC++培训教程草稿(2000年撰写)张孝祥、袁龙友著网址:(CGraph,CObject,1)CGraph::CGraph(){}可见,将该宏的调用添加在构造函数前,也不需要分号。IMPLEMENT_SERIAL宏用于定义一个从CObject派生的可序列化类的各种函数。宏的第一和第二个参数分别代表可序列化的类名和该类的直接基类。第三个参数是对象的版本号,它是一个大于或等于零的整数。MFC序列化代码在将对象读入内存时检查版本号。如果磁盘文件上的对象的版本号和内存中的对象的版本号不一致,MFC将抛出一个CArchiveException异常,阻止程序读入一个不匹配版本的对象。现在,我们就可以象使用标准MFC类一样使用CGraph的序列化功能了。步骤4:构造CGraph类,做准备工作。1.在类CGraph的头文件中添加三个成员变量m_ptOrigin、m_ptEnd、m_nType。我们既然想保存下来所画的图形,那么至少要保留住关于这些图形的一些信息,不管用户画的是线、矩形还是椭圆,它们都有一个共同点:就是由两点决定这个图形,那么从起点到终点画的到底是什么图形,就要看m_nType的值了,因此这里定义了两个CPoint型的变量,用于保存用户所画的一组图形的各个起点和终点;另一个为int型变量用来指定所画的每个图形的类型。classCGraph:publicCObject{public:CPointm_ptOrigin;//记录起始点CPointm_ptEnd;//记录终点intm_nType;//记录画图类型voidSerial