首页 > 代码库 > MFC文件操作、序列化机制

MFC文件操作、序列化机制

一 MFC的文件操作

   1 相关类

  CFile类-封装了文件句柄以及操作文件的API函数。

  CFileFind类-封装了文件搜索功能。

   2CFile类的使用

  2.1 文件读写

      2.1.1 创建或者打开文件

            CFile::Create

      2.1.2 文件读写

            CFile::Read/Write

      2.1.3 关闭文件

            CFile::Close

      注意:1 文件读写需要异常处理

            2 注意文件的指针位置

  2.2 文件属性的获取和设置

      2.2.1 CFile::GetStatus

      2.2.2 CFile::SetStatus

   3CFileFind类的使用

    3.1 开始查找(指定查找的目录)

        CFileFind::FindFile

    3.2 查找下一个(获取当前文件信息,返回下一个文件是否存在)

        CFileFind::FindNextFile

    3.3 获取/判断文件信息

        CFileFind::GetXXX/IsXXX

    3.4 结束查找

        CFileFind::Close

测试Cfile类编写测试程序:

新建一个Win32    Console Application 注意选择MFC库的支持

编写如下测试代码:

/*****************************************
    测试CFile
*******************************************/
void CFileTest ()
{
        // 定义CFile对象
	CFile file;
	if (! file.Open ("D:/test.txt", CFile::modeCreate | CFile::modeReadWrite))
	{// 打开文件,如果文件不存在就创建文件,并且以读和写的权限打开文件
		AfxMessageBox ("open file failed!");
		return;
	}
	try
	{
	// 写文件
	file.Write ("This is a test txt file!", strlen ("This is a test txt file!"));

	// 读文件
	char szBuf [256] = {0};
	// 注意:上面写完文件后文件指针位于文件末尾
	// 移动文件指针到文件头
	file.SeekToBegin ();
	file.Read (szBuf, 256);
	cout << szBuf << endl;
	}
	catch (CFileException* e)
	{
		// 异常处理...
		file.Close ();
	}
    
	// 关闭文件
	file.Close ();
}
/**************************************************
    测试CFile::GetStatus、CFile::SetStatus
	修改当前文件的创建日期
***************************************************/
void  FileStatusTest ()
{
	CFileStatus status;// 保存文件状态信息的结构体
	CFile::GetStatus ("D:/test.txt", status);
	CTimeSpan span (7,0, 0, 0);// CTimeSpan 时间间隔类
        status.m_ctime -=  span;
	CFile::SetStatus ("D:/test.txt", status);
}
/*****************************************************
   测试CFileFind, 查找指定路径下的所有文件
******************************************************/
void CFileFindTest (CString strPath)
{
	// 查找strPath路径下的所有(*.*)文件
	strPath += "/*.*";
	CFileFind find;
	// 开始查找
	BOOL bRet = find.FindFile (strPath);
	while (bRet)
	{
		// 获取当前文件信息并查找下一个文件
		bRet = find.FindNextFile ();
		if (! bRet)
			return;
		if (! find.IsDots())
		{// 点目录不处理,防止死循环
			if (find.IsDirectory ())
			{// 目录则继续进入查找
				// cout 无法输出CString类型
				//cout << "[" << find.GetFileName () << "]" << endl;
				printf ("[%s]\n", find.GetFileName ());
				CFileFindTest (find.GetFilePath ());
			}
			else
			{// 输出普通文件
				//cout << find.GetFileName() << endl;
				printf ("%s\n", find.GetFileName ());
			}
		}
	}
	find.Close ();
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
        //CFileTest ();
	//FileStatusTest ();
	CFileFindTest ("D:");
	return 0;
}

注意:

文件同时具备读写权限时要写成:CFile::modeReadWrite而不是分开写读和写权限。

CtimeSpan 为是时间间隔类,可以用来对现有时间做加减运算

Cout不能输出Cstring类型,应该使用printf

使用Cfile操作文件时使用异常处理



序列化
   这里之所以把文件操作和序列化放在一起,是因为序列化主要是用来方便文件操作的
   1 概念-将数据以二进制流的方式依次写入到文件或者从文件中读取的过程。
   2 相关类
     CArchive类-功能是完成具体的数据读写。(代替CFile类的Read/Write函数)。
   3 使用
     3.1 创建或者打开文件
             CFile::Create
     3.2 文件读写
         3.2.1 构造CArchive对象
         3.2.2 数据读写
               >>  读操作
               <<  写操作
         3.2.3 关闭CArchive对象
               CArchive::Close    
     3.3 关闭文件
             CFile::Close

 新建一个Win32 Console Application, 选择MFC库的支持

 编写如下测试代码:

