温州哪里做双眼皮好:序列化

来源:百度文库 编辑:偶看新闻 时间:2024/04/29 15:54:26

一 :序列化:创建可序列化的类

使类可序列化需要五个主要步骤。下面列出了这些步骤并在以后章节内进行了解释:

1从 CObject 派生类(或从 CObject 派生的某个类中派生)。

2重写 Serialize 成员函数。

3使用 DECLARE_SERIAL 宏(在类声明中)。

4定义不带参数的构造函数。

5为类在实现文件中使用 IMPLEMENT_SERIAL 宏。

如果直接调用 Serialize 而不是通过 CArchive 的“>>”和“<<”运算符调用,则序列化不需要最后三个步骤。

从 CObject 派生类

在 CObject 类中定义了基本的序列化协议和功能。正如在 CPerson 类的下列声明中所示,通过从 CObject 中(或从 CObject 的派生类中)派生类,可获得对 CObject 的序列化协议及功能的访问权限。 

重写 Serialize 成员函数

在 CObject 类中定义的 Serialize 成员函数实际上负责对捕获对象的当前状态所必需的数据进行序列化。Serialize 函数具有 CArchive 参数,该函数使用其来读写对象数据。CArchive 对象具有成员函数 IsStoring,该成员函数指示 Serialize 正在存储(即正在写入数据)还是正在加载(即正在读取数据)。用 IsStoring 的结果作为参考,使用输出运算符 (<<) 将对象数据插入到 CArchive 对象中或使用输入运算符 (>>) 提取数据。

假定一个类是从 CObject 派生的并具有两个新成员变量,分别为 CString 和 WORD 类型。下列类声明段显示了新成员变量和重写的 Serialize 成员函数的声明:

class CPerson : public CObject

{

public:

    DECLARE_SERIAL( CPerson )

    // empty constructor is necessary

    CPerson(){};

    CString m_name;

    WORD   m_number; 

    void Serialize( CArchive& archive ); 

    // rest of class declaration

};

重写 Serialize 成员函数,调用 Serialize 的基类版本以确保序列化对象的继承部分。插入或提取您的类所特定的成员变量。 输出运算符及输入运算符与存档类交互作用以读写数据。下面的示例显示了如何实现以上声明的 CPerson 类的 Serialize:

void CPerson::Serialize( CArchive& archive )

{

    // call base class function first

    // base class is CObject in this case

    CObject::Serialize( archive );

    // now do the stuff for our specific class

    if( archive.IsStoring() )

        archive << m_name << m_number;

    else

        archive >> m_name >> m_number;

}

也可使用 CArchive::Read 及 CArchive::Write 成员函数来读写大量未键入的数据。

使用 DECLARE_SERIAL 宏

在支持序列化的类的声明中需要 DECLARE_SERIAL 宏,如下所示:

class CPerson : public CObject

{

    DECLARE_SERIAL( CPerson )

    // rest of declaration follows...

};

定义不带参数的构造函数

反序列化对象(从磁盘上加载)后,MFC 重新创建这些对象时,需要一个默认的构造函数。反序列化进程将用重新创建对象所需的值填充所有成员变量。

可将该构造函数声明为公共的、受保护的或私有的。如果使该构造函数成为受保护的或私有的,请确保它将仅由序列化函数使用。该构造函数必须使对象处于这样一种状态:必要时,可允许将其安全删除。

注意   如果忘记在使用 DECLARE_SERIAL 及 IMPLEMENT_SERIAL 宏的类中定义不带参数的构造函数,将在使用 IMPLEMENT_SERIAL 宏的行上得到“没有可用的默认构造函数”编译器警告。

在实现文件中使用 IMPLEMENT_SERIAL 宏

IMPLEMENT_SERIAL 宏用于定义从 CObject 中派生可序列化类时所需的各种函数。在类的实现文件 (.CPP) 中使用这个宏。该宏的前两个参数是类名和直接基类的名称。

该宏的第三个参数是架构编号。架构编号实质上是类对象的版本号。架构编号使用大于或等于零的整数。(请不要将该架构编号与数据库术语混淆。)

MFC 序列化代码在将对象读取到内存时检查该架构编号。如果磁盘上对象的架构编号与内存中类的架构编号不匹配,库将引发 CArchiveException,防止程序读取对象的不正确版本。

如果要使 Serialize 成员函数能够读取多个版本(即,读取用应用程序的不同版本写入的文件),可将 VERSIONABLE_SCHEMA 值作为 IMPLEMENT_SERIAL 宏的参数。有关用法信息和示例,请参见 CArchive 类的 GetObjectSchema 成员函数。

以下示例显示了如何将 IMPLEMENT_SERIAL 用于从 CObject 派生的 CPerson 类。

IMPLEMENT_SERIAL( CPerson, CObject, 1 )

正如序列化:序列化对象文章中所讨论的,一旦具有可序列化的类,就可以序列化类的对象。

 

二 :序列化:序列化对象

序列化:创建可序列化的类一文说明如何使类可序列化。一旦具有可序列化的类,就可以通过 CArchive 对象将该类的对象序列化到文件和从文件序列化该类的对象。本文将解释:

CArchive 对象的定义。

两种方法创建 CArchive。

使用 CArchive << 和 >> 运算符的方法。

通过存档存储和加载 CObjects。

可使框架创建可序列化文档的存档或自己显式创建 CArchive 对象。通过使用 CArchive 的“<<”和“>>”运算符或在某些情况下通过调用 CObject 派生类的 Serialize 函数,可在文件和可序列化对象间传输数据。

什么是 CArchive 对象

CArchive 对象提供了一个类型安全缓冲机制,用于将可序列化对象写入 CFile 对象或从中读取可序列化对象。通常,CFile 对象表示磁盘文件;但是,它也可以是表示“剪贴板”的内存文件(CSharedFile 对象)。

给定的 CArchive 对象要么存储数据(即写入数据或将数据序列化),要么加载数据(即读取数据或将数据反序列化),但决不能同时进行。CArchive 对象的寿命只限于将对象写入文件或从文件读取对象的一次传递。因此,需要两个连续创建的 CArchive 对象将数据序列化到文件,然后从文件反序列化数据。

当存档将对象存储到文件时,存档将 CRuntimeClass 名称附加到这些对象。然后,当另一个存档将对象从文件加载到内存时,将基于这些对象的 CRuntimeClass 动态地重新构造 CObject 派生的对象。通过存储存档将给定对象写入文件时,该对象可能被引用多次。然而,加载存档将仅对该对象重新构造一次。有关存档如何将 CRuntimeClass 信息附加到对象以及重新构造对象(考虑可能的多次引用)的详细信息,请参见技术说明 2。

将数据序列化到存档时,存档积累数据,直到其缓冲区被填满为止。然后,存档将其缓冲区写入 CArchive 对象指向的 CFile 对象。同样,当您从存档中读取数据时,存档会将数据从文件读取到它的缓冲区,然后从缓冲区读取到反序列化的对象。这种缓冲减少了物理读取硬盘的次数,从而提高了应用程序的性能。

创建 CArchive 对象有两种方法:

通过框架隐式创建 CArchive 对象

显式创建 CArchive 对象

通过框架隐式创建 CArchive 对象

最普通且最容易的方法是使框架代表“文件”菜单上的“保存”、“另存为”和“打开”命令为文档创建 CArchive 对象。

以下是应用程序的用户从“文件”菜单上发出“另存为”命令时,框架所执行的操作:

显示“另存为”对话框并从用户获取文件名。

打开用户命名的文件作为 CFile 对象。

创建指向该 CFile 对象的 CArchive 对象。在创建 CArchive 对象时,框架将模式设置为“存储”(即写入或序列化),而不是“加载”(即读取或反序列化)。

调用在 CDocument 派生类中定义的 Serialize 函数,将 CArchive 对象的引用传递给该函数。

然后,文档的 Serialize 函数将数据写入 CArchive 对象(刚作了解释)。从 Serialize 函数返回时,框架先销毁 CArchive 对象,再销毁 CFile 对象。

因此,如果让框架为文档创建 CArchive 对象,您所要做的一切是实现写入存档和从存档中读取的文档的 Serialize 函数。您还必须为文档的 Serialize 函数直接或间接依次序列化的任何 CObject 派生对象实现 Serialize。

显式创建 CArchive 对象

除了通过框架将文档序列化之外,在其他场合也可能需要 CArchive 对象。例如,可能要序列化到达或来自剪贴板的数据,由 CSharedFile 对象表示。或者,可能要使用用户界面来保存与框架提供的文件不同的文件。在这种情况下,可以显式创建 CArchive 对象。使用下列过程,用与框架采用的相同方式来执行此操作。

显式创建 CArchive 对象

构造 CFile 对象或从 CFile 导出的对象。

按照下面示例所示,将 CFile 对象传递到 CArchive 的构造函数:

CFile theFile;

theFile.Open(..., CFile::modeWrite);

CArchive archive(&theFile, CArchive::store);

