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=0;
	double dValue=0.0;
	char cValue;
	ar>>iValue>>dValue>>cValue;
	// 關閉對象
	ar.Close();
	// 關閉文件
	file.Close();
	// 輸出
	printf("iValue=%d\n",iValue);
	printf("dValue=%f\n",dValue);
	printf("cValue=%c\n",cValue);


}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	Store();
	Load();
	return 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中。


寫對象的過程跟蹤:


ar<<pPerson

跟進:

_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ 
	ar.WriteObject(pOb);
	return ar;
}
ar.WriteObject(pOb)

跟進:

void CArchive::WriteObject(const CObject* pOb)
{// pOb === pPerson
    ..........................................
	// write class of object first
	// 獲取當前類的運行時類信息
	CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
	// 將類的信息寫入到文件
	WriteClass(pClassRef);

	.......................................

	// cause the object to serialize itself
	// 然後寫入類成員變量到文件,由於虛函數機制,這裏調用我們重寫的函數
	((CObject*)pOb)->Serialize(*this);
}
WriteObject-->WriteClass(pClassRef)

跟進:

void CArchive::WriteClass(const CRuntimeClass* pClassRef)
{
       .......................................................
		// store new class
	    // 一次將類的版本、長度、類名稱寫入到文件
		pClassRef->Store(*this);
       ......................................
}
WriteObject-->WriteClass-->Store(*this)

跟進:

void CRuntimeClass::Store(CArchive& ar) const
	// stores a runtime class description
{
	// m_lpszClassName 類名稱
	WORD nLen = (WORD)lstrlenA(m_lpszClassName);
	// 寫入類名稱、版本
	ar << (WORD)m_wSchema << nLen;
	ar.Write(m_lpszClassName, nLen*sizeof(char));
}

WriteObject-->Serialize(*this)

跟進:

void CPerson::Serialize( CArchive& ar )
{// 我們重寫的函數,完成類成員變量的寫入

	//如果父類成員變量需要序列化時,首先調用父類相關函數
	CObject::Serialize(ar);
	if (ar.IsStoring())//寫
	{
		ar<<m_strName<<m_iAge;
	}
	...............
}
總結以上流程如下:

  ar.WriteObject(pOb);
   {
      //1 獲取當前類的運行時類信息
      pOb->GetRuntimeClass();
      //2 將類的信息寫入到文件 
      WriteClass(pClassRef);
      {
        //依次將類的版本、類名稱長度以及類名稱寫入到文件
        pClassRef->Store(*this);
        {
          WORD nLen = (WORD)lstrlenA(m_lpszClassName);
	  ar << (WORD)m_wSchema << nLen;
	  ar.Write(m_lpszClassName, nLen*sizeof(char));
        }
      }
      //3 由於虛函數機制,調用CPerson::Serialize()函數,
      //  在函數中,將成員變量寫入到文件
      ((CObject*)pOb)->Serialize(*this);
      {
        if (ar.IsStoring())//寫
	{
		ar<<m_strName<<m_iAge;
	}
        ...
      } 

   } 


下面分析下讀取數據的過程:

ar>>pPerson

跟進:

CArchive& AFXAPI operator>>(CArchive& ar, CPerson* &pOb)
{ 
	pOb = (CPerson*) ar.ReadObject(RUNTIME_CLASS(CPerson)); 
			return ar; 
}

ReadObject(RUNTIME_CLASS(CPerson))

跟進:

CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
	....................................................
    // 讀取類的信息
	CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);

	// check to see if tag to already loaded object
	CObject* pOb;
    .................................................
	// allocate a new object based on the class just acquired
	// 調用運行時類信息的函數動態創建CPerson對象
	pOb = pClassRef->CreateObject();
	.....................................................
	// 調用我們重寫的函數
	pOb->Serialize(*this);
	..........................................

	return pOb;
}

ReadObject-->ReadClass(pClassRefRequested, &nSchema, &obTag)

跟進:

CRuntimeClass* CArchive::ReadClass(const CRuntimeClass* pClassRefRequested,
	UINT* pSchema, DWORD* pObTag)
{
	........................................................
	// new object follows a new class id
	if ((pClassRef = CRuntimeClass::Load(*this, &nSchema)) == NULL)
			AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);
}

ReadObject-->ReadClass-->Load(*this, &nSchema)

跟進:

CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
	// loads a runtime class description
{

	// 一次讀取 類版本、類名稱長度、類名稱
	WORD nLen;
	char szClassName[64];
	CRuntimeClass* pClass;

	WORD wTemp;
	ar >> wTemp; 
	*pwSchemaNum = wTemp;
	ar >> nLen;

	if (nLen >= _countof(szClassName) ||
		ar.Read(szClassName, nLen*sizeof(char)) != nLen*sizeof(char))
	{
		return NULL;
	}
	// 這裏就是爲什麼前面要保存類名稱長度
	szClassName[nLen] = '\0';


	// search app specific classes
	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
	AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
	for (pClass = pModuleState->m_classList; pClass != NULL;
		pClass = pClass->m_pNextClass)
	{// 根據類名稱在鏈表中的到對應的運行時類信息地址
		if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
		{
			AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
			return pClass;
		}
	}
    .......................................
}

ReadObject-->Serialize(*this)

跟進:

void CPerson::Serialize( CArchive& ar )
{
    ......................
	else
	{// 讀取
       ar >> m_strName >> m_iAge;
	}
}

注意:到這裏我們要明白爲什麼在讀取完數據之後要delete pPerson對象了,應爲pPerson是CArchive幫我們動態創建的對象(new)

總結上面的流程如下:

   ar.ReadObject(RUNTIME_CLASS(CPerson)); 
   { 
      //1 讀取類的信息
      ReadClass(pClassRefRequested, &nSchema, &obTag);
      {
         //1.1 依次讀取版本,類名稱長度,類名稱
         CRuntimeClass::Load(*this,...)
         {
            ar >> wTemp;
            ar >> nLen;
            ar.Read(szClassName,...);
            //得到一個完整的類名
            szClassName[nLen] = '\0'; 
         } 
        //1.2 根據類名在鏈表中得到對應的運行時類信息的地址
       	for (pClass = pModuleState->m_classList; 
            pClass != NULL;pClass = pClass->m_pNextClass)
        {
            if (lstrcmpA(szClassName, 
                            pClass->m_lpszClassName) == 0)
	    {
		 return pClass;
	    }
        }
		      
      }
      // 2 調用運行時類信息的函數動態創建CPerson對象
      pOb = pClassRef->CreateObject();
      // 3 同樣虛函數機制,調用CPerson::Serialize()函數
      pOb->Serialize(*this);
      {
         ...
         else//讀取
	{
	   ar>>m_strName>>m_iAge;
	}
      }  
   }



發佈了93 篇原創文章 · 獲贊 67 · 訪問量 100萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章