《【教学课件】第6章数据和文档.ppt》由会员分享,可在线阅读,更多相关《【教学课件】第6章数据和文档.ppt(46页珍藏版)》请在三一办公上搜索。
1、第6章数据和文档,6.1CString类,6.2使用简单数组集合类,6.3使用CFile类,6.4文档序列化,6.5综合应用,6.1CString类,6.1.1 BSTR、const char*、LPCTSTR和CString 什么是BSTR、LPSTR以及LPWSTR呢?BSTR(Basic STRing,Basic字符串)是一个OLE CHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下也得到了较为
2、广泛的使用。LPSTR和LPWSTR是Win32和Visual C+所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(0)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在Visual C+中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图6.1所示。,图6.1 字符串类型表示的含义,6.1CString类,例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C+的const char*相映射,
3、而LPTSTR映射为 char*。一般地,Visual C+中还有下列类型定义:#ifdef UNICODE typedef LPWSTR LPTSTR;typedef LPCWSTR LPCTSTR;#else typedefLPSTR LPTSTR;typedef LPCSTR LPCTSTR;#endif,6.1CString类,CString类支持字符串类型,并可通过CString类构造函数和一些运算符进行构造。CString类构造函数原型如下:CString();CString(const CString/从C语言样式的字符串来创建city,6.1CString类,当然,也可使用CS
4、tring类的Format成员函数将任意数据类型转换成CString字符串。Format成员函数使用C语言的printf的格式样式进行创建,例如:CString str;str.Format(Floating point:%.2fn,12345.12345);str.Format(Left-justified integer:%.6dn,35);若将一个CString字符串向上述字符串类型进行转换,则可使用CString类提供了的const char*、LPCTSTR运算符以及AllocSysString和SetSysString成员函数等。例如:/将CString向LPTSTR转换的方法一
5、CString theString(This is a test);LPTSTR lpsz=new TCHARtheString.GetLength()+1;/TCHAR在Unicode平台中等同于WCHAR(16位Unicode字符),在ANSI中等价于char。_tcscpy(lpsz,theString);/将CString向LPTSTR转换的方法二 CString theString(This is a test);LPTSTR lpsz=(LPTSTR)(LPCTSTR)theString;/将CString向BSTR转换 CString str(This is a test);BS
6、TR bstrText=str.AllocSysString();SysFreeString(bstrText);/用完释放,6.1CString类,6.1.2 字符串的字符访问在CString类中,可以用SetAt和GetAt来设置或获取指定字符串中的字符,也可以使用运算符“”来直接操作。它们的函数原型描述如下:void SetAt(int nIndex,TCHAR ch);其中,参数nIndex用来指CString对象中的某个字符的索引(从零开始),它的值必须大于或等于0,且应小于由GetLength的返回的值。ch用来指定要插入的字符。这样,就可将一个CString对象看作是一个字符数组
7、,SetAt成员函数用来改写指定索引的字符。TCHAR GetAt(int nIndex)const;该函数用来返回由nIndex指定索引位置(从零开始)的TCHAR字符。例如:CString str(abcdef);ASSERT(str.GetAt(2)=c);/断言返回的字符与c相等。在MFC中,断言机制常用于调试,当断言失败后,程序在此中断,/然后弹出对话框,询问是否进入调试或选择其他操作 TCHAR operator(int nIndex)const;这是一个运算符重载函数,即将一个CString对象看作是一个字符数组,使用下标运行符“”,通过指定下标值nIndex来获取相应的字符。例
8、如:CString str(abc);ASSERT(str1=b);,6.1CString类,6.1.3 清空及字符串长度 清空CString对象可用Empty函数,判断CString对象是否为空用函数IsEmpty,获取CString对象的字符串长度用函数GetLength,它们的原型如下:void Empty();该函数强迫CString对象为空(字符串长度为0)并释放相应的内存。BOOL IsEmpty()const;该函数用来判断CString对象是否为空(字符串长度为0),“是”为TRUE,“否”为FALSE。int GetLength()const;该函数用来获取CString对象
9、的字符串长度(字符个数),这个长度不包括字符串结尾的结束符。例如:CString s(abcdef);ASSERT(s.GetLength()=6);6.1.4 提取和大小写转换 CString类提供许多用来从一个字符串中提取部分字符串的操作函数,也提供了大小写转换函数。下面分别说明。CString Left(int nCount)const;该函数用来从CString对象中提取最前面的nCount个字符作为要提取的子字符串(简称子串)。如果nCount超过了字符串的长度,则整个字符串都被抽取。CString Mid(int nFirst)const;CString Mid(int nFirs
10、t,int nCount)const;,6.1CString类,该函数函数用从CString对象中提取一个从nFirst(从零开始的索引)指定的位置开始的nCount个字符的子串。若nCount不指定,则提取的子串是从nFirst开始直到字符串结束。CString Right(int nCount)const;该函数用来从CString对象中提取最后面的nCount个字符作为要提取的子字符串。如果nCount超过了字符串的长度,则整个字符串都被抽取。void MakeLower();该函数用来将CString对象的所有字符转换成小写字符。void MakeUpper();该函数用来将CStri
11、ng对象的所有字符转换成大写字符。void TrimLeft();void CString:TrimLeft(TCHAR chTarget);void CString:TrimLeft(LPCTSTR lpszTargets);该函数用来将CString对象最左边的空格、空格和tab字符或chTarget指定的字符或lpszTargets指定的子串删除。void TrimRight();void CString:TrimRight(TCHAR chTarget);void CString:TrimRight(LPCTSTR lpszTargets);,6.1CString类,该函数用来将CSt
12、ring对象最后边的空格、空格和tab字符或chTarget指定的字符或lpszTargets指定的子串删除。例如:CString strBefore;CString strAfter;strBefore=Hockey is Best!;strAfter=strBefore;strAfter.TrimRight(!);/strAfter中的字符串“Hockey is Best!”变成了“Hockey is Best”strBefore=Hockey is Best?!?!?!?!;strAfter=strBefore;strAfter.TrimRight(?!?);/strAfter中的字符串
13、“Hockey is Best?!?!?!?!”变成了“Hockey is Best”,6.2使用简单数组集合类,1.简单数组集合类的构造及元素的添加 对简单数组集合类构造的方法都是一样的,均是使用各自的构造函数,它们的原型如下:CByteArray CByteArray();CDWordArray CDWordArray();CObArray CObArray();CPtrArray CPtrArray();CStringArray CStringArray();CUIntArray CUIntArray();CWordArray CWordArray();下面的代码说明了简单数组集合类的两
14、种构造方法:CObArray array;/使用默认的内存块大小 CObArray*pArray=new CObArray;/使用堆内存中的默认的内存块大小 为了有效使用内存,在使用简单数组集合类之前最好调用成员函数SetSize 设置此数组的大小,与其对应的函数是GetSize,用来返回数组的大小。它们的原型如下:void SetSize(int nNewSize,int nGrowBy=-1);int GetSize()const;,6.2使用简单数组集合类,其中,参数nNewSize用来指定新的元素的数目(必须大小或等于0)。nGrowBy表示当数组需要扩展时允许可添加的最少元素数目,默
15、认时为自动扩展。向简单数组集合类添加一个元素,可使用成员函数Add和Append,它们的原型如下:int Add(CObject*newElement);int Append(const CObArray 其中,Add函数是向数组的末尾添加一个新元素,且数组自动增1。如果调用的函数SetSize的参数nGrowBy 的值大于1,那么扩展内存将被分配。此函数返回被添加的元素序号,元素序号就是数组下标。参数newElement表示要添加的相应类型的数据元素。而Append函数是向数组的末尾添加由src指定的另一个数组的内容。函数返回加入的第一个元素的序号。,6.2使用简单数组集合类,2.访问简单数
16、组集合类的元素 在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可使用“”操作符,例如:/CObArray:operator 示例 CObArray array;CAge*pa;/CAge是一个用户类 array.Add(new CAge(21);/添加一个元素 array.Add(new CAge(40);/再添加一个元素 pa=(CAge*)array0;/获取元素0 array0=new CAge(30);/替换元素0;/CObArray:GetAt示例 CObArray array;array.Add(new CAge(21);/元素 0 array.Add(new
17、 CAge(40);/元素 1,6.2使用简单数组集合类,3.删除简单数组集合类的元素 删除简单数组集合类中的元素一般需要进行以下几个步骤:(1)使用函数GetSize和整数下标值访问简单数组集合类中的元素。(2)若对象元素是在堆内存中创建的,则使用delete操作符删除每一个对象元素。(3)调用函数RemoveAll删除简单数组集合类中的所有元素。例如,下面代码是一个CObArray的删除示例:CObArray array;CAge*pa1;CAge*pa2;array.Add(pa1=new CAge(21);array.Add(pa2=new CAge(40);ASSERT(array.
18、GetSize()=2);for(int i=0;iarray.GetSize();i+)delete array.GetAt(i);array.RemoveAll();需要说明的是:函数RemoveAll是删除数组中的所有元素,而函数RemoveAt(int nIndex,int nCount=1)则表示要删除数组中从序号为nIndex元素开始的,数目为nCount的元素。,6.3使用CFile类,6.3.1 文件的打开和关闭在MFC中,使用CFile打开一个文件通常使用下列两个步骤:构造一个不带任何参数的CFile对象;(1)调用成员函数Open并指定文件路径以及文件标志。(2)CFile
19、类的Open函数原型如下:BOOL Open(LPCTSTR lpszFileName,UINT nOpenFlags,CFileException*pError=NULL);其中,lpszFileName用来指定一个要打开的文件路径,该路径可以是相对的、绝对的或是一个网络文件名(UNC)。nOpenFlags用来指定文件打开的标志,它的值见表6.1。pError用来表示操作失败产生的CFileException指针,CFileException是一个与文件操作有关的异常处理类。函数Open操作成功时返回TRUE,否则为FALSE。,6.3使用CFile类,表6.1 CFile类的文件访问方式
20、,6.3使用CFile类,例如,下面的代码将显示如何用读写方式创建一个新文件:char*pszFileName=c:testmyfile.dat;CFile myFile;CFileException fileException;if(!myFile.Open(pszFileName,CFile:modeCreate|CFile:modeReadWrite),代码中,若文件创建打开有任何问题,Open函数将在它的最后一个参数中返回CFileException(文件异常类)对象,TRACE宏将显示出文件名和表示失败原因的代码。使用AfxThrowFileException函数将获得更详细的有关错
21、误的报告。与文件“打开”相反的操作是“关闭”,可以使用Close函数来关闭一个文件对象,若该对象是在堆内存中创建的,还需调用delete来删除它(不是删除物理文件)。,6.3使用CFile类,6.3.2 文件的读写和定位 CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:UINT Read(void*lpBuf,UINT nCount);此函数将文件中指定大小的数据读入指定的缓冲区,并返回向缓冲区传输的字节数。需要说明的是,这个返回值可能小于nCount,这是因为可能到达了文件的结尾。void Write(const void*lpBuf,UINT nCount);此函数将缓冲区
22、的数据写到文件中。参数lpBuf用来指定要写到文件中的数据缓冲区的指针,nCount表示从数据缓冲区传送的字节数。对于文本文件,每行的换行符也被计算在内。LONG Seek(LONG lOff,UINT nFrom);此函数用来定位文件指针的位置,若要定位的位置是有效的,则此函数将返回从文件开始的偏移量。否则,返回值是不定的且激活一个CFileException对象。参数lOff用来指定文件指针移动的字节数,nFrom表示指针移动方式,它可以是CFile:begin(从文件的开始位置)、CFile:current(从文件的当前位置)或CFile:end(从文件的最后位置,但lOff必须为负值才
23、能在文件中定位,否则将超出文件)等。需要说明的是,文件刚打开时,默认的文件指针位置为0,即文件的开始位置。另外,函数void SeekToBegin()和DWORD SeekToEnd()分别将文件指针移动到文件开始和结尾位置,对于后者还将返回文件的大小。,6.3使用CFile类,6.3.3 获取文件的有关信息 CFile 还支持获取文件状态,包括文件是否存在、创建与修改的日期和时间、逻辑大小和路径等。BOOL GetStatus(CFileStatus 若指定文件的状态信息成功获得,该函数返回TRUE,否则返回FALSE。其中,参数lpszFileName用来指定一个文件路径,这个路径可以是
24、相对的或是绝对的,但不能是 网络文件名。rStatus用来存放文件状态信息,它是一个CFileStatus结构类型,该结构具 有下列成员:CTime m_ctime 文件创建日期和时间 CTime m_mtime 文件最后一次修改日期和时间 CTime m_atime 文件最后一次访问日期和时间 LONGm_size 文件大小的字节数 BYTE m_attribute 文件属性 char m_szFullName_MAX_PATH 文件名,6.3使用CFile类,需要说明的是,static形式的GetStatus函数将获得指定文件名的文件状态,并将文件名复制至m_szFullName中。该函数
25、仅获取文件状态,并没有真正打开文件,这对于测试一个文件的存在性是非常有用的。例如下面的代码:CFile theFile;char*szFileName=c:testmyfile.dat;BOOL bOpenOK;CFileStatus status;if(CFile:GetStatus(szFileName,status)/该文件已存在,直接打开 bOpenOK=theFile.Open(szFileName,CFile:modeWrite);else/该文件不存在,需要使用modeWrite方式创建它 bOpenOK=theFile.Open(szFileName,CFile:modeCre
26、ate|CFile:modeWrite);,6.3使用CFile类,6.3.4 CFile示例 如图6.2所示,单击浏览按钮,将弹出文件“打开”对话框,从中选择一个文件时,编辑框上方显示出该文件的路径名、创建时间和文件大小,并在编辑框中显示出该文件的内容。,图6.2 CFile示例运行结果,6.3使用CFile类,例Ex_File 使用CFile(1)创建一个默认的对话框应用程序Ex_File。(2)将对话框的标题设为“使用CFile”。删除“TODO:在这里设置对话控制。”静态文本控件和 取消按钮,将确定按钮标题改为“退出”。(3)打开对话框网格,参看图6.2的控件布局,添加静态文件控件ID
27、C_STATIC_TITLE(选中“垂直居中”和“凹陷”选项)、一个编辑框IDC_EDIT1(选中“多行”、“水平滚动”、“垂直滚动”和“自动垂直滚动”选项)和一个“打开”按钮IDC_BUTTON_OPEN。(4)打开MFC ClassWizard的Member Variables页面,为IDC_STATIC_TITLE控件添加Value类型变量m_strTitle,为IDC_EDIT1控件添加Value类型变量m_strContent。(5)再次打开MFC ClassWizard,切换到Messsage Maps页面,为CEx_FileDlg类添加按钮IDC_BUTTON_OPEN 的BN_
28、CLICKED消息映射,保留默认的映射函数名,并添加下列代码:(6)编译运行并测试。,6.4文档序列化,6.4.1 文档模板和字串资源 1.文档模板 文档应用程序框架是在程序运行时就开始构造的,在单文档应用程序(设为Ex_SDI)的应用程序类InitInstance函数中,可以看到这样的代码:BOOL CEx_SDIApp:InitInstance()CSingleDocTemplate*pDocTemplate;pDocTemplate=new CSingleDocTemplate(IDR_MAINFRAME,/资源IDRUNTIME_CLASS(CEx_SDIDoc),/文档类RUNTIM
29、E_CLASS(CMainFrame),/主框架窗口类RUNTIME_CLASS(CEx_SDIView);/视图类 AddDocTemplate(pDocTemplate);return TRUE;,6.4文档序列化,类似的,多文档模板类CMultiDocTemplate的构造函数也有相同的定义。如下面的代码(设为Ex_MDI):BOOL CEx_MDIApp:InitInstance()CMultiDocTemplate*pDocTemplate;pDocTemplate=new CMultiDocTemplate(IDR_EX_MDITYPE,/资源ID RUNTIME_CLASS(CE
30、x_MDIDoc),/文档类 RUNTIME_CLASS(CChildFrame),/MDI文档窗口类 RUNTIME_CLASS(CEx_MDIView);/视图类 AddDocTemplate(pDocTemplate);/创建主框架窗口 CMainFrame*pMainFrame=new CMainFrame;if(!pMainFrame-LoadFrame(IDR_MAINFRAME)return FALSE;m_pMainWnd=pMainFrame;return TRUE;,6.4文档序列化,2.文档模板字串资源 从前面的单文档模板类可以看出,为了能将菜单、加速键、图标等资源加载到
31、应用程序框架中,这些资源的标识符都设为了IDR_MAINFRAME。事实上,在MFC文档序列化流程(后面会讨论到)中,文档标题和通用文件“打开”和“保存”对话框的过滤器中文件类型还必需能够在相关的资源给予指定,这个资源就是文档模板字串资源,它是String Table(字符串)资源列表中的IDR_MAINFRAME项,其内容如下(以单文档应用程序Ex_SDI为例):Ex_SDInnEx_SDInnnExSDI.DocumentnEx_SDI Document 可以看出,IDR_MAINFRAME所标识的字符串被分成了一些以“n”结尾的子串,这些子串共有7段,每段都有特定的用途,其含义如表6.2
32、所示。,6.4文档序列化,表6.2 文档模板字符串的含义,6.4文档序列化,但对于MDI来说,上述的字串分别由IDR_MAINFRAME和IDR_EX_MDITYPE(若项目名为Ex_MDI)组成;其中,IDR_MAINFRAME表示窗口标题,而IDR_EX_MDITYPE表示后6项内容。它们的内容如下:IDR_MAINFRAME:Ex_MDI图6.3 Advanced Options对话框5036214 IDR_EX_MDITYPE:nEx_MDInEx_MDInnnExMDI.DocumentnEx_MDI Document 实际上,文档模板字串资源内容既可直接通过上述字串资源编辑器进行修
33、改,也可以在文档应用程序创建向导的第四步中,单击高级按钮,通过“高级选项(Advanced Options)”对话框中的“文档字符模板(Document Template Strings)”页面来指定,如图6.3所示(以单文档应用程序Ex_SDI为例)。图中的数字表示该项的含义与表6.3中对应串号的含义相同。,6.4文档序列化,6.4.2 文档序列化过程 MFC文档序列化过程包括:创建空文档、打开文档、保存文档和关闭文档这几个操作,下面来阐述它们的具体运行过程。1.创建空文档 应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通过CWinApp:Proce
34、ssShellCommand间接调用CWinApp的另一个非常有用的成员函数OnFileNew,并依次完成下列工作:(1)构造文档对象,但并不从磁盘中读数据。(2)构造主框架类CMainFrame的对象,并创建该主框架窗口,但不显示。(3)构造视图对象,并创建视图窗口,也不显示。(4)通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。注意与 AddDocTemplate函数的区别,AddDocTemplate函数建立的是“类”之间的联系。(5)调用文档对象的CDocument:OnNewDocument虚函数,并调用CDocument:DeleteContents虚函数来清除文档
35、对象的内容。,6.4文档序列化,(6)调用视图对象的CView:OnInitialUpdate虚函数对视图进行初始化操作。(7)调用框架对象的CFrameWnd:ActiveFrame虚函数,以便显示出带有菜单、工具栏、状态栏以及视图窗口的主框架窗口。在单文档应用程序中,文档、主框架以及视图对象仅被创建一次,并且这些对象在整个运行过程中都有效。CWinApp:OnFileNew函数被InitInstance函数所调用。但当用户选择“文件(File)”菜单中的“新建(New)”时,CWinApp:OnFileNew也会被调用,但与InitInstance不同的是,这种情况下不再创建文档、主框架以
36、及视图对象,但上述过程的最后三个步骤仍然会被执行。2.打开文档 当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“打开(Open)”命令(ID号为ID_FILE_OPEN)映射到CWinApp的OnFileOpen成员函数。这一结果可以从应用类(.cpp)的消息入口处得到验证:BEGIN_MESSAGE_MAP(CEx_SDIApp,CWinApp)ON_COMMAND(ID_FILE_NEW,CWinApp:OnFileNew)ON_COMMAND(ID_FILE_OPEN,CWinApp:OnFileOpen)/Standard print setup c
37、ommand ON_COMMAND(ID_FILE_PRINT_SETUP,CWinApp:OnFilePrintSetup)END_MESSAGE_MAP()OnFileOpen函数还会进一步完成下列工作:,6.4文档序列化,(1)弹出通用文件“打开”对话框,供用户选择一个文档。(2)文档指定后,调用文档对象的CDocument:OnOpenDocument虚函数。该函数将打开文档,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive对象用于 数据的读取,接着又自动调用Serialize函数。(3)调用视图对象的CView:OnInitialUpdate虚函数。
38、除了使用“文件(File)”“打开(Open)”菜单项外,用户也可以选择最近使用过的文件列表来打开相应的文档。在应用程序的运行过程中,系统会记录下4个默认最近使用过的文件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会最近使用过的文件名称显示在“文件(File)”菜单中。3.保存文档(1)当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作:(2)弹出通用文件“保
39、存”对话框,让用户提供一个文件名。(3)调用文档对象的CDocument:OnSaveDocument虚函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。,6.4文档序列化,需要说明的是:只有在保存文档之前还没有存过盘(亦即没有文件名)或读取的文档是“只读”的,OnFileSave函数才会弹出通用“保存”对话框。否则,只执行第二步。“文件(File)”菜单中还有一个“另存为(Save As)”命令,它是与文档类CDocument的 OnFileSaveAs函数相关联。不管文档有没有保存过,OnFileSaveAs都会执行上述两个步骤。上述文档存盘的必要操作都
40、是由系统自动完成的。4.关闭文档当用户试图关闭文档(或退出应用程序)时,应用程序会根据用户对文档的修改与否来进一步完成下列任务:(1)若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。当用户选择“是”,则应用程序执行OnFileSave过程。(2)调用CDocument:OnCloseDocument虚函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类CDocument的DeleteContents清除文档数据。,6.4文档序列化,6.4.3 CArchive类和序列化操作从上述的单文档序列化过程可以看出:打开和保存文档时,系统都会自动调用Serialize函数
41、。事实上,MFC AppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数,通过在该函数中添加代码可达到实现数据序列化的目的。例如,在Ex_SDI单文档应用程序的文档类中有这样的默认代码:void CEx_SDIDoc:Serialize(CArchive&ar)if(ar.IsStoring()/当文档数据需要存盘时/TODO:add storing code here else/当文档数据需要读取时/TODO:add loading code here 代码中,Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring的结果
42、是“真”还是“假”就可决定向文档写或读数据。,6.4文档序列化,CArchive(归档)类提供对文件数据进行缓存,它同时还保存一个内部标记,用来标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作,它提供“”运算符,用于向文件写入简单的数据类型以及从文件中读取它们,表6.6列出了CArchive所支持的的常用数据类型。,表6.3 ar中可以使用运算符的数据类型,6.4文档序列化,除了“”运算符外,CArchive类还提供成员函数ReadString和WriteString用来从一个文件对象中读写一行文本,它们的原型如下:Bool R
43、eadString(CString 其中,lpsz用来指定读或写的文本内容,nMax用来指定可以读出的最大字符个数。需要说明的是,当向一个文件写一行字符串时,字符 0和n都不会写到文件中,在使用时要特别注意。下面举一个简单的示例来说明Serialize函数和CArchive类的文档序列化操作方法。例Ex_SDIArchive 一个简单的文档序列化示例(1)创建一个默认的单文档应用程序Ex_SDIArchive。(2)打开String Table资源,将文档模板字串资源IDR_MAINFRAME内容修改为:文档序列化操作nnn自定义文件(*.my)n.mynExSDIArchive.Docume
44、ntnEx_SDI Document,6.4文档序列化,(3)为CEx_SDIArchiveDoc类添加下列成员变量:public:char m_chArchive100;/读写数据时使用CString m_strArchive;/读写数据时使用BOOL m_bIsMyDoc;/用于判断文档(4)在CEx_SDIArchiveDoc类构造函数中添加下列代码:CEx_SDIArchiveDoc:CEx_SDIArchiveDoc()m_bIsMyDoc=FALSE;(5)在CEx_SDIArchiveDoc:OnNewDocument函数中添加下列代码:BOOL CEx_SDIArchiveDo
45、c:OnNewDocument()if(!CDocument:OnNewDocument()return FALSE;strcpy(m_chArchive,6.4文档序列化,(6)在CEx_SDIArchiveDoc:Serialize函数中添加下列代码:(7)编译运行并测试。程序运行后,选择“文件”“另存为”菜单,指定一个文档名1.my,然后选择“文件”“新建”菜单,再打开该文档,结果就会弹出对话框,显示该文档的内容,如图6.4所示。,图6.4 显示文档内容,6.4文档序列化,6.4.4 CArchive类和CFile类关联事实上,文档应用程序框架就是将一个外部磁盘文件和一个CArchive
46、 对象关联起来。当然,这种关联还可直接通过CFile来进行。例如:CFile theFile;theFile.Open(.,CFile:modeWrite);CArchive archive(,6.5综合应用,在用文件来存取数据时,最大的难度是要保证读取的数据的正确性。若有一个记录结构,包括学生的姓名(字符串)、学号(字符串)以及三门课程成绩,则如何保证在文件读写的正确性呢?一种办法是将记录定义成C结构体类型,使用CFile来操作,这是避开MFC文档序列化机制而进行的方法,虽有效但缺少对MFC机制和类的应用,因为在C+中,C结构体被看作是类的一种简单形式。另一种方法,是将记录声明成一个类,并使
47、该类具体可序列化特性。一个可序列化的类的对象可以在Serialize函数使用CArchive对象通过“”来正确地向文件进行写入和读取操作。,图6.5 Ex_Student运行结果,6.5综合应用,下面来看一个综合应用,如图6.5所示。它首先通过对话框来输入一个学生记录,记录包括学生的姓名、学号和三门成绩,用类CStudent来描述,并使其可序列化。然后将记录内容保存到一个对象数组集合类对象中,最后通过文档序列化将记录保存到一个文件中。当添加记录或打开一个记录文件,还会将数据显示在文档窗口(即视图)中。例Ex_Student 文档序列化示例1)添加用于学生记录输入的对话框(1)创建一个默认的单文
48、档应用程序Ex_Student。(2)向应用程序中添加一个对话框资源,打开属性对话框将其字体设置为“宋体,9”,标题改为“添加学生记录”,取默认的ID号IDD_DIALOG1,将OK和Cancel按钮的标题分别改为“确 定”和“取 消”。(3)参看图6.5的控件布局,用编辑器为对话框添加如下表6.4所示的一些控件。,6.5综合应用,表6.4 添加的控件,6.5综合应用,(4)双击对话框模板或按Ctrl+W快捷键,为对话框资源IDD_DIALOG1创建一个对话框类CInputDlg。(5)打开ClassWizard的Member Variables标签,在Class name中选择CInputD
49、lg,选中所需的控件ID标识符,双击鼠标或单击Add Variables按钮。依次为表6.5控件增加成员变量。,表6.5 控件变量,6.5综合应用,2)添加一个CStudent类并使该类可序列化 一个可序列化的类必须是CObject的一个派生类,且在类声明中,需要包含DECLARE_SERIAL宏调用,而在类的实现文件中包含IMPLEMENT_SERIAL宏调用,这个宏有3个参数:前2个参数分别表示类名和基类名,第3个参数表示应用程序的版本号。最后还需要重载Serialize函数,使该类的数据成员进行相关序列化操作。由于使用ClassWizard无法添加一个CObject派生类,因此必须手动进
50、行。为了简化类文件的复杂性,这里创建的这个CStudent类的声明和实现代码是直接添加在Ex_StudentDoc.h和Ex_StudentDoc.cpp文件中的,具体如下:3)添加并处理菜单项(1)在菜单资源的主菜单中增加顶层菜单项“学生记录(&S)”,在该顶层菜单项中增加子菜单“添加(&A)”(ID_STUREC_ADD)。(2)用ClassWizard为CEx_StudentDoc类添加ID_STUREC_ADD的COMMAND消息映射,并在映射函数中添加下列代码:,6.5综合应用,void CEx_StudentDoc:OnSturecAdd()CInputDlg dlg;if(IDO