MFC六大關鍵技術(第四部分)——永久保存(串行化)

MFC?六大關鍵技術?(?第四部分?)?——永久保存(串行化)

先用一句話來說明永久保存的重要:弄懂它以后,你就越來越像個程序員了!

如果我們的程序不需要永久保存,那幾乎可以肯定是一個小玩兒。那怕我們的記事本、畫圖等小程序,也需要保存才有真正的意義。

對于?MFC?的很多地方我不甚滿意,總覺得它喜歡拿一組低能而神秘的宏來故弄玄虛,但對于它的連續存儲(?serialize?)機制,卻是我十分鐘愛的地方。在此,可讓大家感受到面向對象的幸福。

MFC?的連續存儲(?serialize?)機制俗稱串行化。“在你的程序中盡管有著各種各樣的數據,?serialize?機制會象流水一樣按順序存儲到單一的文件中,而又能按順序地取出,變成各種不同的對象數據。”不知我在說上面這一句話的時候,大家有什么反應,可能很多朋友直覺是一件很簡單的事情,只是說了一個“爽”字就沒有下文了。

要 實現象流水一樣存儲其實是一個很大的難題。試想,在我們的程序里有各式各樣的對象數據。如畫圖程序中,里面設計了點類,矩形類,圓形類等等,它們的繪圖方 式及對數據的處理各不相同,用它們實現了成百上千的對象之后,如何存儲起來?不想由可,一想頭都大了:我們要在程序中設計函數?store()?,在我們單擊“文件?/?保存”時能把各對象往里存儲。那么這個?store()?函數要神通廣大,它能清楚地知道我們設計的是什么樣的類,產生什么樣的對象。大家可能并不覺得這是一件很困難的事情,程序有能力知道我們的類的樣子,對象也不過是一塊初始化了存儲區域罷了。就把一大堆對象“轉換”成磁盤文件就行了。

即使上面的存儲能成立,但當我們單擊“文件?/?打開”時,程序當然不能預測用戶想打開哪個文件,并且當打開文件的時候,要根據你那一大堆垃圾數據?new?出數百個對象,還原為你原來存儲時的樣子,你又該怎么做呢?

試 想,要是我們有一個能容納各種不同對象的容器,這樣,用戶用我們的應用程序打開一個磁盤文件時,就可以把文件的內容讀進我們程序的容器中。把磁盤文件讀進 內存,然后識別它“是什么對象”是一件很難的事情。首先,保存過程不像電影的膠片,把景物直接映射進去,然后,看一下膠片就知道那是什么內容。可能有朋友 說它象錄像磁帶,拿著錄像帶我們看不出里面變化的磁場信號,但經過錄像機就能把它還原出來。

其實不是這樣的,比如保存一個矩形,程序并不是把矩形本身按點陣存儲到磁盤中,因為我們繪制矩形的整個過程只不過是調用一個?GDI?函數罷了。它保存只是坐標值、線寬和某些標記等。程序面對“?00 FF?”這樣的東西,當然不知道它是一個圓或是一個字符!

拿 剛才錄像帶的例子,我們之所以能最后放映出來,前提我們知道這對象是“錄像帶”,即確定了它是什么類對象。如果我們事先只知道它“里面保存有東西,但不知 道它是什么類型的東西”,這就導致我們無法把它讀出來。拿錄像帶到錄音機去放,對錄音機來說,那完全是垃圾數據。即是說,要了解永久保存,要對動態創建有 深刻的認識。

現在大家可以知道困難的根源了吧。我們在寫程序的時候,會不斷創造新的類,構造新的對象。這些對象,當然是舊的類對象(如?MyDocument?)從未見過的。那么,我們如何才能使文檔對象可以保存自己新對象呢,又能動態創建自己新的類對象呢?