// 存储数据的过程(写操作)
void Store()
{
	// 打开或者新建文件
	CFile file;
	BOOL bRet=file.Open("c:/serial.dat",
		CFile::modeCreate|CFile::modeWrite);
	if (!bRet)
	{
		printf("文件打开失败!");
		return;
	}
	// 构造CArchive对象
	CArchive ar(&file,CArchive::store);
	// 写数据
	ar<<100<<12.25<<'A';
	// 关闭CArchive对象
	ar.Close();
	// 关闭文件
	file.Close();

}
// 加载数据的过程(读操作)
void Load()
{
	// 打开文件
	CFile file;
	BOOL bRet=file.Open("c://serial.dat",CFile::modeRead);
	if (!bRet)return;
    // 构造CArchive对象
	CArchive ar(&file,CArchive::load);
	// 读数据
	int iValue=http://www.mamicode.com/0;>
注意:

         在Win C下文件路径写成C:/serial.dat 永远要比 C:\\serial.dat 要好,一个反斜杠“/”不仅写起来简单而且具有更好的通用性,不易出错。

         文件与CArchive的模式要一致,写<<   CArchive::store,  读 >> CArchive::load
         数据读写的顺序要一致

对象的序列化:

  1 概念
     序列化对象-将对象的类的信息以及对象的成员变量以二进制流的方式依次写入到文件的过程。
     反序列化对象-从文件中首先读取类的信息创建对象,然后读取成员变量赋值给新建的对象的过程。
   2 定义支持序列化的类
     2.1 派生自CObject类
     2.2 在类的定义中添加序列化的声明宏 DECLARE_SERIAL(className)
     2.3 在类的实现中添加序列化的实现宏 IMPLEMENT_SERIAL(...)
     2.4 重写CObject::Serialize()函数,在函数中,完成成员变量的序列化。
   3 使用
     在读写对象时,参数是对象的指针。

   新建一个Win32 Console Application, 选择MFC库的支持

  编写如下测试代码: 其中定义了一个支持序列化的类CPerson ,且CPerson中包含了一个普通类的成员m_course, 把该成员当普通变量用即可

  

// 支持序列化的课程类
class Course
{
public:
	int m_iNO;
	CString m_strName;
};
// 定义一个支持序列化的类
class CPerson : public CObject
{
public:
	void Show ()
	{
		printf ("Name:%s,Age:%d\niNO:%d, CourseName:%s\n", m_strName, m_iAge, m_course.m_iNO ,m_course.m_strName);
	}
	CPerson (){};
	CPerson (int age,  CString strName, int iNO, CString strCourName) : m_iAge (age), m_strName (strName)
	{
		m_course.m_iNO  = iNO;
		m_course.m_strName = strCourName;
	}
	virtual void Serialize (CArchive &ar);
private:
	int m_iAge;
	CString m_strName;
	Course m_course;
    // 序列化声明宏
	DECLARE_SERIAL (CPerson)
};
// 序列化实现宏
IMPLEMENT_SERIAL (CPerson, CObject, 1)
void CPerson::Serialize (CArchive &ar)
{
	// 如果父类成员需要序列化时,首先调用父类相关函数
	CObject::Serialize (ar);
	if (ar.IsStoring ())// 写
		ar << m_iAge << m_strName << m_course.m_iNO << m_course.m_strName;
	else// 读
		ar >> m_iAge >> m_strName >> m_course.m_iNO >> m_course.m_strName;
}
// 写对象
void ObjectStore (CPerson* pPerson)
{
	CFile file;
	BOOL bRet = file.Open ("E:/Serial.dat", CFile::modeCreate | CFile::modeWrite);
    if (! bRet)
		return;
	CArchive ar(&file, CArchive::store);
	ar << pPerson;

	ar.Close();
	file.Close ();
}
// 读对象
void ObjectLoad ()
{
	CFile file;
	BOOL bRet = file.Open ("E:/Serial.dat", CFile::modeRead);
	if (! bRet)
		return;
	CArchive ar (&file, CArchive::load);
	// 注意这里是指针,对象不用我们去定义
	CPerson *pPerson = NULL;
	ar >> pPerson;
	if (pPerson)
	{
	        pPerson->Show ();
		delete pPerson;
		pPerson = NULL;
	}

	ar.Close();
	file.Close ();
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	CPerson person (24, "关羽", 1, "武术");
	ObjectStore (&person);
	ObjectLoad ();
	return 0;
}

实现原理:

先展开两个宏:

DECLARE_SERIAL(CPerson)

        _DECLARE_DYNCREATE(CPerson) 
	AFX_API friend CArchive& AFXAPI 
	operator>>(CArchive& ar, CPerson* &pOb);

IMPLEMENT_SERIAL(CPerson,CObject,1)

CObject* PASCAL CPerson::CreateObject() 
{ 
	return new CPerson; 
}  
_IMPLEMENT_RUNTIMECLASS(CPerson, CObject, 1, CPerson::CreateObject) 
AFX_CLASSINIT _init_CPerson(RUNTIME_CLASS(CPerson));
CArchive& AFXAPI operator>>(CArchive& ar, CPerson* &pOb)
{ 
	pOb = (CPerson*) ar.ReadObject(RUNTIME_CLASS(CPerson)); 
			return ar; 
} 

很容易看到序列化的申明和实现宏张开后明显包含MFC的: 动态创建机制 和 运行时类信息机制

那么就不难理解为什么CArchive 只需要对象指针就可以操作一个对象了(运行式动态创建对象)

首先展开宏后观察几个成员的作用:

相关结构体

struct AFX_CLASSINIT
{ 
          AFX_CLASSINIT(CRuntimeClass* pNewClass) 
          { 
             AfxClassInit(pNewClass);
             {
               pModuleState->m_classList.AddHead(pNewClass);
             } 
           } 
};

该结构体就包含一个函数:把运行时类信息的地址加入到当前程序模块状态信息的一个链表成员变量m_classList中


operator>>,友元函数,读对象的函数。设置友元的目的是得到当前类的私有成员。


_init_CPerson,全局的结构体变量。作用是将当前类的运行时类信息的地址保存到模块状态信息的一个链表m_classList中。


写对象的过程跟踪: