1 / 144

第七章 文档类对象持续性

第七章 文档类对象持续性. 本章将要学习 MFC 应用程序框架另一个重要的机制 —— 对象的持续性 ,本章讲述的重点是对文档类所提 供的如何将 对象的数据保存到磁盘文件 中和如何从 磁 盘文件中保存的数据恢复对象数据 这一机制进行探讨 性的分析。. 7.1 CObject 根基类 MFC 中绝大部分的类都是直接或间接从根基类 CObject 派生 得到的,它们都自然地具备了 CObject 所具有的性质,当然用户 从这些 MFC 类派生的自定义类也同样继承了 CObject 的性质。

temira
Download Presentation

第七章 文档类对象持续性

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第七章 文档类对象持续性

  2. 本章将要学习 MFC应用程序框架另一个重要的机制 —— 对象的持续性,本章讲述的重点是对文档类所提 供的如何将对象的数据保存到磁盘文件中和如何从磁 盘文件中保存的数据恢复对象数据这一机制进行探讨 性的分析。

  3. 7.1 CObject 根基类 MFC 中绝大部分的类都是直接或间接从根基类 CObject 派生 得到的,它们都自然地具备了 CObject 所具有的性质,当然用户 从这些 MFC 类派生的自定义类也同样继承了 CObject 的性质。 因此,理解 CObject 的性质就有着特别重要的意义。 7.1.1 CObject 类的三大性质 持续性、动态性和诊断性是面向对象的类要实现的十分重要 的功能。将实现这些功能的基本操作封装在根基类中,使之具 有这三方面的性质,从而导致它的派生类都能自然继承这些重要 性质是面向对象的类结构设计的精髓之一。

  4. 1 持续性 所谓对象的持续性是指将内存中的对象数据保存到持久介质 (例如,磁盘文件),或者反过来,从持久介质(例如,磁盘 文件)中读取数据然后自动在内存中重建对象,这对于面向对 象程序中的数据读写是十分重要的性质。在 CObject 类中提供 持续化操作的是虚拟成员函数 Serialize( CArchive& ar )。它的派生 类都自动地继承这个虚函数,并且可以重定义派生类中此虚函 数的版本,以便实现特定数据的持续化操作。例如,CMyClass 是 CObject 的派生类,只要对 Serialize进行重定义,便可以方便 地通过调用 Serialize 对 CMyClass 对象实现持续化操作。

  5. Class CMyClass : public CObject { … protected: Serialize( CArchive& ar ); … }; 其中参数 CArchive类对象的引用 ar提供了被打开的文件(用于 读或写)的相关信息和用于读写的缓冲区。只要在 Serialize 的 新版本中添加用于类对象数据持续化的代码,便可以通过 ar实 现类对象与磁盘文件之间的读写操作,例如,下列代码就可以 完成读入文件中的数据对 CMyClass 类对象的初始化 —— 重建 持续化。 (假设 CMyClass::Serialize已经定义)

  6. CMyClass *pOb = new CMyClass; // 初始化 CArchive类对象ar CFile file; if(file.Open( “filename.dat”,CFile::modeRead ) ) // 文件 filename.dat 用于保存着描述 CMyClass类对象的全部信息 { CArchive ar( &file, CArchive::load ); pOb->Serialize(ar); } ar.Close(); file.Close(); 下面的代码将实现 CMyClass 对象的数据保存到文件中 —— 保 存持续化操作。

  7. CMyClass Ob; // 初始化 CMyClass类对象 Ob … // 初始化 CArchive类对象 ar CFile file; if(file.Open( “filename.dat”, CFile::modeCreate | CFile::modeWrite ) ) // 文件 filename.dat用于保存描述 CMyClass 类对象的全部信息 { CArchive ar( &file, CArchive::store ); Ob.Serialize( ar ); } ar.Close(); file.Close();

  8. 2 动态性 CObject 类所提供的动态性是最基本形式 —— 回答或者提供 类似“我是谁”的服务,但不提供动态性的高级形式 —— 动态创建。CObject 的动态性是由成员函数 IsKindOf( CRunTimeClass* ptr ) 提供的。CObject 类的派生类都继承了这一方法,只要调用该函 数,派生类对象便可以判断该类指针所指的对象是不是该类的 对象,例如: CMyClass *pOb; pOb->IsKindOf( RUNTIME_CLASS( CMyClass ) ); //如果是,则返回 TRUE,否则返回 FALSE。

  9. 3. 诊断性 提供将对象的状态转储给调试机制(如 Debug 输出窗口)的 能力。CObject 类有两种转储方式: ·虚成员函数 Dump 将类对象的内部数据输出到 CDumpContext 类对象 afxDump 中,而 afxDump 是与调试输出窗口绑定的; ·虚成员函数 AssertValid 用于判断数据的有效性。 AppWizard 会自动为 CObject 的派生类加入这两个成员函数的 重定义版本,例如定义一个 CObject 的间接派生类 CMyDoc(由 CDocument直接派生),自动添加的重定义诊断函数代码:

  10. #ifdef _DEBUG void CMyDoc::AssertValid() const { CDocument::AssertValid(); } void CMyDoc::Dump( CDumpContext& dc ) const { CDocument::Dump( dc ); } #endif // _DEBUG 假如 CMyDoc 类定义中具有两个数据成员: CString m_szName; int m_nAge; 那么,可以将上述两个成员函数的定义代码修改为:

  11. void CMyDoc::AssertValid() const { ASSERT( !m_szName.IsEmpty() ); ASSERT( m_nAge< 0 ); CDocument::AssertValid(); } void CMyDoc::Dump( CDumpContext& dc ) const { dc << “m_szName” << m_szName << endl; dc << “m_nAge” << m_nAge << endl; CDocument::Dump( dc ); }

  12. 7.1.2 MFC应用程序中的三对宏 微软公司在设计 MFC 类库的根基类 CObject时,已经使该根 基类具有了上述三个重要性质,从而 MFC 中直接或间接地从 CObject 类派生的类都通过 CObject 类获得了持续性、动态性和 诊断性的机制和最基本功能。但是,CObject 对动态性和持续 性的支持是有限的,不能满足应用程序框架设计的需要,微软 公司便通过宏技术为 MFC 类设计了三对用于弥补这些不足的宏 定义。使用这三对宏定义能灵活地满足不同的应用需要。

  13. 1 第一对宏 DECLARE_DYNAMIC – IMPLEMENT_DYNAMIC和在类中的添加(以 CMyClass为例) : class CMyClass : public CObject { … protected: CMyClass(); DECLARE_DYNAMIC( CMyClass ) … }; IMPLEMENT_DYNAMIC添加在类的实现文件中: IMPLEMENT_DYNAMIC( CMyClass, CObject ) 这对宏的作用就是为所定义的派生类加入动态性的必要补充功能的代码:

  14. ⑴ DECLARE_DYNAMIC 的定义(在系统头文件“afx.h”中) #define DECLARE_DYNAMIC( class_name ) \ protected: \ static CRuntimeClass* PASCAL _GetBaseClass(); \ public: \ static const AFX_DATA CRuntimeClass class##class_name; \ virtual CRuntimeClass* GetRuntimeClass() const; \ 由此可知,DECLARE_DYNAMIC 在类中加入三个成员: ·_GetBaseClass() 用于返回基类的 CRuntimeClass 类对象指针。 ·该类的运行时信息类 CRuntimeClass对象。其中 ## 可称为合 并操作符,其作用是将 class 和由参数 class_name 传递的类 名合并成 CRuntimeClass类对象名。) ·GetRuntimeClass() 用于返回该类的 CRuntimeClass对象指针。

  15. ⑵ IMPLEMENT_DYNAMIC 的定义(在系统头文件 “afx.h”中) #define IMPLEMENT_DYNAMIC( class_name, base_class_name ) \ IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, 0xFFFF, NULL ) #define IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, wSchema, pfnNew ) CRuntimeClass* PASCAL class_name::_GetBaseClass() \ { return RUNTIME_CLASS(base_class_name); } \ AFX_COMDAT const AFX_DATADEF CRuntimeClass class_name::class##class_name = { #class_name, sizeof(class class_name), wSchema, pfnNew, \ &class_name::_GetBaseClass, NULL }; \ CRuntimeClass* class_name::GetRuntimeClass() const \ { return RUNTIME_CLASS( class_name ); } \ 由此可知,IMPLEMENT_DYNAMIC 在类中加入三个成员的实现代码。

  16. ⑶将上述宏定义展开后 CMyClass类的定义 在类的定义文件中,相当于增加了: class CMyClass : public CObject { … protected: CMyClass(); … }; protected: static CRuntimeClass* PASCAL _GetBaseClass(); public: static const CRuntimeClass classCMyClass; virtual CRuntimeClass* GetRuntimeClass() const; DECLARE_DYNAMIC( CMyClass )

  17. 在类的实现文件中,相当于增加了: 可见,加入DECLARE_DYNAMIC – IMPLEMENT_DYNAMIC 的类将提供了下列动态性功能服务: ·能够支持 IsKindOf 服务。 ·能提供该类及其基类的类名和大小。 ·能提供该类及其基类的 CRuntimeClass 结构信息。 CRuntimeClass* PASCALCMyClass::_GetBaseClass() {return RUNTIME_CLASS( CObject ); } const CRuntimeClass CMyClass::classCMyClass = {“CMyClass”,sizeof(class CMyClass), 0xFFFF, NULL, &CMyClass::_GetBaseClass, NULL}; CRuntimeClass*CMyClass::GetRuntimeClass() const { return RUNTIME_CLASS(CMyClass); } IMPLEMENT_DYNAMIC(CMyClass, CObject)

  18. 2第二对宏 DECLARE_DYNCREATE – IMPLEMENT_DYNCREATE 和 在类中的添加 (以 CMainFrame 为例): class CMainFrame : public CFrameWnd { … protected: CMainFrame(); DECLARE_DYNCREATE( CMainFrame ) … }; IMPLEMENT_DYNCREATE 添加在类的实现文件中: IMPLEMENT_DYNCREATE( CMainFrame, CFrameWnd ) 这对宏的作用就是为所定义的派生类加入动态性的必要补充功能的代码:

  19. ⑴ DECLARE_DYNCREATE 的定义(在系统头文件“afx.h”中) #define DECLARE_DYNCREATE( class_name ) \ DECLARE_DYNAMIC( class_name ) \ static CObject* PASCAL CreateObject(); 可见,在添加了 DECLARE_DYNCREATE 的类定义中增加了: ·DECLARE_DYNAMIC 所提供的成员。 ·CreateObject() 用于动态创建该类对象,并返回对象的指针。 ⑵ IMPLEMENT_DYNCREATE 的定义(在系统头文件“afx.h”中) #define IMPLEMENT_DYNCREATE( class_name, base_class_name ) \ CObject* PASCAL class_name::CreateObject() \ { return new class_name; } \ IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, 0xFFFF, class_name::CreateObject )

  20. ⑶ 将上述宏定义展开后 CMainFrame 类的定义 在类的定义文件中,相当于增加了: class CMainFrame : public CFrameWnd { … protected: CMainFrame(); … }; protected: static CRuntimeClass* PASCAL _GetBaseClass(); public: static const CRuntimeClass classCMainFrame; virtual CRuntimeClass* GetRuntimeClass() const; static CObject* PASCAL CreateObject(); DECLARE_DYNCREATE( CMainFrame )

  21. 在类的实现文件中,相当于增加了: … … IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) CObject* PASCAL CMainFrame::CreateObject() \ { return new CMainFrame; } \ CRuntimeClass* PASCAL CMainFrame::_GetBaseClass() { return RUNTIME_CLASS(CFrameWnd); } const CRuntimeClass CMainFrame::classCMainFrame = { “CMainFrame”, sizeof( class CMainFrame ), 0xFFFF, CMainFrame::CreateObject(), RUNTIME_CLASS(CFrameWnd), NULL}; CRuntimeClass* CMainFrame::GetRuntimeClass() const { return &CMainFrame::classCMainFrame; }

  22. 可见,添加了 DECLARE_DYNCREATE – IMPLEMENT_DYNCREATE 的类将提供比添加 DECLARE_DYNAMIC – IMPLEMENT_DYNAMIC 的类更强的动态性功能服务: ·能够支持 IsKindOf 服务。 ·能提供该类及其基类的类名和大小。 ·能提供该类及其基类的 CRuntimeClass 结构信息。 ·能支持程序运行时动态创建对象的能力。

  23. 3第三对宏 DECLARE_SERIAL – IMPLEMENT_SERIAL和在类中的 添加: 这对宏的作用除了提供对象的动态性的增强功能,还提供了 持续性的增强功能,与 CObject 类提供的持续性功能比较,有 哪些增强呢?我们通过分析这对宏的定义,和一个从 CObject 派生的自定义类 CMyClass 添加了这对宏所发生的变化加以了 解。与前两对宏一样,DECLARE_SERIAL被添加在自定义类的类 定义中,例如在 CMyClass 的定义代码中:

  24. class CMyClass : public CObject { … protected: CMyClass(); DECLARE_SERIAL( CMyClass ) … public: … virtual void Serialize( CArchive& ar ); … }; IMPLEMENT_SERIAL 添加类的实现中,例如: IMPLEMENT_SERIAL( CMyClass, CObject, 1 ) 这对宏的作用就是为所定义的派生类加入必要的补充动态性和 持续性功能的代码:

  25. ⑴ DECLARE_SERIAL 的定义(在系统头文件 “afx.h”中) #define DECLARE_SERIAL( class_name ) \ _DECLARE_DYNCREATE ( class_name ) \ AFX_API friend CArchive& AFXAPI \ operator>> ( CArchive& ar, class_name* &pOb ); 可见,在添加了 DECLARE_SERIAL 的类定义中增加了: ·提供了 DECLARE_DYNCREATE 所增加的成员。 ·增加了一个友元函数 operator>>的定义,表示支持使用运算 符 >>从 CArchive 对象读取数据以重建该类对象的能力。

  26. ⑵ IMPLEMENT_SERIAL 的定义(在系统头文件“afx.h”中) #define IMPLEMENT_SERIAL(class_name,base_class_name, wSchema) \ CObject* PASCAL class_name::CreateObject() \ { return new class_name; } \ _IMPLEMENT_RUNTIMECLASS( class_name, base_class_name, \ wSchema, class_name::CreateObject ) \ AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS( class_name )); \ CArchive& AFXAPI operator>>( CArchive& ar, class_name* &pOb ) \ { pOb=(class_name*)ar.ReadObject(RUNTIME_CLASS( class_name )); \ return ar; } \

  27. ⑶将上述宏定义展开后 CMyClass类的定义相当于增加了: class CMyClass : public CObject { … protected: CMyClass(); … }; protected: static CRuntimeClass* PASCAL _GetBaseClass(); public: static const CRuntimeClass classCMyClass; virtual CRuntimeClass* GetRuntimeClass()const; static CObject* PASCAL CreateObject(); AFX_API friend CArchive& AFXAPI operator>>( CArchive& ar, CMyClass* &pOb ); DECLARE_SERIAL( CMyClass )

  28. 在类的实现文件中,相当于增加了: … … IMPLEMENT_SERIAL( CMyClass, CObject, 1 ) CObject* PASCAL CMyClass::CreateObject() \ { return new CMyClass; } \ CRuntimeClass* PASCAL CMyClass::_GetBaseClass() { return RUNTIME_CLASS( CObject ); } const CRuntimeClass CMyClass::classCMyClass = { “CMyClass”, sizeof(class CMyClass), 1, CMyClass::CreateObject(), RUNTIME_CLASS(CObject), NULL}; CRuntimeClass* CMyClass::GetRuntimeClass() const { return &CMyClass::classCMyClass; } CArchive& AFXAPI operator>> ( CArchive& ar, CMyClass* &pOb ) { pOb = ( CMyClass* )ar.ReadObject(RUNTIME_CLASS( CMyClass )); return ar; }

  29. 可见,添加了 DECLARE_SERIAL – IMPLEMENT_SERIAL 的类能提 供下列动态性和持续性功能服务: ·能够支持 IsKindOf 服务。 ·能提供该类及其基类的类名和大小。 ·能提供该类及其基类的 CRuntimeClass 结构信息。 ·能支持程序运行时动态创建对象的能力。 ·支持使用>>运算符重建该类对象的能力。 其中,重建该类对象的服务可以用下例表示:

  30. CMyClass *pOb; // 初始化 CArchive 类对象 ar CFile file; if( file.Open( “filename.dat”,CFile::modeRead ) ) { CArchive ar( &file, CArchive::load ); ar >>pOb; // 等效于创建 CMyClass 对象,然后执行 pOb->Serialize(ar) } ar.Close(); file.Close(); …

  31. 需要说明的是:ar >> pOb是一个复杂操作过程,它包括了: ·首先从文件中保存的 CRuntimeClass 结构中获取类对象的运行 时信息; ·然后使用该类的 CreateObject 函数创建一个对象,并将该对象 的地址返回给 pOb; ·从文件中读取对象属性的信息,为对象初始化。 注意:同样可以用 ar << pOb 将对象数据信息保存到文件中。

  32. 4 小结:

  33. 7.2 文档类持续性原理 MFC 应用程序框架基本类 —— 文档类的基类是 CDocument, 它是 CCmdTarget 的直接派生类,CObject 的间接派生类。因 此,CDocument 类自然具有 CObject 类的三大特性。如果在它的 派生类中加入三对宏中的任意一个,就可以获得不同层次的动 态性和持续性支持。 文档类在 MFC 应用程序框架中的基本作用是完成应用程序的 数据保存和读取。因此,如何将数据的写入文件和如何从文件 读取数据是文档类中要实现的最主要的操作之一。实现的方法 按是否使用类的持续化功能可以分为两类:

  34. 磁盘文件 操作函数 1文档数据与磁盘文件之间的直接读写 ⑴ 使用 C运行库的打开文件函数,如 fopen,获得带有缓冲区 的 FILE 指针,然后以该指针为参数,调用读写文件的库函 数,如 fread 和 fwrite 以及 fseek(移动文件访问指针),对文 件进行读写,操作结束时调用关闭文件库函数,如 fclose, 关闭文件并释放 FILE 指针。上述操作过程还可以使用 C++ 文件流的相应成员函数或Win32 的其他的文件操作函数,如 _lopen、_lread、_lwrite、_lseek 和_lclose 函数实现。但必须注 意,各类函数之间不可混用。这类操作图示如下: 磁盘文件 文档数据

  35. CFile对象 成员函数 ⑵ 创建一个 MFC 的 CFile 类对象,通过调用 CFile 类相应的功 能的成员函数 Open、Read 或 ReadHuge、Write 或 WriteHuge、 Seek 和 Close等进行磁盘文件的操作,从而实现文档的读写 操作。虽然在实质上与方法 ⑴ 没有什麽区别,但使用这种 方法更安全、更符合面向对象的程序设计方法。这类操作图 示如下: 磁盘文件 文档数据

  36. 2使用 MFC 对象的持续化功能,实现文档和文件之间的读写 ⑴ 隐式调用 Serialize 函数进行文档的读写。采用这类方法应用 程序不直接调用磁盘读写操作,而只依赖持续化处理过程, 避免了在程序中直接使用 CFile 对象。这是因为在 Serialize 函 数和 CFile 对象之间使用一个归档对象(CArchive 类对象)进 行关联。CArchive 对象是 CFile 对象的类型安全的数据缓存, 它同时还保存一个内部标记,用来标识归档是写文件还是读 文件。每次只能有一个活动的归档与被操作文件关联。应用 程序框架会在响应 File 菜单的 Open、Save 和 Save As 命令 时,很好地管理 CFile 对象及 CArchive 对象的自动创建,

  37. 文档对象 数据 Serialize 并为 CFile 对象打开选定的磁盘文件,再将相应的 CArchive 对象与 CFile 对象相关联。而我们需要做的就是在 Serialize 中,将数据插入到归档对象中或从归档对象中析取。在响应 File 菜单的 Open、Save 和 Save As 命令的过程中会调用指定 文档的 Serialize 函数。这类操作图示如下: CFile对象 CArcheive对象 磁盘文件

  38. ⑵显式调用 Serialize 函数进行文档的读写。即不使用在进行文 件操作时应用程序框架自动产生的 CFile 对象和 CArchive 对 象,而是在相关文件操作响应函数中自己创建的 CFile 对象 和 CArchive对象,并且为 CFile 对象打开文件和将 CArchive 对 象与文件相连,然后调用 Serialize 函数完成文档的读写操 作。 注意:使用持续化功能除了保存或恢复类中的数据成员外,还 将保存或恢复包括类的全部运行时信息。

  39. 7.2.1 Serialize 函数原理 例如,在一个自定义文档类 CMyDoc 中包含了两个数据成 员:CStringm_szName和 int m_nAge,该文档类的持续化函数 Serialize 的典型定义如下: void CMyDoc::Serialize( CArchive& ar ) { if( ar.IsStoring() ) { // storing code ar << m_szName << m_nAge; } else { // loading code ar >> m_szName >> m_nAge; } }

  40. Serialize 函数所进行的持续化操作是通过传入的参数 —— CArchive 类对象的引用ar实现的,它提供了: ·与被读写文件相关联的 CFile 类对象的指针。 ·能判断持续化操作的方向,一个给定的 CArchive 类对象在某 一时刻只能读或者写,而不能同时提供读写操作。 ·写操作时,数据被放到 ar的缓冲区中,直到缓冲区满,才将 数据写入它的文件指针所指向的 CFile 对象,即文件中。 ·读操作时,数据从 ar的文件指针指向的 CFile 对象,即文件 中读到缓冲区,然后再从缓冲区读到可持续化的类对象中 (重建类对象)。 这种缓冲机制是类型安全的,减少了访问物理磁盘的次数, 从而提高了应用程序的性能。MFC 框架应用程序中,CArchive 类对象 ar是由应用程序框架动态创建并完成初始化的。

  41. OnNewDocument() OnOpenDocument() GetFile(…) 构造CArchive对象 DeleteContents() SetModifiedFlag(FALSE) DeleteContents() Serialize(…) SetModifiedFlag(FALSE) 文档对象可用 7.2.2 何时调用 Serialize 函数 一般情况下,只有在读取文件和保存文件时,才会发生文档 类对象的持续化,调用 Serialize 函数。 1 建立或打开文档 菜单命令File→Open 菜单命令File→New

  42. OnSaveDocument() GetFile(…) 构造CArchive对象 Serialize(…) SetModifiedFlag(FALSE) 文件保存命令完成 2保存文档 从上述过程可以看出,实现文档对象持续化操作的 Serialize 和 DeleteContents 函数一般都需要重定义,完成特定的操作。当 文档数据被修改时,应该调用 SetModifiedFlag(TRUE),以便在文 件关闭时,提示用户是否保存修改后的文档数据。 菜单命令File→Save As 菜单命令File→Save

  43. 7.2.3 Serialize 函数支持哪些数据类型直接持续化 在 Serialize 函数中能否使用插入运算符“<<”和析取运算符“>>” 支持数据的持续化,取决于该数据类型是否对归档类 CArchive 的插入运算符“<<”和析取运算符“>>”运算符进行了重新定义: 1 对象指针和对象的持续化 friend CArchive& operator<<( CArchive& ar, const CObject* pOb ); throw( CArchiveException, CFileException ); friend CArchive& operator>>( CArchive& ar, CObject *& pOb ); throw( CArchiveException, CFileException, CMemoryException ); friend CArchive& operator>>( CArchive& ar, const CObject *& pOb ); throw( CArchiveException, CFileException, CMemoryException );

  44. 上述对象指针的类必须是 CObject 的派生类,因此,只要在需 要持续化的 CObject 的派生类中加入宏定义 DECLARE_SERIAL, 并在该派生类的实现中加入宏定义 IMPLEMENT_SERIAL,该类 对象就可以通过 CArchive 的插入操作实现写文件功能;而通过 CArchive 的析取操作从文件中读数据并恢复对象,则需要使用该 类的指针。下面是使用对象和对象指针持续化的两种情况: ⑴ 第一种情况:假如一个自定义类 CStudent的定义如下: class CStudent : public CObject { … public: CStringm_strName; int m_nGrade; CTranscript m_transcript; … };

  45. 其中的自定义类 CTranscript 必须从 CObject 类派生,并对持续化 函数 Serialize 进行了重新定义,使得 CTranscript 对象 m_transcript 的属性数据能通过 CArchive对象实现持续化操作。在这种情况 下,CStudent 的持续化函数 Serialize 就可以重定义如下: void CStudent::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << m_strName << m_nGrade; } else { ar >> m_strName >> m_nGrade; } m_transcript.Serialize(ar); }

  46. 上述代码表明 CStudent::Serialize 之所以能够从归档(文件)中 载入学生信息,是因为在 CStudent::Serialize 被调用之前,存放 学生信息的 CStudent 对象已经创建,其内嵌对象 CTranscript 对 象 m_transcript也随着 CStudent 对象的创建同时被创建。因此, 在 CStudent 对象类对象调用 Serialize 时,可以从归档对象 ar 中 将相应数据载入属性成员 m_strName和 m_nGrade 中,并通过 CTranscript::Serialize 函数将相应数据载入到 m_transcript 的相应属 性成员中。可见,对于 CObject 派生类的内嵌对象总是可以通 过重定义持续化函数 Serialize ,并通过Serialize 来实现与文件之间 的数据读写,但却不能实现从文件数据中自动重建类对象。 ⑵第二种情况:如果 CStudent 对象中包含的不是 CTranscript 的 内嵌对象,而是 CTranscript 类的指针对象:

  47. class CStudent : public CObject { … public: … CStringm_strName; int m_nGrade; CTranscript*m_pTranscript; … }; 并且使用宏 DECLARE_SERIAL - IMPLEMENT_SERIAL 对 CTranscript 的持续化功能进行了扩充:

  48. class CTranscript : public CObject { … DECLARE_SERIAL(CTranscript) void Serialize(CArchive &ar); … }; 在 CTranscript 的实现代码中加入了: IMPLEMENT_SERIAL(CTranscript, CObject, 1) 使得 CTranscript 类指针指向的对象数据能被插入到 CArchive 类对 象关联的文件中,并能从 CArchive 类对象关联的文件中析取数 据、重建对象,使 CTranscript 类指针指向它。

  49. 在这种情况下, CStudent::Serialize 函数的操作代码应按如下方 式来编写: void CStudent::Serialize(CArchive& ar) { if (ar.IsStoring()) ar << m_strName << m_nGrade << m_pTranscript; else ar >> m_strName >> m_nGrade >> m_pTranscript; } 与前面的 CStudent::Serialize 函数定义比较,CTranscript 对象是如 何与文件进行数据读、写的呢?

  50. ⑴ 当 CTranscript 对象被写入归档时,通过指针 m_pTranscript调 用 CTranscript::Serialize 实现将类的运行时信息和属性数据一 起写入归档; ⑵ 当从归档读入时,这对宏所产生的代码将完成如下操作: ·读入类 CTranscript 的运行时信息; ·引用指针 m_pTranscript 动态创建相应类 CTranscript 对象; ·一旦对象被创建,便可以被调用 CTranscript::Serialize 完成 将数据从归档读入对象。

More Related