孙鑫第13课笔记 文档与串行化

本文详细介绍了MFC中的文档串行化思想,包括CArchive类的使用、如何修改文档标题、文档对象的序列化过程以及可串行化类的实现步骤。通过示例代码展示了如何在文件读写中应用CArchive,以及如何处理CObArray中的对象串行化和内存管理。同时,讨论了在文档对象数据销毁时如何避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

serialization是一种思想,这个思想是——对象是连续的,这意味着我们可以在程序退出时把它们保存到磁盘上,程序重新运行是又恢复它们。(摘自VC++6.0技术内幕)

 

 

 

1.CArchive类


 

CArchive类没有基类,它的作用类似于一个二进制流,用于文件的输入输出;

它也重载了操作符"<<",">>",用法和cout和cin一样,但CArchive不仅可以输出基本类型的数据,还可以输出CObject类型的对象(包括从CObject中派生的类型)

CArchive类使用实例:(结合文件对话框的使用)

void CGraphicView::OnFileRead()
{
 // TODO: Add your command handler code here
       CFileDialog fileDlg(TRUE);                               //创建一个文本对话框,TRUE表示用于文本打开
       fileDlg.m_ofn.lpstrTitle="我的文件打开对话框";
       fileDlg.m_ofn.lpstrFilter="Text Files(*.txt)/0*.txt/0All Files(*.*)/0*.*/0/0";//这里注意过滤器的格式
 
       if(IDOK==fileDlg.DoModal())
      {
              CFile file(fileDlg.GetFileName(),CFile::modeRead);
              CArchive ar(&file,CArchive::load);
              int i;
              float f;
              CString str;
              CString fstr;
              ar>>i>>f>>str;
              fstr.Format("%d,%f,%s",i,f,str);
              MessageBox(fstr);
        }
}

 

void CGraphicView::OnFileWrite()
{
 // TODO: Add your command handler code here
     CFile file("1.txt",CFile::modeWrite|CFile::modeCreate);
     CArchive ar(&file,CArchive::store);
     int i=1;
     float f=11.1f;
     CString str="MFC13";
     ar<<i<<f<<str;
}

 

 

2.修改文档标题

 

 

方法1:在CDoc中的OnNewDocument中的return前插入(程序启动或文件新建命令被响应最终都会调用该函数)

            SetTitle("Never give up!");

方法2:修改IDR_MAINFRAME字符串:在两个/n之间插入字符串。在这里不仅可以设置文档标题,仔细看一下可以发现共有七处可以插入字符串,不同位置含义不同,含义按顺序分别如下:

 

  • CDocTemplate::windowTitle                主窗口上的标题栏上的字符串
  • CDocTemplate::docName                    文档名称
  • CDocTemplate::fileNewName              文档类型名称,出现在File/New对话框中
  • CDocTemplate::filterName                  过滤器
  • CDocTemplate::filterExt                      过滤器格式(扩展名). 这两处的格式见第12课笔记
  • CDocTemplate::regFileTypeId             注册表文件类型
  • CDocTemplate::regFileTypeName        注册表文件类型名称 

ps:以上项目名称都可以作为参数传递给GetDocString(),该函数用来查看这些项目中的的具体内容

 

3.文档串行化


 

文件新建和文件保存操作最终要调用下面函数:

void CGraphicDoc::Serialize(CArchive& ar)
{
      if (ar.IsStoring())
     {
       // TODO: add storing code here
             int i=1;
             float f=11.1f;
            CString str="MFC13";
            ar<<i<<f<<str;
     }
      else
     {
          // TODO: add loading code here

          int i;
          float f;
          CString str;
          CString fstr;
          ar>>i>>f>>str;
          fstr.Format("%d,%f,%s",i,f,str);
          AfxMessageBox(fstr);
    }
}

 

如果保存后立即打开刚才存储的文件,MessageBox中并不会出现我们刚才输入的文字,因为MFC会判断出此时和数据相关联的文件对象仍然存在,就不会再调用Serialize函数;

关闭应用程序窗口后再运行一次,这时MessageBox里就可以看到我们刚才输入的文字了。

 

 4.可串行化的类


  每个可串行化的类都必须有一个Serialize成员函数,要想使一个类可串行化,可以经过一下五个步骤实现:

1.把要实现串行化的类从CObject(或其派生类)中派生;

2.重写Serialize成员函数

3.在类声明使用DECLARE_SERIAL宏:DECLARE_SERIAL(class_name)//注意没有分号

4.构造不带参数的构造函数;//还不太理解为什么