許多朋友在這個時候想起了?CObject?這個類,也想到了虛函數的概念。于是以為自己“大致了解”串行化的概念。他們設想:“我們設計的?MyClass?(我們想用于串行化的對象)全部從?CObject?類派生,?CObject?類對象當然是?MyDocument?能認識的。”這樣就實現了一個目的:本來?MyDocument?不能識別我們創建的?MyClass?對象,但它能識別CObject?類對象。由于?MyClass?從?CObject?類派生,我產的新類對象“是一個?CObject?”,所以?MyDocument?能把我們的新對象當作?CObiect?對象讀出。或者根據書本上所說的:打開或保存文件的時候,?MyDocument?會調用?Serialize?(),?MyDocument?的?Serialize?()函會呼叫我們創建類的?Serialize?函數?[?即是在MyDocument Serialize?()中調用:m_pObject?->?Serialize()?,注意:在此m_pObject?是CObject?類指針,它可以指向我們設計的類對象]?。最終結果是?MyDocument?的讀出和保存變成了我們創建的類對象的讀出和保存,這種認識是不明朗的。

有意思還有,在網上我遇到幾位自以為懂了?Serialize?的朋友,居然不約而同的犯了一個很低級得讓人不可思議的錯誤。他們說:?Serialize?太簡單了!?Serialize?()是一個虛函數,虛函數的作用就是“優先派生類的操作”。所以?MyDocument?不實現?Serialize?()函數,留給我們自己的?MyClass?對象去調用?Serialize?()……真是哭笑不得,我們創建的類?MyClass?并不是由?MyDocument?類派生,?Serialize?()函數為虛在?MyDocument?和?MyClass?之間沒有任何意義。?MyClass?產生的?MyObject?對象僅僅是?MyDocument?的一個成員變量罷了。

話說回來,由于?MyClass?從?CObject?派生,所以CObject?類型指針能指向?MyClass?對象,并且能夠讓?MyClass?對象執行某些函數(特指重載的?CObject?虛函數),但前提必須在?MyClass?對象實例化了,即在內存中占領了一塊存儲區域之后。不過,我們的問題恰恰就是在應用程序隨便打開一個文件,面對的是它不認識的?MyClass?類,當然實例化不了對象。

幸好我們在上一節課中懂得了動態創建。即想要從CObject?派生的MyClass?成為可以動態創建的對象只要用到DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC?宏就可以了(注意:最終可以Serialize?的對象僅僅用到了DECLARE_SERIAL/IMPLEMENT_SERIAL?宏,這是因為DECLARE_SERIAL/IMPLEMENT_SERIAL?包含了DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC?宏)。

從解決上面的問題中,我們可以分步理解了:

1、???Serialize?的目的:讓?MyDocument?對象在執行打開?/?保存操作時,能讀出(構造)和保存它不認的?MyClass?類對象。

2、???MyDocument?對象在執行打開?/?保存操作時會調用它本身的?Serialize?()函數。但不要指望它會自動保存和讀出我們的?MyClass?類對象。這個問題很容易解決,就直接在?MyDocument::?Serialize?(){

//?在此函數調用MyClass?類的Serialize?()就行了!即

MyObject. Serialize?();???????

}

3、???我們希望?MyClass?對象為可以動態創建的對象,所以要求在MyClass?類中加上DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC?宏。

但目前的Serialize?機制還很抽象。我們僅僅知道了表面上的東西,實際又是如何的呢?下面作一個簡單深刻的詳解。

先看一下我們文檔類的Serialize?()

void CMyDoc::Serialize(CArchive& ar)

{

????if (ar.IsStoring())

????{

????????// TODO: add storing code here

????}

????else

????{

????????// TODO: add loading code here

????}

}

目前這個子數什么也沒做(沒有數據的讀出和寫入),CMyDoc?類正等待著我們去改寫這個函數。現在假設CMyDoc?有一個MFC?可識別的成員變量m_MyVar,?那么函數就可改寫成如下形式:

void CMyDoc::Serialize(CArchive& ar)

{

????if (ar.IsStoring())?????//?讀寫判斷

????{

????????ar<<m_MyVar;????????//?寫

????}

????else

????{

????????ar>>m_MyVar;????????//?讀

????}

}

許多網友問:自己寫的類(即?MFC?未包含的類)為什么不行?我們在?CMyDoc?里包含自寫類的頭文件MyClass.h?,這樣CMyDoc?就認識MyDoc?類對象了。這是一般常識性的錯誤,MyDoc?類認識MyClass?類對象與否并沒有用,關鍵是CArchive?類,即對象ar?不認識MyClass?(當然你夢想重寫CArchive?類當別論)。“>>?”、“<<?”都是CArchive?重載的操作符。上面ar>>m_MyVar?說白即是在執行一個以ar?和m_MyVar?為參數的函數,類似于function(ar,m_MyVar)?罷了。我們當然不能傳遞一個它不認識的參數類型,也因此不會執行function(ar,m_MyObject)?了。