CArchive 构造函数的第二个参数是指定存档将用于向文件中存储数据还是用于从文件中加载数据的枚举值。对象的 Serialize 函数通过调用存档对象的 IsStoring 函数来检查该状态。  

当完成向 CArchive 对象存储数据或从该对象中加载数据时,关闭该对象。虽然 CArchive 对象(和 CFile 对象)会自动关闭存档(和文件),好的做法是显式执行,因为这使从错误恢复更为容易。有关错误处理的更多信息,请参见异常:捕捉和删除异常一文。 

关闭 CArchive 对象

以下示例阐释了如何关闭 CArchive 对象:

archive.Close();

theFile.Close();

使用 CArchive 的“<<”和“>>”运算符

CArchive 提供“<<”和“>>”运算符,用于向文件中写入简单的数据类型和 CObjects 以及从文件中读取它们。

通过存档将对象存储在文件中

以下示例显示了如何通过存档将对象存储在文件中:

CArchive ar(&theFile, CArchive::store);

WORD wEmployeeID;

...

ar << wEmployeeID;

从先前存储在文件中的值加载对象

以下示例显示了如何从先前存储在文件中的值加载对象:

CArchive ar(&theFile, CArchive::load);

WORD wEmployeeID;

...

ar >> wEmployeeID;

通常,通过 CObject 派生类的 Serialize 函数中的存档将数据存储到文件中或从文件中加载数据,必须已用 DECLARE_SERIALIZE 宏来声明这些函数。将 CArchive 对象的引用传递到 Serialize 函数。调用 CArchive 对象的 IsLoading 函数以确定是否已调用 Serialize 函数来从文件中加载数据或将数据存储到文件中。

可序列化的 CObject 派生类的 Serialize 函数通常具有以下形式:

void CPerson::Serialize(CArchive& ar)

{

    CObject::Serialize(ar);

    if (ar.IsStoring())

    {

        // TODO:  add storing code here

    }

    else

    {

    // TODO:  add loading code here

    }

}

上面的代码模板与 AppWizard 为该文档(从 CDocument 派生的类)的 Serialize 函数所创建的代码模板完全相同。由于存储代码和加载代码总是并行,该代码模板有助于写的代码更容易复查,如下例中所示:

void CPerson:Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        ar << m_strName;

        ar << m_wAge;

    }

    else

    {

        ar >> m_strName;

        ar >> m_wAge;

    }

}

库将 CArchive 的“<<”和“>>”运算符定义为第一操作数,将下列数据类型和类类型定义为第二操作数:

CObject* SIZE 和 CSize float

WORD CString POINT 和 CPoint

DWORD BYTE RECT 和 CRect

double LONG CTime 和 CTimeSpan

int COleCurrency COleVariant

COleDateTime COleDateTimeSpan  

注意   通过存档存储及加载 CObjects 需要额外注意。有关更多信息,请参见通过存档存储和加载 CObjects。

CArchive 的“<<”和“>>”运算符总是返回 CArchive 对象的引用,该引用为第一操作数。这使您可以链接运算符,如下所示:

BYTE bSomeByte;

WORD wSomeWord;

DWORD wSomeDoubleWord;

...

ar << bSomeByte << wSomeWord << wSomeDoubleWord; 

通过存档存储和加载 CObjects

通过存档存储及加载 CObject 需要额外注意。在某些情况下,应调用对象的 Serialize 函数,其中,CArchive 对象是 Serialize 调用的参数,与使用 CArchive 的“<<”或“>>”运算符不同。要牢记的重要事实是:CArchive 的“>>”运算符基于由存档先前写到文件的 CRuntimeClass 信息构造内存中的 CObject。

因此,是使用 CArchive 的“<<”和“>>”运算符还是调用 Serialize,取决于是否需要加载存档基于先前存储的 CRuntimeClass 信息动态地重新构造对象。在下列情况下使用 Serialize 函数:

反序列化对象时,预先知道对象的确切的类。

反序列化对象时,已为其分配了内存。

警告   如果使用 Serialize 函数加载对象,也必须使用 Serialize 函数存储对象。不要使用 CArchive 的“<<”运算符先存储,然后使用 Serialize 函数进行加载;或使用 Serialize 函数存储,然后使用 CArchive 的“>>”运算符进行加载。

以下示例阐释了这些情况:

class CMyObject : public CObject

{

// ...Member functions

   public:

   CMyObject() { }

   virtual void Serialize( CArchive& ar ) { }

// Implementation

   protected:

   DECLARE_SERIAL( CMyObject )

};

class COtherObject : public CObject

{

   // ...Member functions