5.在类的实现文件中(.cpp文件)使用IMPLEMENT_SERIAL宏:IMPLEMENT_SERIAL(class_name, base_class_name,wSchema/*版本号*/)//也没有分号

 

在这里,孙老师其实一共讲了2个不同的类串行化的实现,下面让我们拆开一个一个来看

一、CGraph类串行化的实现

   (1)改变其头文件让其派生于CObject

   (2)分别在头文件和实现文件中添加上面说的两个宏

   (3)在view中增加一个CObArray类型成员,并用add()把保存着图形的CGraph对象保存到其中;

   (4)重写CGraphicDoc::Serialize(CArchive& ar)函数:在文档类中CGraphicDoc中获得view指针(方法在下面详解),并通过该指针把CObArray中的元素进行串行输入或输出

  void CGraphicDoc::Serialize(CArchive& ar)
{
 POSITION pos=GetFirstViewPosition();
 CGraphicView *pView=(CGraphicView*)GetNextView(pos);
      if (ar.IsStoring())
     {
       // TODO: add storing code here
               int nCount=pView->m_obArray.GetSize();
               ar<<nCount;
               for(int i=0;i<nCount;i++)
             {
                         ar<<pView->m_obArray.GetAt(i);
              }
       }
       else
      {
        ar>>nCount;
        CGraph *pGraph;
        for(int i=0;i<nCount;i++)
       {
         ar>>pGraph;
         pView->m_obArray.Add(pGraph);
       }
     }

}

 

   (5)实际上,调用CDoc类的Serialize()运行到ar<<pView->m_obArray.GetAt(i)(或ar>>pGraph)时,会调用CGraph类的Serialize()函数,因此需要实现该函数:


void CGraph::Serialize(CArchive& ar)
{
        if(ar.IsStoring())
       {
        ar<<m_nDrawType<<m_ptOrigin<<m_ptEnd;
       }
       else
       {
        ar>>m_nDrawType>>m_ptOrigin>>m_ptEnd;
        }  

 

二、利用CObArray实现

(1)同上面一样,先完成对CGraph的串行化工作

(2)这次把CObArray的对象作为CDoc的变量,通过在View类中得到CDoc的指针(实现方法下面会具体讲到),完成对该序列的添加;

(3)CObArray本身支持串行化,这时不需要具体实现CDoc中Serialize函数,而只需要在函数末尾调用CObArray的Serialize函数即可

(4)和上面很相似,CObArray的Serialize函数又会调用CGraph的Serialize函数来完成自己的串行化。

 

5.文档对象数据的销毁


我们知道,通过new在堆上创建的变量,一般都要通过delete来回收,否则会发生内存泄露。

在view类中的LBUTTONUP的响应函数里,我们动态创建了一个CGraph的对象,并把它加入CObArray中:

CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);

 

因此,我们不仅要释放CGraph对象的内存,还要把它从CObArray中移除。那么这些操作的代码应该加在哪里呢?

首先,单文档程序只有一个文档对象CDocument,因此在创建新文档或打开其他文档时该文档对象就会被重复使用,在重复使用前要做的第一个工作,当然是清除文档的内容,保证文档时干净的。

我们注意到在文档创建的响应函数OnNewDocmen中会首先调用一个DeleteContents的虚函数(并由此推断打开另一个文档时也是这样的),因此,我们只要在把释放内存的代码写入DeleteContents函数中就可以了。

 

下面具体说一下这些代码该如何实现(这里孙老师讲的就精髓了)

1.必须保证先delete掉CGraph的指针,再从CObArray中移除。因为一旦先从CObArray移除掉,我们就无法找到CGraph指针,也就无法释放它所指向的内存了

2.关于GetSize(),其实我们可以把CObArray看做时MFC为我们提供的一个链表类(其实也是这样),当一个链表删除某个元素时,它的总数目就会自动-1,最后的两端代码实际上就是解决了这个问题:

void CGraphicDoc::DeleteContents()
{
 // TODO: Add your specialized code here and/or call the base class
 int nCount;
 nCount=m_obArray.GetSize();
/*方法一:在for循环中只delete,不移除,这样就保证链表的总数不会变化 ,for循环结束后一起移除*/

/*for(int i=0;i<nCount;i++)
 {
  delete m_obArray.GetAt(i);
  //m_obArray.RemoveAt(i);
 }
 m_obArray.RemoveAll();*/

/*方法二:你变我也变,每次都把总数减1,这时一种巧妙的实现方式*/
 while(nCount--)
 {
  delete m_obArray.GetAt(nCount);
  m_obArray.RemoveAt(nCount);
 }
 CDocument::DeleteContents();

 

 


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值