[?注:這里我們可以用指針。讓MyClass?從Cobject?派生,一切又起了質的變化,假設我們定義了:MyClass *pMyClass = new MyClass;?因為MyClass?從CObject?派生,根據虛函數原理,pMyClass?也是一個CObject*?,即pMyClass?指針是CArchive?類可認識的。所以執行上述function(ar, pMyClass)?,即ar << pMyClass?是沒有太多的問題(在保證了MyClass?對象可以動態創建的前提下)。]

?

回過頭來,如果想讓?MyClass?類對象能?Serialize?,就得讓MyClass?從CObject?派生,Serialize?()函數在CObject?里為虛,MyClass?從CObject?派生之后就可以根據自己的要求去改寫它,象上面改寫CMyDoc::Serialize?()方法一樣。這樣MyClass?就得到了屬于MyClass?自己特有的Serialize?()函數。

現在,程序就可以這樣寫:

……

#include “MyClass.h”

……

void CMyDoc::Serialize(CArchive& ar)

{

????//?在此調用?MyClass?重寫過的?Serialize()

????m_MyObject. Serialize(ar);??????// m_MyObject?為?MyClass?實例

}

至此,串行化工作就算完成了,一即簡單直觀:從?CObject?派生自己的類,重寫?Serialize?()?。在此過程中,我刻意安排:在沒有用到?DECLARE_SERIAL/IMPLEMENT_SERIAL?宏,也沒有用到CArray?等模板類的前提下就完成了串行化的工作。我看過某些書,總是一開始就講DECLARE_SERIAL/IMPLEMENT_SERIAL?宏或馬上用CArray?模板,讓讀者覺得串行化就是這兩個東西,導致許多朋友因此找不著北。

大家看到了,沒有DECLARE_SERIAL/IMPLEMENT_SERIAL?宏和CArray?等數據結構模板也依然可以完成串行化工作。

?

現在可以騰出時間講一下大家覺得十分抽象的?CArchive?。我們先看以下程序(注:以下程序包含動態創建等,請包含DECLARE_SERIAL/IMPLEMENT_SERIAL?宏)

void?MyClass?::Serialize(CArchive& ar)

{

????if (ar.IsStoring())?????//?讀寫判斷

????{

????????ar<< m_pMyVar;??????//?問題:ar?如何把m_pMyVar?所指的對象變量保存到磁盤?

????}

????else

????{

????????pMyClass = new MyClass; //?準備存儲空間

????????ar>> m_pMyVar;?????

????}

}

要回答上面的問題,即“?ar<<XXX?”的問題。和?我們得看一下模擬?CArchive?的代碼。

“ar<<XXX?”是執行CArchive?對運算符“<<?”的重載動作。ar?和XXX?都是該重載函數中的一參數而已。函數大致如下:

CArchive& operator<<( CArchive& ar, const CObject* pOb)

{

????…………

????????//?以下為CRuntimeClass?鏈表中找到、識別pOb?資料。

????????CRuntimeClass* pClassRef = pOb->GetRuntimeClass();

????????//?保存pClassRef?即類信息(略)

???????

????????((CObject*)pOb)->Serialize();//?保存MyClass?數據

????…………

}

從上面可以看出,因為?Serialize()?為虛函數,即“ar<<XXX?”的結果是執行了XXX?所指向對象本身的Serialize()?。對于“ar>>XXX?”,雖然不是“ar<<XXX?”逆過程,大家可能根據動態創建和虛函數的原理料想到它。

至此,永久保存算是寫完了。在此過程中,我一直努力用最少的代碼,詳盡的解釋來說明問題。以前我為本課題寫過一個版本,并在幾個論壇上發表過,但不知怎么在網上遺失(可能被刪除)。所以這篇文章是我重寫的版本。記得第一個版本中,我是對DECLARE_SERIAL/IMPLEMENT_SERIAL?和可串行化的數組及鏈表對象說了許多。這個版本中我對DECLARE_SERIAL/IMPLEMENT_SERIAL?其中奧秘幾乎一句不提,目的是讓大家能找到中心,有更簡潔的永久保存的概念,我覺得這種感覺很好!

