




已阅读5页,还剩24页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第8章 文档序列化大多数应用程序都为用户提供了数据的保存功能,这些数据可能是电子表格、字处理文档、一组数据或图形等等。从磁盘存储器上存取这些数据的工作往往是通过文件操作或者数据库操作来完成的。关于数据库操作的内容,我们将在后面的章节中进行详细的介绍,在本章的内容中,我们主要讨论如何通过文件操作实现一般意义上的数据存取工作。通过文件操作来实现数据的存取工作通常有两种工作方式:一种是使对象具有序列化;另一种方法就是直接使用CFile对象处理文件。本章就将对这两部分内容分别做出详解。8.1 序列化在MFC当中,对象的序列化功能主要是通过文档/视图结构中特有的文档对象的序列化机制来实现的。本节,我们将详细介绍如何使用序列化机制来实现对象的序列化。序列化,简单地说就是向一个持久性的存储媒体如磁盘文件保存对象或读取对象的过程。序列化分为两部分,当把应用程序数据以文件形式存储在系统磁盘中时,叫做序列化;当从磁盘文件中恢复应用程序数据的状态时,叫做反序列化,这两个部分的组合构成了Visual C+中的应用程序对象的序列化。8.1.1 CArchive类和Serialize函数Visual C+应用程序中的序列化是通过CArchive类来实现的。CArchive类总是与一个CFile对象相关联,CArchive类是作为CFile对象的输入输出流而设计的,如图8-00所示,它使用经过重载的C+流入()操作符从存储应用程序数据的文件中实现读取和写入数据,而将这些数据保存到磁盘文件中的工作由CArchive对象指示CFile对象来完成。应用程序框架应用程序对象 序列化函数CFile CArchive类图8-00 CArchive类和CFile类可以实现序列化的类即从CObject继承而来的类,有一个叫做Serialize的成员函数,序列化工作主要是在这个函数当中进行的。当应用程序读取或写入文件时,文档对象的Serialize函数被调用,并传递用于从文件读取或向文件写入数据的CArchive对象。在Serialize函数中,要遵循的典型逻辑是通过调用CArchive类的IsStoring或IsLoading函数来判定当前行为是正在对文件写入还是读取。根据这两个函数中任何一个的返回值即可判定应用程序需要从CArchive类的I/O流中读取还是向其写入。当用户在打开或保存拥有文档对象数据的文件或者使用文档对象的Open、Save、Save As菜单命令时,MFC便会自动调用Serialize函数,一个典型的Serialize函数如下所示:void CAge:Serialize( CArchive& ar ) CObject:Serialize( ar ); if( ar.IsStoring() ) ar m_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,具体操作步骤如下:l 步骤1:新建一个MFC单文档应用程序,工程名为EX08_00或用户自定义。l 步骤2:为新建的工程先实现第6章的简单绘图功能(详细步骤请参见第6章)。1. 在资源面板中修改原来的标准菜单,新插入一个菜单名为“绘图”,下面有四个菜单项“点”、“线”、“矩形”、“椭圆”,修改它们的ID分别为:ID_GRAPH_DOT、ID_GRAPH_LINE、ID_GRAPH_RECTANGLE、ID_GRAPH_ELLIPSE。2. 在CEX08_00View类中添加两个成员变量CPoint m_ptOrigin和int m_nType,分别表示绘图的起点和绘图的类型,并在构造函数中初始化为0和-1。3. 在CEX08_00View中加入四个菜单项“点”、“线”、“矩形”和“椭圆”的WM_COMMAND消息的响应函数OnGraphDot、OnGraphLine、OnGraphRectangle 、OnGraphEllipse,在消息响应函数中设置变量m_nType的值。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的值画相应的图形。l 步骤3:给工程添加一个可序列化的类CGraph。1 新建一个类CGraph,从CObject派生。打开工作台ClassView页面,鼠标右击最顶层的EX08_00 classes,在弹出的快捷方式菜单中选择New Class,在弹出的New Class对话框上,在Class type中要选Generic Class,在类名Name中输入CGraph,单击Base class下面列表框中Derived From下面高亮显示的第一栏,输入将要派生的基类CObject,后面类型为publice,如图8-01所示,然后单击OK。图8-01 添加新类CGraph当单击OK按钮添加该类时,会弹出一个如图8-02所示的对话框,该对话框提示Class Wizard无法为从CObject派生出的CGraph类找到合适的头文件。我们不用理会它,在此消息框中单击确定按钮即可,因为合适的头文件已经包含在CGraph类中。2 在CGraph类中重载Serialize()成员函数。我们要实现序列化,先对其进行改造,在工作台的ClassView页面中选择CGraph类,单击鼠标右键,选择Add Member Function增加一个成员函数,在弹出的对话框中Functiong Type中输入void,在Function Declaretion编辑框中输入 Serialize(CArchive& ar),然后选择Public,按OK即可。然后在ClassView中可以看到这个函数。图8-02 创建新类时的警告信息3. 在类CGraph的头文件中,加入DECLARE_SERIAL宏,代码如下:class CGraph : public CObject public:void Serialize(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: implementation of the CGraph class./#include stdafx.h#include EX08_00.h#include Graph.h#ifdef _DEBUG#undef THIS_FILEstatic char THIS_FILE=_FILE_;#define new DEBUG_NEW#endif/ Construction/Destruction/IMPLEMENT_SERIAL(CGraph,CObject,1)CGraph:CGraph()可见,将该宏的调用添加在构造函数前,也不需要分号。IMPLEMENT_SERIAL宏用于定义一个从CObject派生的可序列化类的各种函数。宏的第一和第二个参数分别代表可序列化的类名和该类的直接基类。第三个参数是对象的版本号,它是一个大于或等于零的整数。MFC序列化代码在将对象读入内存时检查版本号。如果磁盘文件上的对象的版本号和内存中的对象的版本号不一致,MFC将抛出一个CArchiveException异常,阻止程序读入一个不匹配版本的对象。现在,我们就可以象使用标准MFC类一样使用CGraph的序列化功能了。l 步骤4:构造CGraph类,做准备工作。1. 在类CGraph的头文件中添加三个成员变量m_ptOrigin、m_ptEnd、m_nType。我们既然想保存下来所画的图形,那么至少要保留住关于这些图形的一些信息,不管用户画的是线、矩形还是椭圆,它们都有一个共同点:就是由两点决定这个图形,那么从起点到终点画的到底是什么图形,就要看m_nType的值了,因此这里定义了两个CPoint型的变量,用于保存用户所画的一组图形的各个起点和终点;另一个为int型变量用来指定所画的每个图形的类型。class CGraph : public CObject public:CPoint m_ptOrigin; /记录起始点CPoint m_ptEnd; /记录终点int m_nType; /记录画图类型void Serialize(CArchive& ar);CGraph();virtual CGraph();DECLARE_SERIAL(CGraph);2. 给类CGraph添加一个带参数的构造函数鼠标右击CGraph类,在弹出的快捷方式菜单中选择Add Member Function,函数类型(Function Type)编辑框中什么都不填,因为构造函数没有返回值,函数声明(Function Declaration)为CGraph(int m_drawType,CPoint m_ptFrom, CPoint m_ptTo),编辑这个带参数的构造函数,添加如下代码:CGraph:CGraph(int m_drawType, CPoint m_ptFrom, CPoint m_ptTo)m_nType=m_drawType;m_ptOrigin=m_ptFrom;m_ptEnd=m_ptTo;在这个对象构造函数中,用传递到构造函数的画图类型、起点和终点三个参数来初始化类CGraph中相应意义的三个变量。l 步骤5:在文档类CEX08_00Doc中定义一个成员变量,用于保存每一个图形对象。现在已经有了一个可以用来表示用户所绘图形的对象,那么接下来的重点是如何将这些对象保存下来。当用户画一个图形,就产生一个这样的图形对象,因此,这个对象是动态的不断增长的。CObArray类是一个对象数组类,它可以动态调整自己的大小以适应放在它里面的元素的个数。它可以存放任何从CObject类派生出的对象(如前面的CGraph对象),它的大小只受系统的内存空间的限制。MFC中其他动态数组类包括CStringArray、CByteArray、CWordArray、CDWordArray、CPtrArray,它们的不同之处在于存放的对象的类型。这里,我们鼠标右击CEX08_00Doc类,在弹出的快捷方式菜单中选择Add Member Variable,变量类型为CObArray,变量名为m_obArray。l 步骤6:将用户画的每一个图形对象保存到m_obArray中。要将图形对象保存到对象数组m_obArray中,首先就要得到对象的绘图类型和起点、终点,然后创建一个新的图形对象,并把它加入到对象数组m_obArray中。我们知道,当用户按下鼠标左键,然后随之拖动出一个图形,最后当鼠标抬起的时候,那么就是这个图形对象生成的时候,因此,应该在CEX08_00View:OnLButtonUp函数中保存图形对象。在OnLButtonUp函数中的原来代码的尾部添加如下代码:void CEX08_00View:OnLButtonUp(UINT nFlags, CPoint point) CGraph *pGraph=new CGraph(m_nType,m_ptOrigin, point);GetDocument()-m_obArray.Add(pGraph);CView:OnLButtonUp(nFlags, point);这段代码中,首先声明一个CGraph类的一个指针对象,并且用该类的带参数的构造函数来构建这个对象,构造函数中的参数就是OnLButtonUp函数中用于画图的类型和起点、终点。用户再回头看一看CGraph类中带参数的构造函数,就会明白,实际上构造函数传递过来的参数是为了初始化CGraph类的三个成员变量m_ptOrigin、m_ptEnd、m_nType,这三个成员变量构成了一个CGraph类对象,然后将该对象通过m_obArray.Add保存到对象数组中。由于对象数组是在文档类中定义的,在视图类中不能直接引用,因此,前边需要调用GetDocument函数来取得访问文档类的权利。最后别忘了在视图类的实现文件中添加#include Graph.h,将类CGraph的头文件包含进来。l 步骤7:在CGraph类中完成绘图功能。CGraph类对象中包含着三个重要的画图参数,因此,这个对象是可以绘制自身的,当视图类需要重绘图形时,它只需要向该类发送一条消息,告诉它要绘制自己就可以了。鼠标右击CGraph类,选择Add Member Function,函数类型(Function Type)为void,函数声明(Function Declaration)为Draw(CDC* pDC),在该函数中添加如下代码:void CGraph:Draw(CDC *pDC)switch(m_nType)case 0:pDC-SetPixel(m_ptEnd.x,m_ptEnd.y,RGB(255,0,0);break;case 1:pDC-MoveTo(m_ptOrigin);pDC-LineTo(m_ptEnd);break;case 2:pDC-Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptEnd.x,m_ptEnd.y);break;case 3:pDC-Ellipse(m_ptOrigin.x,m_ptOrigin.y,m_ptEnd.x,m_ptEnd.y);break;default:break;在CGraph类中绘图,Draw函数中用到三个绘图参数就是CGraph类的三个成员变量。l 步骤8:在CEX08_00View:OnDraw函数中绘图,在OnDraw函数中添入如下代码:void CEX08_00View:OnDraw(CDC* pDC)CEX08_00Doc* pDoc = GetDocument();ASSERT_VALID(pDoc);/ TODO: add draw code for native data hereif (pDoc-m_obArray.GetSize()for(int i=0;im_obArray.GetSize();i+)(CGraph*)pDoc-m_obArray.GetAt(i)-Draw(pDC);在这个函数中,首先判断数组对象中是否有元素,即是否有图形需要绘制,如果没有就什么都不做,如果有元素,那么就通过for循环从文档类的数组对象中依次取出图形对象(包括三个重要绘图参数),然后调用图形对象的Draw函数,即第7步骤中的Draw函数。到现在,这个绘图程序就具备了重绘的功能,无论窗口怎样改变,用户所绘制的图形依然显示在窗口上。下面我们继续给它添加保存和再显示的功能。l 步骤9:保存和再显示图形。1. 打开CEX08_00Doc:Serialize函数,将原来的代码删除,添加如下代码:void CEX08_00Doc:Serialize(CArchive& ar)m_obArray.Serialize(ar);这里利用了CObArray类的功能。在文档类的Serialize函数中调用对象数组的Serialize函数,该对象数组将会把指令向下传递到对象数组中,并调用每个对象的Serialize函数。因此,下面我们将完善CGraph类的Serialize函数。2. 完善CGraph:Serialize()函数,代码如下:void CGraph:Serialize(CArchive &ar)if (ar.IsStoring()arm_nTypem_ptOriginm_nTypem_ptOriginm_ptEnd;在上面的代码中我们用到了和和m_nTypem_ptOriginm_ptEnd;这一句,其中表示从ar中读出数据m_nType, m_ptOrigin和 m_ptEnd,这个符号及可以连用,亦可以分开来用,如ar m_nType;ar m_ptOrigin;同样arm_nTypem_ptOriginm_ptEnd;中的是把数据存入ar中。需要注意的是,三个变量读取和保存的顺序一定要和在类CGraph中带参数的构造函数中三个参数的声明顺序是一致的。现在编译运行这个程序,随意画几个图形,然后选择菜单“文件”|“保存”,在弹出的“保存”对话框中输入文件名将所画图形保存到文件中,然后关闭应用程序,再重新运行该应用程序,选择 “文件”|“打开” 菜单,在弹出的“打开”对话框上双击先前保存有图形的文件,就可以看到先前保存的图形再一次显示在窗口上了。8.1.4 删除文档数据当用户单击“打开”和“新建”菜单项或按钮时会自动调用DeleteContents虚函数,这个时候是删除文档数据的最好时机。下面将以上一小节的例子EX08_00工程为基础,讲述如何删除文档数据。打开工作台的Class View页面,鼠标右击CEX08_00Doc类,在弹出的快捷方式菜单中选择Add Virtual Function,在弹出的对话框上左边的列表框中找到DeleteContents函数,双击该函数将其加入到文档类中,用户可以在这个函数中删除文档数据。通常,用户在删除文档数据时,经常犯以下两种错误:1错误方法一:for (int i=0;im_obArray.GetSize();i+)delete (CGraph*)m_obArray.GetAt(i);m_obArray.RemoveAll();原因:每循环一次,m_obArray.GetSize()返回的值都会减小,造成数据的漏删。2错误方法二:int index= m_obArray.GetSize();for (int i=0;iindex;i+)delete (CGraph *) m_obArray.GetAt(i); m_obArray.RemoveAt(i);原因:每删除一个数组元素,数组都会重新排序,它的下标会变。正确的删除方法应按如下方式:void CEX08_00Doc:DeleteContents() / TODO: Add your specialized code here and/or call the base classint index=m_obArray.GetSize();while(index-)delete (CGraph*)m_obArray.GetAt(index);m_obArray.RemoveAll();CDocument:DeleteContents();最后不要忘了在文档类CEX08_00Doc实现文件的前面添加#include Graph.h语句,将类CGraph的头文件包含进来。序列化简化了对象的保存和载入,为对象提供了持久性。但是,序列化本身仍具有一定的局限性。由于序列化一次从文件中载入所有对象,因此,它不适合于大文件编辑器和数据库。对于数据库和大文件编辑器,它们每次只是从文件中读入一部分。此时,就不应该采用文档的序列化机制来直接读取和保存文件了。另外,使用外部文件格式(预先定义的文件格式而不是本应用程序定义的文件格式)的程序一般也不使用文档的序列化。8.2 CFile类文件的处理包括创建及打开文件、数据读取、关闭文件等一系列操作。在MFC中,包含文件相关操作的基类为CFile类。CFile类是从CObject类派生而来的,CFile类用来处理正常文件的I/O操作,它直接供无缓冲的、二进制磁盘输入/输出服务,并且通过其派生类间接支持文本文件和内存文件。8.2.1 CFile类的构造函数CFile类有三个构造函数,其原型如下:1. CFile();2. CFile(int nFile);3. CFile( LPCTSTR lpszFileName, UINT nOpenFlags );第一种方式,在声明CFile实例后,不需要设定任何参数。第二种方式,有一个参数nFile为一个已打开文件的句柄,就是建立已打开文件的另一个实例,它不做任何错误检查,直接用构造函数打开。第三种方式,参数lpszFileName指定想要操作的文件的名称和路径的字符串,路径可以是相对的或绝对的;参数nOpenFlags指定文件的共享和存取方式,该参数可以用OR来组合下面所列的值:l CFile:modeCreate指示构造函数创建一个新文件,如果该文件已存在,则该文件截短为0l CFile:modeRead 打开文件只用于读l CFile:modeReadWrite 打开文件用于读写l CFile:modeWrite 打开文件只用于写l CFile:modeNoInberit 阻禁止该文件被子进程继承l CFile:shareDenyNone 打开文件,不允许其它进程读或写该文件。如果该文件已由其它任何进程用兼容方式打开,则Create将失败l CFile:shareDenyWrite 打开文件,不允许其它进程写该文件。如果该文件已由其它任何进程用兼容方式或写方式打开,则Create将失败l CFile:shareDenyRead 打开文件,不允许其它进程读该文件。如果该文件已由其它任何进程用兼容方式或读方式打开,则Create将失败l CFile:shareExclusive 以独占方式打开文件,不允许其它进程写该文件。如果该文件已用其它读或写方式打开,即使是当前进程打开,则构造也将失败l CFile:shareCompat 以兼容方式打开文件,允许给定机器上任何进程打开该文件任意次。如果该文件已用其它任何共享方式打开,构造将失败l CFile:typeText 设置文本方式,对回车换行进行特殊处理,它只用于派生类l CFile:typeBinary 设置二进制方式,它只用于派生类以上描述了文件共享和存取标志。在第三种构造函数中,一个存取许可和一个共享选项是必需的。modeCreate 和modeNoInberit方式是可选的。8.2.2 打开文件CFile类用Open函数来创建和打开文件,用Open创建新文件,必须有一个文件名,并且选择一定的打开方式,函数原形如下:virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL);第一个参数lpszFileName指定想要操作的文件的名称和路径的字符串。第二个参数nOpenFlags指定文件的共享和存取方式,用法同CFile类第三种构造函数中的nOpenFlags参数用法相同。第三个参数pError,表示文件异常处理对象的指针,当文件打开失败时,系统会将文件异常消息存放在CFileException打开文件的m_cause数据成员。用户可以根据调用Open函数后的返回值来简单的判断文件是否正常打开或创建。若用户想了解是什么原因造成打开文件失败时,就必须指定CFileException对象,该对象专门处理文件操作上的问题。在CFileException对象内的m_cause数据成员便负责记录文件操作到底出了哪些问题。8.2.3 读写数据文件创建及打开后,就可以对数据进行读取或写入了。CFile对磁盘文件的定点、读取和写入操作分别通过函数Write,Read和Seek进行的。1. 写入数据Write函数virtual void Write( const void* lpBuf, UINT nCount );该函数用于将缓冲区数据写到文件中,参数lpBuf指向用户定义的缓冲区的指针,该参数中包含用户将要写到文件中的数据;参数nCount表示要向文件中写入的最大字节数。2. 读取数据Read函数virtual UINT Read( void* lpBuf, UINT nCount );该函数从文件中读取数据到缓冲区里,参数lpBuf指向用户定义的缓冲区的指针,用来接收数据;参数nCount表示要从文件中读出的最大字节数。3. 定位文件指针到文件的指定位置Seek函数virtual LONG Seek( LONG lOff, UINT nFrom );该函数用于定位文件指针位置,如果所请求的位置合法,则返回值为距离文件头的新字节偏移。第一个参数lOff,表示指针偏移的字节数。第二个参数nFrom,表示偏移的起点,该参数必须是下列值中的唯一:l CFile:begin表示从文件的头部开始偏移指针l CFile:current表示从文件的当前位置开始偏移指针l CFile:end表示从文件的尾部开始偏移指针8.2.4 关闭文件文件的打开和关闭是相对的,打开一个文件之后,必须把它关闭,关闭文件可以调用Close函数,原形如下:virtual void Close( );8.3 文件I/O处理实现文件操作的方法有很多,用户不仅可以通过先前学过的CFile类来进行文件操作,还可以通过C或C+函数、API函数来实现,更可以通过创建打开和保存对话框来实现文件操作。本章将分别介绍这几种文件操作的方法及如何创建打开和保存对话框。8.3.1 利用MFC类来实现这里将通过一个例子来介绍如何通过定义类CFile对象来实现文件的I/O操作,完整例程请参见光盘中例子代码EX08_01,具体操作步骤如下:l 步骤1:新建一个MFC单文档应用程序,工程名为EX08_01或用户自定义。l 步骤2:给工程新添加一个菜单,名为“文件操作”,在下面再添加一个级联菜单名为“利用MFC类”,然后给这个级联菜单添加两个菜单项分别为“写入数据”和“读取数据”,ID分别为ID_FILEIO_MFC_WRITE和ID_FILEIO_MFC_READ。结果如图8-03所示。图8-03 添加菜单“利用MFC类”l 步骤3:在CEX08_01sView类中添加上述两个菜单项的COMMAND命令消息处理函数,分别为OnFileioMfcRead和OnFileioMfcWrite,并添加如下代码:1. 在消息处理函数OnFileioMfcWrite中写入数据void CEX08_01View:OnFileioMfcWrite() / TODO: Add your command handler code hereCFile file(c:example1.txt,CFile:modeCreate|CFile:modeWrite);char szchar20=利用MFC类来实现;file.Write(szchar,strlen(szchar);MessageBox(数据已写入);file.Close();2. 在消息处理函数OnFileioMfcRead中读取数据void CEX08_01View:OnFileioMfcRead() / TODO: Add your command handler code hereCFile file(c:example1.txt,CFile:modeRead);char szchar20;memset(szchar,0,20);file.Read(szchar,20);MessageBox(szchar);file.Close();其中,函数memset是将缓冲区用指定的字符填充,原形如下:void *memset( void *dest, int c, size_t count );第一个参数dest表示要被填充的缓冲区。第二个参数c表示要填充的字符。第三个参数count,填充的长度。本例中用0字符将整个缓冲区填满的用意是防止在读取文件数据时在正常数据的后面出现乱码。因为,我们并不知道将要读取多少个字符,因此,只好将缓冲区中的所有字符读出来,而这个缓冲区中没有被写入数据的那部分内存中的内容是不定的,读出来会是乱码,因此,在往这个缓冲区读入数据之前,先将它清0,用0字符填充的原因很简单,在C语言中,一个字符串的结束标志就是0字符。8.3.2 利用C函数来实现在工程EX08_01的基础上再添加一个级联菜单名为“利用C函数”,然后给这个级联菜单添加同样的两个菜单项分别为“写入数据”和“读取数据”,ID分别为ID_FILEIO_C_WRITE和ID_FILEIO_C_READ。结果如图8-04所示。图8-04添加菜单“利用C函数”在CEX08_01View类中添加上述两个菜单项的COMMAND命令消息处理函数,分别为OnFileioCRead和OnFileioCWrite,并添加如下代码:1. 在消息处理函数OnFileioCWrite中写入数据void CEX08_01View:OnFileioCWrite() / TODO: Add your command handler code hereFILE *pFile;char szchar20=利用C函数来实现;pFile=fopen(c:example2.txt,w);fwrite(szchar,1,strlen(szchar),pFile);MessageBox(数据已写入);fclose(pFile);其中fopen是C语言中用于打开文件的函数,原形如下:FILE *fopen( const char *filename, const char *mode );该函数返回一个指向FILE文件类型的指针,该函数有两个参数,第一个参数filename表示要打开的文件名;第二个参数mode表示打开方式,打开方式有如下几个选择:r 打开文件用于读取数据w 打开文件用于写入数据a打开文件用于在文件尾部写数据,并不覆盖前面的数据r+打开文件即可以读又可以写,前提是该文件必须存在w+ 打开一个空文件即可以读又可以写,如果文件存在,则文件的内容将被清除a+打开文件可以读取并且在尾部可以添加数据函数fwrite用于向文件中写入数据,原形如下:size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );第一个参数buffer是用户定义的缓冲区的指针,该参数中包含用户将要写到文件中的数据;第二个参数size和第三个参数count是相辅相成的,代码中的下面语句fwrite(szchar,1,strlen(szchar),pFile);也可以写成如下形式;fwrite(szchar,strlen(szchar),1,pFile);假设用户定义的缓冲区大小为20个字节,那么如果按照第一种代码形式,表示需要一个缓冲区(size为1),缓冲区大小为20个字节(count为20);如果按照第二种代码形式,表示需要20个缓冲区(size为20),每个缓冲区大小为1个字节(count为1)。第四个参数stream指向FILE文件类型的指针。2. 在消息处理函数OnFileioCRead中读取数据void CEX08_01View:OnFileioCRead() / TODO: Add your command handler code hereFILE *pFile;char szchar20;memset(szchar,0,20);pFile=fopen(c:example2.txt,r);fread(szchar,1,20,pFile);MessageBox(szchar);fclose(pFile);其中fread是C语言中用来从文件中读取数据的函数,原形如下:size_t fread( void *buffer, size_t size, size_t count, FILE *stream );第一个参数buffer指向用户定义的缓冲区的指针,用来接收数据。第二个参数size和第三个参数count的用法同fwrite函数雷同。第四个参数stream指向FILE文件类型的指针。图8-05添加菜单“利用C+函数”8.3.3 利用C+函数来实现在工程EX08_01的基础上再添加一个级联菜单名为“利用C+函数”,然后给这个级联菜单添加同样的两个菜单项分别为“写入数据”和“读取数据”,ID分别为ID_FILEIO_CPLUS_WRITE和ID_FILEIO_CPLUS_READ。结果如图8-05所示。在CEX08_01View类中再添加上述两个菜单项的COMMAND命令消息处理函数,分别为OnFileioCplusRead和OnFileioCplusWrite,并添加如下代码:1. 在消息处理函数OnFileioCplusWrite中写入数据void CEX08_01View:OnFileioCplusWrite() / TODO: Add your command handler code hereofstream ofs(c:example3.txt);char szchar20=利用C+函数来实现;ofs.write(szchar,strlen(szchar);MessageBox(数据已写入);ofs.close();其中ofstream表示输出流对象,它的成员函数write用于向文件写入数据。2. 在消息处理函数OnFileioCplusRead中读取数据void CEX08_01View:OnFileioCplusRead() / TODO: Add your command handler code hereifstream ifs(c:example3.txt);char szchar20;memset(szchar,0,20);ifs.read(szchar,20);MessageBox(szchar);ifs.close();其中ifstream表示输入流对象,它的成员函数read用于从文件读取数据。利用C+函数来实现文件操作,可以选择操作的文件是文本文件还是二进制文件。文本文件和二进制文件的区别:文件文件是一种特殊的二进制文件,当它遇到回车键10时,写入文件时会自动地在它的前面加一个13,而读出文件时遇到1310的组合时,又把它还原到10。而二进制文件就是把数据原封不动的写入文件,再原封不动的读取出来,没有文本文件的这种转换操作。下面的代码演示了之间的这种区别:写入文件时,按如下填写:ofstream f(c:1.txt);char buf3;buf0=a;buf1=n;buf2=b;f.write(buf,3);读出文件时:ifstream f(c:1.txt);f.setmode(filebuf:binary);char buf5;memset(buf,0,5);f.read(buf,5);CString str;str.Format(%d,%d,%d,%d,buf0,buf1,buf2,buf3);MessageBox(str);在写入文件时不指定格式,文件将按默认的文本格式存储,然后读出文件时指定二进制格式,读出的数据如图8-06所示:图8-06如果注释掉f.setmode(filebuf:binary);语句,文件将按文本文件读出,如图8-06所示:图8-07请读者敲入以上代码体会一下文本文件和二进制文件的不同。8.3.4 利用API函数来实现在工程EX08_01的基础上再添加一个级联菜单名为“利用API函数”,然后给这个级联菜单添加同样的两个菜单项分别为“写入数据”和“读取数据”,ID分别为ID_FILEIO_API_WRITE和ID_FILEIO_API_READ。结果如图8-08所示。图8-08添加菜单“利用API函数”在CEX08_01View类中再添加上述两个菜单项的COMMAND命令消息处理函数,分别为OnFileioApiRead和OnFileioApiWrite,并添加如下代码:1. 在消息处理函数OnFileioApiWrite中写入数据:void CEX08_01View:OnFileioApiWrite() / TODO: Add your command handler code hereHANDLE handle;char szchar20=利用API函数来实现;DWORD dwWrite;handle=CreateFile(c:example4.txt,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);WriteFile(handle,szchar,strlen(szchar),&dwWrite,NULL);CloseHandle(handle);MessageBox(数据已写入);其中CreateFile函数在这里表示创建并打开文件,但是我们不能光从字面来理解这个函数,因为它还可以创建及打开
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 民爆物品安全培训会课件
- 初二八校联考试卷及答案
- 棒球专业考试题库及答案
- 民族风课件教学课件
- 算力与新质生产力的关联
- 安全生产管理系统讲解
- 新质生产力的发展策略
- 文旅产品融入新质生产力探索
- 民族的课件教学课件
- 陕西新质生产力十大产业榜单
- 2025版全新离婚协议书:财产分割、子女抚养及离婚后财产保全合同范本
- 石油钻井知识课件
- “学回信精神·助改革发展”专题调研报告
- 2025年医学基础知识题库及答案
- (2025秋新版)苏教版三年级数学上册全册教案
- 职业院校实习生考核评价标准
- 水果保鲜的秘密课件
- 无人机公开课课件
- 2025年事业单位招聘考试综合类职业能力倾向测验真题模拟试卷:电子信息工程领域
- 仓库维修协议书
- 城管协管员面试题及答案
评论
0/150
提交评论