   public:

   COtherObject() { }

   virtual void Serialize( CArchive& ar ) { }

// Implementation

protected:

   DECLARE_SERIAL( COtherObject )

}; 

class CCompoundObject : public CObject

{

   // ...Member functions

   public:

   CCompoundObject();

   virtual void Serialize( CArchive& ar ); 

// Implementation

protected:

   CMyObject m_myob;    // Embedded object

   COtherObject* m_pOther;    // Object allocated in constructor

   CObject* m_pObDyn;    // Dynamically allocated object

   //..Other member data and implementation 

   DECLARE_SERIAL( CCompoundObject )

}; 

IMPLEMENT_SERIAL(CMyObject,CObject,1)

IMPLEMENT_SERIAL(COtherObject,CObject,1)

IMPLEMENT_SERIAL(CCompoundObject,CObject,1)  

CCompoundObject::CCompoundObject()

{

   m_pOther = new COtherObject; // Exact type known and object already

            //allocated.

   m_pObDyn = NULL;    // Will be allocated in another member function

            // if needed, could be a derived class object.

void CCompoundObject::Serialize( CArchive& ar )

{

   CObject::Serialize( ar );    // Always call base class Serialize.

   m_myob.Serialize( ar );    // Call Serialize on embedded member.

   m_pOther->Serialize( ar );    // Call Serialize on objects of known exact type.

   // Serialize dynamic members and other raw data

   if ( ar.IsStoring() )

   {

      ar << m_pObDyn;

      // Store other members

   }

   else

   {

      ar >> m_pObDyn; // Polymorphic reconstruction of persistent

            // object

            //load other members

   }

}

总之,如果可序列化的类将嵌入的 CObject 定义为成员,则不应使用该对象的 CArchive 的“<<”和“>>”运算符,而应调用 Serialize 函数。同时,如果可序列化的类将指向 CObject(或从 CObject 派生的对象)的指针定义为成员,但在自己的构造函数中将其构造为其他对象,则也应调用 Serialize。

序列化:序列化与数据库输入/输出的对比

如同在数据库应用程序中一样,应用程序基于每个事务读写数据,因此本文解释了何时将文档对象和序列化用于基于文件的输入/输出 (I/O) 以及其他 I/O 技术在何时适用。如果不使用序列化,则不需要“文件打开”、“保存”和“另存为”命令。所涵盖的主题包括:

有关处理输入/输出的建议

处理数据库应用程序中的“文件”菜单

有关处理输入/输出的建议

是否使用基于文件的 I/O 取决于如何响应下列决策树中的问题:

应用程序中的主要数据驻留在磁盘文件中吗?

是的,主要数据驻留在磁盘文件中:

在使用“文件打开”时应用程序将整个文件读取到内存中,在使用“保存文件”时应用程序又将整个文件写回磁盘吗?

是的:这是默认的 MFC 文档情况。使用 CDocument 序列化。

不是:基于事务更新文件通常是这种情况。“MFC 高级概念”的 CHKBOOK 示例是这种情况的一个例子。基于每个事务更新文件,不需要 CDocument 序列化。

不是,主要数据不驻留在磁盘文件中:

数据驻留在 ODBC 数据源中吗?

是的,数据驻留在 ODBC 数据源中:

使用 MFC 的数据库支持。正如在什么是 MFC 数据库编程模型?一文中所讨论的,该情况的标准 MFC 实现包括存储 CDatabase 对象的 CDocument 对象。应用程序也可能读写辅助文件,这正是应用程序向导的“数据库视图和文件都支持”选项的目的。这种情况下,应将序列化用于辅助文件。 

不是,数据不驻留在 ODBC 数据源中。

该情况的示例:数据驻留在非 ODBC DBMS 中;通过其他机制(如 OLE 或 DDE)读取数据。

在这种情况下,将不使用序列化,而且应用程序也不会有“打开”及“保存”菜单项。可能还要将 CDocument 用作主基,正如 MFC ODBC 应用程序使用文档来存储 CRecordset 对象一样。但是不能使用框架的默认“文件”->“打开/保存”文档序列化。 

为了支持“文件”菜单上“打开”、“保存”和“另存为”命令,框架提供文档序列化。序列化将数据(包括从 CObject 类派生的对象)读取和写入永久性存储器(通常是磁盘文件)中。序列化易于使用并用于多种需要,但它可能在许多数据访问应用程序中不适用。数据访问应用程序通常基于每个事务更新数据。这些应用程序更新受事务影响的记录,而不是一次读写整个数据文件。

COPY FROM MSDN