摘自:http://blog.csdn.net/liyi268/archive/2006/03/13/623367.aspx

轉載于:https://www.cnblogs.com/lzjsky/archive/2010/11/24/1886503.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/378825.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/378825.shtml
英文地址,請注明出處:http://en.pswp.cn/news/378825.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在網絡中配置思科交換機

By default, all ports of a switch are enabled. As we are talking about layer 2 switching, there is no need to configure IP address or any routing protocol on the switch. In such a situation, the configuration is not focused on the switch. 缺省情況下&#…

黑色背景下,描繪照片的輪廓形狀并保存

描繪照片的輪廓形狀并保存 import cv2 from matplotlib import pyplot as plt # 1.先找到輪廓 img cv2.imread(E:\Python-workspace\OpenCV\OpenCV/beyond.png, 0) _, thresh cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) image, conturs, hierarchy c…

java pdf合并_Java 合并、拆分PDF文檔

本文將介紹如何在Java程序中合并及拆分PDF文檔&#xff0c;合并文檔時&#xff0c;包括合并多個不同PDF文檔為一個文檔&#xff0c;以及合并PDF文檔的不同頁面為一頁&#xff1b;拆分文檔是&#xff0c;包括將PDF文檔按每一頁拆分&#xff0c;以及按指定頁數范圍來拆分。下面將…

HDU4405 期望

對于期望&#xff0c;首先&#xff0c;對于這個公式中p表示概率&#xff0c;x表示隨機變量 展開則為 ex p1*x1p2*x2p3*x3....... 對于本題 假設 ex[ i ]表示當前 i 走到 n 的期望值。所以若 i 處沒有飛機&#xff0c;ex[ i ]sigma(1/6*ex[ik])1 其中(k1...6) &#xff08;1表示…

調用本地電腦攝像頭并進行按P進行捕獲照片并保存,按下Q退出

調用本地電腦攝像頭并進行按P進行捕獲照片并保存&#xff0c;按下Q退出 灰度攝像頭顯示&#xff1a; import cv2 cap cv2.VideoCapture(0) if not cap.isOpened():print("Cannot open camera")exit() while True:# 逐幀捕獲ret, frame cap.read()# 如果正確讀取幀…

intersect函數_PHP array_intersect()函數與示例

intersect函數PHP array_intersect()函數 (PHP array_intersect() Function ) array_intersect() function is used to find the matched elements from two or more elements. Function “array_intersect()” compares the values of the first array with the other arrays …

很全的SQL注入語句

1、返回的是連接的數據庫名and db_name()>02、作用是獲取連接用戶名and user>03、將數據庫備份到Web目錄下面;backup database 數據庫名 to diskc:\inetpub\wwwroot\1.db;--4、顯示SQL系統版本and 1(select VERSION) 或and 1convert(int,version)--5、判斷xp_cmdshell擴展…

使用DataTable更新數據庫

1、修改數據 DataRow dr hRDataSet.Tables["emp"].Rows.Find(textBox3.Text);//DataRow dr hRDataSet.Tables["emp"].Select("id"textBox3.Text)[0];dr.BeginEdit();dr["name"] textBox1.Text;dr.EndEdit();SqlCommandBuilder cmdn…

java異常體系_JAVA異常體系結構詳解

一、什么是異常異常&#xff1a;程序在運行過程中發生由于硬件設備問題、軟件設計錯誤等導致的程序異常事件。(在Java等面向對象的編程語言中)異常本身是一個對象&#xff0c;產生異常就是產生了一個異常對象。 ——百度百科二、異常體系Java把異常當作對象來處理&#xf…

對照片質量進行壓縮

對照片質量進行壓縮 其實無論是jpg還是png都是已經壓縮編碼化的格式罷了&#xff0c;原圖片的大小要遠遠大于壓縮編碼后的格式 1&#xff0c;像素&#xff1a;圖片放大到一定程度之后的一個個的小方塊 2&#xff0c;RGB&#xff1a;每一個像素&#xff08;小方塊&#xff09;都…

Silverlight訪問 Apache服務器(Tomcat,Geronimo)中部署的Webservice

Silverlight 訪問 Apache服務器中的Webservice 開發環境 Vs2010 、 Silverlight4 、 Java Jdk1.6 U 21 、 Apache-tomcat-6.0.20 、 Myeclipse8.5 、 Apache-ant-1.8.1 、 Axis2 、 Geronimo-tomcat6-javaee5-2.2. 下載地址&#xff1a; Apache-tomcat &#xff1a; http://apa…

那些幫助你成為優秀前端工程師的講座——《性能篇》

這篇文章是前端優秀講座和討論列表系列連載第七篇&#xff0c;介紹前端性能優化技巧。前端領域發展迅速&#xff0c;只有時刻掌握前端發展趨勢和技術動態&#xff0c;學習前沿的開發思想和理念才能讓自己跟上時代的步伐&#xff0c;保持自己的技術優勢。 您可能感興趣的相關文章…

mca終端_MCA的完整形式是什么?

mca終端1)MCA&#xff1a;計算機應用碩士 (1) MCA: Master of Computer Application) MCA is an abbreviation of Master of Computer Application. It is a masters degree program for post-graduation in Computer applications. This post-graduate course duration is abo…

鋼鐵俠java_現代版“鋼鐵俠”,無所不能的程序員,java工程師實現人造器官!...

一位名叫利亞姆澤貝迪(Liam Zebedee)的軟件工程師已經厭倦了糖尿病患者的生活挑戰&#xff0c;因此他決定入侵他的胰島素泵&#xff0c;并將其轉變成一種嶄新的高科技胰腺胰腺。Zebedee詳細介紹了查找和訂購零件的過程&#xff0c;為智能胰島素泵編寫軟件的代碼以及在其博客中組…

Windows下的Memcache安裝 (轉)

Windows下的Memcache安裝&#xff1a;1. 下載memcache的windows穩定版&#xff0c;解壓放某個盤下面&#xff0c;比如在c:\memcached2. 在終端&#xff08;也即cmd命令界面&#xff09;下輸入 ‘c:\memcached\memcached.exe -d install’ 安裝3. 再輸入&#xff1a; ‘c:\memca…

C#中實現js中的eval函數功能

在js中有eval函數&#xff0c;比如 eval&#xff08;‘33*4’&#xff09;結果為15&#xff1b; 但C#中想要完成這樣的功能&#xff0c;卻沒有相應的函數&#xff0c;可以用sql語句的方式實現&#xff0c;比如&#xff0c;執行 select 33*4 的方式。 可以先構造公式 Formula …

查看照片的指定位置的像素點值,并在照片中繪制一條指定像素顏色的線段

查看照片的指定位置的像素點值&#xff0c;并在照片中繪制一條指定像素的線段 import cv2 img cv2.imread(E:\Python-workspace\OpenCV\yanyu/beyond.png,1)#1為彩色圖片&#xff0c;0為灰度圖片 (b,g,r) img[20,20]#取照片的(20,20)處的像素點&#xff0c;左上角為(0,0)&am…

大數據和云計算涉及的技術_云計算涉及的風險

大數據和云計算涉及的技術In todays life using of cloud is very common among people, we use different clouds like Google cloud, cloud Azure etc. to store our photos, Videos, documents, data etc. to save space as well as we think that we will be able to retri…

int 轉interger java_Java中Integer和int之間的轉換

int到Integer:int a3;Integer Anew Integer(a);或:Integer AInteger.valueOf(a);Integer到int:Integer Anew Integer(5);int aA.intValue();至于Integer.parseInt(String str)則是將String類型轉為int類型。int類型是放在棧空間的&#xff0c;Integer是作為對象放在堆空間的;in…

圖像分割-二階導數零交叉點的含義

已知&#xff1a; 二階導數在灰度斜坡和灰度臺階過渡處會產生雙邊緣響應。 二階導數的符號可以用于確定邊緣的過渡是從亮到暗還是暗到亮。 斜坡開始處&#xff0c;二階導數為負&#xff0c;斜坡結束二階導數為正&#xff0c;斜坡上&#xff0c;二階導數為0.&#xff08;亮到暗…