• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            依舊的博客

            技術學習

            C++博客 首頁 新隨筆 聯系 聚合 管理
              17 Posts :: 1 Stories :: 2 Comments :: 0 Trackbacks

            2007年4月15日 #

            關于Windows程序模型的最重要之處在于,程序是在Windows面向對象的體系結構中運行的。

            在WinMain()函數中,程序所進行的最重要工作是注冊窗口類,從而把自定義的窗口過程提供給Windows。然后程序調用Windows創建和顯示窗口,由此啟動同用戶的交互過程。在消息循環中,程序不斷取得消息,但并不進行處理,而是將其發回Windows,由Windows將消息發給相應的窗口過程。消息循環的作用在于控制生命期,如果沒有消息循環,進程將立即結束。

            在較高層次上來看,一個可擴展的系統會給模塊提供資源和自由,而模塊應當配合系統的整體結構。程序執行時,Windows會為其創建進程,分配資源,并調用WinMain()。WinMain()是進程入口,也是進程出口,在此期間進程可以做任何事情,但是為了使用Windows提供的各種便利,它必須符合Windows程序模型,將自己的運行結合到Windows環境中。作為進程出口,WinMain()決定著程序生命期。一個提供窗口過程而等待Windows調用的程序如何維持和結束自己的生命期呢,應該由消息來決定。當進程沒有要處理的消息時,它應該等待,所以WinMain()必須知道有沒有消息,Windows發給窗口過程的消息不能繞過WinMain();當進程收到特定的消息時,它結束生命期,所以WinMain()還應該了解消息的內容。這正是GetMessage()所做的,如果取不到消息就阻塞,如果取到WM_QUIT消息就返回0,結束消息循環。那么如果取到普通的消息呢,由WinMain()直接調用窗口過程不可以嗎?這種做法有悖于程序由Windows調用的基本思想,而實際上也會出現問題。一個窗口程序可能有很多窗口類,一些窗口類及其窗口過程是程序自定義的,另一些則是在Windows內部定義的,程序看不到其窗口過程,比如各種控件窗口。窗口程序運行起來以后,這些窗口類互相配合,它們通信的方式就是消息。由于消息指向的窗口過程可能是自定義的,也可能是Windows內部的,只有Windows才能把它們都送到目的地,并保持發送方式的一致性。所以WinMain()取到消息后,通過DispatchMessage()將其發回Windows,由Windows為其調用適當的窗口過程,直到窗口過程調用后返回Windows,DispatchMessage()才返回。(Windows調用窗口過程之后控制首先返回Windows,由WinMain()調用窗口過程之后控制保持在程序中,這種區別是否也有作用?不過經我試驗,在一個Win32 SDK的Hello程序中改由WinMain()調用窗口過程,沒有發現什么問題)

             
            參考資料:

            1.《Windows程序設計》/Charles Petzold 著 北京博彥科技發展有限公司 譯 北大出版社
            posted @ 2007-04-15 13:08 依舊的博客 閱讀(343) | 評論 (0)編輯 收藏

            2007年1月28日 #

            如果在VC中創建一個控制臺的EXE和一個Win32的DLL,從DLL中導出一個函數,該函數用new分配一塊內存,返回其指針,然后在EXE中調用該函數,獲得返回的指針,用delete釋放這塊內存,就會引發斷言錯誤。

            產生這個問題的原因是:EXE和DLL中分別靜態鏈接了C運行時庫,從而new和delete運算符來自C運行時庫的不同版本。C運行時庫在管理堆內存時,會使用一些全局變量來跟蹤內存分配情況,因此程序中鏈接的C運行時庫必須唯一,否則就會引起不一致。

            解決的辦法很簡單:在EXE和DLL中都動態鏈接C運行時庫,也就是在工程設置的Link面板選擇"忽略所有默認的庫",再加入msvcrt.lib。

            對這個問題有兩種錯誤的觀點需要澄清:一種以為EXE和DLL有不同的堆,實際上DLL總是被映射到加載它的進程的地址空間,它沒有自己的堆;一種以為DLL和EXE相對于不同的起始地址,動態鏈接的地址映射機制引起了前面的問題,實際上DLL是和OBJ一樣的目標模塊,每個目標模塊都有自己的起始地址,但是鏈接成加載模塊以后就會統一到一個起始地址,一個目標模塊對其它模塊的引用在鏈接前是以符號方式表示的,鏈接后會被修改成地址方式。靜態鏈接和動態鏈接都會保證:加載模塊是統一編址的。

            參考資料:
            1. http://topic.csdn.net/t/20020714/19/873683.html
            2. MSDN July 2000/Knowledge Base/Windows Development/Win32 Software Development Kit/HOWTO: Use the C Run-Time
            3. 《操作系統-內核與設計原理(第四版)》/William Stallings 著,魏迎梅等譯 電子工業出版社

            posted @ 2007-01-28 14:02 依舊的博客 閱讀(311) | 評論 (0)編輯 收藏

            2006年10月19日 #

            有兩個比較基本的問題:

            1. 為什么要用異常代替錯誤碼?

            錯誤碼的缺點:

            1) 默認是可以忽略的,因為調用函數時可以不處理其返回值,從而錯誤處理要依賴于程序員的主動性,而不是程序機制的要求;

            2) 不能跨作用域傳送,必須逐層向上轉發,即使中間沒有對錯誤碼進行重新定義;
            ?

            使用異常可以解決解決這兩個問題:

            1) 異常默認是不可忽略的,拋出的異常必須捕獲,否則就會報錯;

            2) 異常可以跨作用域傳送,從而錯誤的發現和處理被很好地分離開來;
            ?

            ?

            2. 異常和斷言的區別:
            ?

            異常被捕獲后可以不作處理,程序從捕獲位置繼續執行。而斷言是完全無法忽略的,程序在斷言失敗處立即終止。因此斷言通常用于調試版本,用來發現程序中的邏輯錯誤。雖然異常也能起到這樣的作用,但是不應該用異常代替斷言:
            1) 如果發現了邏輯錯誤,必須修改程序,而不可能在程序中進行處理和恢復,所以不需要向外傳送,沒有必要使用異常。
            2) 使用斷言的開銷比異常小得多,而且斷言可以從發布版中完全去除。
            ?

            異常用于處理正確程序中的運行期問題(比如內存分配失敗,窗口創建失敗,線程創建失敗,打開文件失敗),以盡可能恢復,而不是終止程序。對于運行異常,使用斷言是非常不合適的,理由很顯然:
            1) 斷言在發布版不起作用;
            2) 斷言的處理方式不夠友好;
            3) 運行異常不是程序錯誤,沒有必要報告源代碼出錯位置;



            參考資料:

            1.《C++編程規范-101條規則、準則與最佳實踐》/Herb Sutter,Andrei Alexandrescu 著 劉基誠 譯 人民郵電出版社
            2.《C++程序設計語言》/Bjarne Stroustrup 著?裘宗燕 譯 機械工業出版社
            3.《C與C++中的異常處理》/Robert Schmidt 著 無情 譯 http://download.pchome.net/development/reference/11135.html

            posted @ 2006-10-19 21:28 依舊的博客 閱讀(1443) | 評論 (0)編輯 收藏

            2006年9月10日 #

            軟件設計中會碰到這樣的關系:一個對象依賴于另一個對象,必須根據后者的狀態更新自己的狀態,可以把后者稱作目標對象,前者稱作觀察者對象。不但觀察者依賴于目標,當目標的狀態改變時也要通知觀察者,這就出現了雙向的依賴。兩個對象互相依賴的后果是它們必須一起復用。如果一個目標有多個觀察者,那么目標也依賴所有觀察者,從而目標對象無法獨立復用。如何消除目標和觀察者之間的互相依賴呢?觀察者模式幫助我們解決這個問題。

            觀察者模式把目標對觀察者的依賴進行抽象:使目標只知道自己有若干觀察者,但不知道這些觀察者具體是誰,可能有多少個;當目標狀態改變時只要給這些觀察者一個通知,不必作更多的事情。這樣目標對觀察者的依賴就達到了抽象和最小,而目標對具體觀察者的依賴被解除了。

            類圖如下:

            Observer.JPG

            Subject對象保存一個Observer引用的列表,當我們讓一個ConcreteObserver對象觀察Subject對象時,調用后者的Attach()方法,將前者的引用加入該列表中。當Subject對象狀態改變時,它調用自身的Notify方法,該方法調用列表中每一個Observer的Update()方法。一個ConcreteObserver只要重定義Update()就能收到通知,作為對通知的響應,Update()調用Subject對象的getStatus()獲取數據,然后更新自身。當不需要繼續觀察時,ConcreteObserver對象調用Subject對象的Detach()方法,其引用被從列表中移除。

            解除目標對具體觀察者的依賴以后,很容易增加新的具體觀察者,因為不受依賴的方面就可以自由變化;而目標也可以獨立地復用,因為無所依賴的方面就可以不受影響。

            以上主要考慮了一個目標有多個觀察者的情況,我們設法解除了目標對具體觀察者的依賴,使具體觀察者的種類和數目容易改變。有時候一個觀察者觀察多個目標也是有意義的,在前面的類圖中,觀察者對具體目標的依賴仍然存在,因此無法適應目標方面的變化。怎樣抽象這種依賴呢?使觀察者只知道若干個目標會向自己發出通知,而不知道這些目標具體是誰,可能有多少個;在目標向觀察者發送通知時,將一個自身的引用作為參數,然后觀察者調用其抽象方法就可以獲得目標狀態。這就使得觀察者對目標的依賴是抽象的,觀察者對具體目標的依賴被解除了。

            類圖如下:

            Observer2.JPG

            參考資料:

            1.《設計模式-可復用面向對象軟件的基礎》/Erich Gamma等著,李英軍等譯 機械工業出版社

            posted @ 2006-09-10 12:53 依舊的博客 閱讀(2286) | 評論 (0)編輯 收藏

            2006年8月8日 #

            Factory Method-創建多種同類產品的工廠:

            為一類產品 ( 一個抽象產品及其所有具體產品 ) 提供一個工廠,該類的每一種具體產品由工廠中的一個方法創建。種種做法的缺點是不易增加新的具體產品,每增加一個具體產品,工廠中就要增加一個方法,這意味著工廠的所有使用者都要重新編譯。可以用參數化的方法來改進,工廠只提供一個接受參數的創建函數,參數的取值標志了某種具體產品,在創建函數中對參數進行判斷,根據不同的參數值創建不同的具體產品并返回。這就減小了增加具體產品的代價,每增加一種具體產品時只要修改工廠的創建函數的實現即可。

            原來是用工廠的不同方法創建不同的具體對象,現在是用同一個方法的不同參數創建不同的具體對象。還可以用抽象工廠的不同派生類來創建不同的具體對象,這種做法比較笨重.

            Abstract Factory-可替換的工廠:

            見參考資料1


            參考資料:

            1.《設計模式-可復用面向對象軟件的基礎》/Erich Gamma等著,李英軍等譯 機械工業出版社
            2.《敏捷軟件開發-原則,模式與實踐》/Robert C.Martin著 鄧輝譯??清華大學出版社

            posted @ 2006-08-08 13:01 依舊的博客 閱讀(300) | 評論 (0)編輯 收藏

            2006年8月6日 #

            MVC(Model-View-Controller)模式的基本思想是數據,顯示和處理相分離。模型(Model)負責數據管理,視圖(View)負責數據顯示,控制器(Controller)負責業務邏輯和響應策略。

            從MVC的形成過程來看,最初只有模型和視圖兩個元素。模型封裝了數據并提供操作接口,視圖用來表現數據和接收用戶請求。模型是獨立的,而視圖依賴于模型:從模型獲取數據進行顯示;向模型發送用戶請求,并根據返回結果刷新自己。

            需要用多個視圖表現同一模型時,情況發生了變化:一個視圖修改數據以后,不但本身要刷新,其他所有視圖也要刷新。如果由該視圖通知其他視圖,它就需要知道其他所有視圖,由于每個視圖都可能發出修改,每個視圖都要知道其他所有視圖,這種關聯過于復雜,不但難以維護,而且不便于增加新的視圖。如果讓模型通知所有視圖更新,可能會影響模型的獨立性。用觀察者(Observer)模式可以解決上述矛盾,從而實現:由模型通知視圖,而模型不依賴于具體的視圖,具體視圖之間相互獨立。

            視圖是用戶請求的接收者,但不宜作為請求的處理者。因為界面是易變的,如果業務代碼和界面代碼放在一起,頻繁的界面修改可能會破壞比較穩定的業務代碼。將業務邏輯分離出來,由一個控制器負責,就是為了避免這種干擾。

            模型,視圖和控制器的基本協作關系如下圖

            MVC模式協作圖.gif

            模型在狀態變化的時候,直接通知所有視圖,視圖向模型查詢狀態數據,然后刷新自身。當用戶發出操作時,視圖把消息發給控制器,控制器按照業務邏輯進行處理,需要查詢或更新數據時,控制器會調用模型。下面是一個更詳細的示意圖

            MVC模式協作圖2.gif


            同樣的數據,可以有不同的顯示和進行各種處理。顯示僅僅是表現數據,而處理是根據用戶請求改變數據的過程,不但包含業務邏輯,也要提供響應策略。響應策略由控制器負責,視圖可以使用不同的控制器提供不同的響應方式,這是策略(Strategy)模式的應用。

            此外,MVC還允許視圖嵌套,通過使用組合(Composite)模式,一致地處理組合視圖和普通視圖。

            用多個視圖表現一個模型,在視圖不變的情況下改變響應策略,允許視圖嵌套,這是MVC的三個主要特性。在內部結構上,MVC的主要關系是由觀察者模式,策略模式和組合模式給出的。由觀察者模式確定的模型視圖關系是其中最為重要的。

            MVC模式有許多變體。前述結構中,由模型通知視圖刷新,稱為主動MVC;如果由控制器更新模型以后通知視圖,稱為被動MVC結構。在許多應用中,沒有明顯的控制器角色,也沒有視圖嵌套。可見根據實際需要,構成MVC的三個模式上都可能出現變化。Web瀏覽器就是被動MVC結構的一個實例。
            “瀏覽器是一個交互程序,從概念上講,它是由一組客戶、一組解釋器與一個管理它們的控制器所組成。控制器形成了瀏覽器的中心部件,它解釋鼠標點擊與鍵盤輸入,并且調用其他組件來執行用戶指定的操作。例如,當用戶鍵入一個URL或者點擊一個超文本引用時,控制器調用一個客戶從所需文檔所在的遠程服務器上取回該文檔,并且調用解釋器向用戶顯示該文檔。每個瀏覽器必須包含一個HTML解釋器來顯示文檔,其他解釋器是可選的。HTML解釋器的輸入由符合HTML語法的文檔所組成,輸出由位于用戶顯示器上的格式版本文檔所組成。解釋器通過將HTML規則轉換成適合用戶顯示硬件的命令來處理版面細節。HTML解釋器一個最重要的功能是包含可選項。解釋器必須存儲關于顯示器上位置之間關系的信息和HTML文檔中被瞄定的項。當用戶用鼠標選定了一個項,瀏覽器通過當前的光標位置和存儲的位置信息來決定哪個項被用戶選定。”(參考資料5)


            MFC的文檔/視圖結構(Document/View architecture)是MVC模式的一種變體,下面討論它是怎樣實現的。

            文檔/視圖結構沒有體現業務邏輯和視圖的分離,但是將響應策略和視圖區分開來。它主要包含四種對象:

            1. 文檔
            2. 視圖
            3. 視圖框架窗口
            4. 文檔模板

            這里的視圖框架窗口定義了視圖對用戶輸入的響應方式,而文檔模板用來管理前三種對象的組合。文檔,視圖,視圖框架窗口三者是對應的,從而構成一個三元組。一個應用程序可能需要多個這樣的三元組,以實現文檔的多視圖,所以引入文檔模板來表示該三元組。因為程序中可能使用多個文檔模板,MFC用一個文檔管理者對象來管理它們。

            在MFC中,應用程序和主框架窗口是用來封裝底層機制的對象,文檔,視圖,視圖框架窗口和文檔模板是用來構架文檔/視圖結構的對象。應用程序通過文檔管理者來使用文檔/視圖結構。

            如果要給文檔增加一種視圖,只需要增加一個文檔模板;如果要改變一種視圖的響應策略,只要改變對應文檔模板中的視圖框架窗口。

            <未完待續>


            參考資料:

            1.《設計模式-可復用面向對象軟件的基礎》/Erich Gamma等著,李英軍等譯 機械工業出版社
            2.《Java與模式》/閻宏 電子工業出版社
            3.  模型-視圖-控制器 ( MSDN > 技術資源庫 > 體系結構 > 使用 Microsoft .NET 的企業解決方案模式 >第3章 Web 表示模式)
            4. 《Java設計:對象,UML和過程》/Kirk Knoernschild 著,羅英偉等譯 人民郵電出版社
            5. 《計算機網絡與因特網》/D.E.Comer 著 徐良賢等譯 機械工業出版社
            6.《深入解析MFC》/中國電力出版社
            7.《VC技術內幕》第5版 / 希望電子出版社

            posted @ 2006-08-06 10:05 依舊的博客 閱讀(3046) | 評論 (0)編輯 收藏

            2006年6月1日 #

            1. 一個用例可以有多個參與者,并且可以同時有多個參與者。
            用例和參與者的關聯可以是雙向的,參與者和用例都可以發起通信。

            2. 用例之間的基本關系有:泛化,包含和擴展。
            用例A到B的泛化關系表示A和B是具體與抽象的關系。
            用例A到B的包含關系表示A使用了B提供的功能。
            用例A到B的擴展關系表示A向B提供的可用的功能。
            但從A到B的包含關系和從B到A的擴展關系是不同的:
            A包含B說明B是從A中分解出來的公共行為;B自身是獨立的,但對于A來說是不可缺少的一部分。
            B擴展A說明B是從A中分解出來的變體行為,必須指定擴展點,也就是在基本用例中執行變體行為的具體條件。B僅僅是A的補充,而不是不可缺少的部分,B自身也不是獨立的。A可以單獨執行,表示通常的情況,在特定的情況下,用B來補充它。
            抽象用例不能被實例化,不能被實際執行,它的作用在于更好地組織用例關系。




            參考書:
            《UML用戶指南》/Grady Booch,James Rumbaugh,Ivar Jacobson著 邵維忠等譯 機械工業出版社
            《統一軟件開發過程》/Ivar Jacobson,Grady Booch,James Rumbaugh著 周伯生等譯 機械工業出版社

            posted @ 2006-06-01 17:18 依舊的博客 閱讀(469) | 評論 (0)編輯 收藏

            2006年5月18日 #

            我曾經在有一個Windows XP的機器上另裝一個Windows 2000,結果2000裝完后XP不能啟動了。后來上網看了一些資料,又幾經試驗,解決了這個問題。這里總結一下其中的原理,這個現象很容易在重裝低版本系統后出現,而且弄清原理以后,我們可以更自由地處理多系統安裝的問題。

            單個操作系統的引導過程是這樣的:首先,主引導記錄(MBR)被加載到內存運行,它讀取磁盤分區表(DPT),查找第一個活動分區(可引導分區),該分區的引導扇區存放著操作系統的引導記錄。然后,系統引導記錄被加載到內存運行,它從系統安裝目錄讀取系統的啟動文件,將其加載執行,控制隨后的啟動過程。

            這里面涉及到一些程序和數據,它們存放在不同的地方,在不同階段運行。第一段程序MBR,它的數據是DPT,它們存放在磁盤的主引導扇區。第二段程序是系統引導記錄,存放在系統所在分區的引導扇區。第三段程序是系統啟動文件,存放在系統所在分區系統安裝目錄中。這三段程序像接力跑一樣,前一段程序的工作就是加載后一段程序,并把控制交給它。 引導記錄和啟動文件隨操作系統而不同, 是在安裝時形成的,每個系統的安裝程序都把其引導記錄寫入安裝分區的引導扇區,而啟動文件是系統的一部分。

            上面的引導過程有一個基本缺陷,就是只能引導一個系統,并且只能引導裝在第一活動分區的系統。

            如果一個操作系統不在活動分區,那么該系統要被引導有三種辦法,改寫MBR,改寫第一活動分區引導記錄,或把所在分區設為第一活動分區。最后一種做法是不方便的,系統通常會改寫前兩段引導程序,那么它在解決自身引導問題的同時,也不能破壞其他系統的引導,這就引出了多系統地引導問題。常見的做法是系統提供一個啟動管理器接管引導過程。啟動管理器能夠獲得機器上多個系統的引導記錄,從而可以根據用戶選擇啟動不同的系統。系統在安裝時改寫磁盤第一活動分區的引導記錄,使啟動管理器被作為第三段程序加載。

            如果啟動管理器能夠知道機器上每個系統所在的分區,就能獲得該系統的引導記錄,從而可以引導該系統。但實際上,啟動管理器所屬系統的引導記錄是不能再次被加載的,必須特殊對待。同一系列的系統,也可能有類似的問題。所以啟動管理器可能要了解機器上每個系統具體如何啟動,相應進行引導。這樣只有讓高版本的系統提供啟動管理器,因為低版本的啟動管理器無法啟動高版本系統。
            2000/XP的啟動管理器是OS Loader。它對98和2000/XP的引導就是不同的,對98是加載98引導記錄的鏡像文件,對2000/XP是加載HAL.DLL等文件。OS Loader在引導多系統時,對于windows系列的引導有特殊性,必須向下兼容。

            OS Loader的載體是ntldr文件,它運行時還會讀取一個配置文件boot.ini,兩個文件都存放在磁盤第一活動分區根目錄。boot.ini記錄了每個系統所在的分區,每個版本的windows在安裝時都會在boot.ini中填寫有關自身的一項。2000/XP在安裝時都會更新OS Loader和重寫第一活動分區的引導記錄,后安裝者的兩個程序才會被保留。如果后裝2000,由于前述的OS Loader版本問題,就可能無法引導XP。

            posted @ 2006-05-18 16:27 依舊的博客 閱讀(919) | 評論 (0)編輯 收藏

            2006年5月15日 #

            我們知道MFC的作用在于封裝Windows的編程接口,并提供應用程序框架的開發模式。為了完成從前者到后者的過渡,MFC實現了幾種基本機制,它們是消息映射,命令傳遞,運行時類信息(RTCI),動態創建和序列化。

            消息映射和命令傳遞是對SDK程序交互機制的封裝。序列化是應用程序需要的一種基本特性,即把數據保存到磁盤和從磁盤打開數據。通過RTCI和動態創建,可以把軟件的對象數據保存到磁盤,反過來從這些數據識別和恢復對象,從而實現對象的序列化。基于數據庫的序列化機制和這種方式不同,應用程序和數據庫之間有一個約定,以什么樣的格式保存什么樣的數據,再以同樣的方式打開,并且如何重建對象數據也定下來了,在打開數據時,應用程序不需要有適應性,不需要識別數據類型,也不需要根據在運行期才確定的類型名稱創建其對象。

            動態創建就是創建某種類型的對象,具體類型在運行時確定,編譯時可能不知道。比如運行時用戶輸入一個類型名稱,如果該類型是程序類型體系中的一員,則程序中將能夠創建該類型的對象。下面的代碼是使用MFC動態創建機制的一個簡化的例子:

            CRuntimeClass* g_pFirstClass;
            void func()
            {
                 char szClassName[64];
                 CRuntimeClass* pClass;
                 CObject* pObject;
                
                 cout << "enter a class name...  ";
                 cin >> szClassName;
                
                 for (pClass = g_pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
                 {
                      if (strcmp(szClassName, pClass->m_lpszClassName) == 0)
                          pObject = pClass->CreateObject();
                 }
            }

            實現動態創建的思路是把動態的類型名稱與程序類型體系中的每一個進行比較,與某個類型吻合時讓該類型創建自身的對象。這樣,支持動態創建的類庫中的每一個類都要額外實現一些功能,即判別一個名稱是否與自身相符,以及創建自身的對象。

            判別一個名稱是否與自身相符,這是運行時類識別的內容,所以MFC動態創建是在RTCI基礎上實現的。

            RTCI是一個對象能夠判定自己是否屬于某種類型,該類型的名稱在運行時確定,編譯時可能不知道。從下面的例子很容易理解RTCI,

            void Func()
            {
                 char szClassName[64];
                 CDocument* pDoc = new CDocument;
                
                 cout << "enter a class name...  ";
                 cin >> szClassName;
                
                 cout << pDoc->IsKindOf(szClassName); //是返回1,否返回0
            }

            有一點需要說明的是,因為CDocument派生于CObject,所以IsKindOf對于CObject也要返回1。因為我們是從動態創建出發的,所以如果是這樣可能會有一點背離初衷。但是RTCI明顯和動態創建有密切聯系,RTCI也可能有單獨的價值,所以先把RTCI實現起來。

            實現RTCI的思路是讓每一個類記錄自身的類型信息,并提供IsKindOf(char*)函數進行所給類型與自身類型的比較,而且還要能訪問基類的類型信息,進行比較,一直到根類。所以記錄的類型信息要按繼承關系連起來,每個類的IsKindOf()還要調用基類的IsKindOf()。MFC把要記錄的類型信息抽取到一個CRuntimeClass結構體中,每個類中加入一個CRuntimeClass成員即可。

            現在回到動態創建,在RTCI建立的數據結構基礎上將可實現它。動態創建從不同于IsKindOf()的角度使用這一數據結構,它要遍歷所有類型的CRuntimeClass。那么僅僅有繼承關系的類的CRuntimeClass相連還不夠,要把所有類的CRuntimeClass連成一個鏈表。其實動態創建并不關心類間的繼承關系,它平等看待每個類。現在以CRuntimeClass為結點構成一個縱橫兩個方向的鏈表,IsKindOf()和動態創建分別使用它不同的側面。

            序列化的概念是在文件中存儲對象信息,并能根據它恢復對象。對于文檔視圖結構的軟件,用戶需要保存所編輯的文檔和打開已編輯的文檔,這正是序列化的應用,所以序列化是非常重要的一種特性。在序列化恢復對象時,就可以用到動態創建。

            使用MFC序列化的例子如下,

            void CMyDocument::Serialize(CArichive &ar)
            {
                if (ar.IsStoring())
                {
                    ar << m_pMyClass; //CMyClass m_pMyClass;
                }
                else
                {
                    ar >> m_pMyClass;
                }
            }

            一個支持序列化的類提供Serialize(CArchive &)函數,重載<<和>>操作。注意兩者是不同的,在上例中,CMyDocument類的信息并不被序列化,而CMyClass類的信息被序列化。實際上一個序列化類的<<和>>操作,其不涉及類信息的部分是調用Serialize()完成的,它必須同時實現這兩者。

            按照MFC的要求,需要在支持序列化的類定義中使用DECLARE_SERIAL宏,在類實現中使用IMPLEMENT_SERIAL宏。我們看一下這兩個宏實現了什么,

            #define DECLARE_SERIAL(class_name) \
             _DECLARE_DYNCREATE(class_name) \
             AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);

            #define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
             CObject* PASCAL class_name::CreateObject() \
              { return new class_name; } \
             _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
              class_name::CreateObject) \
             AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
             CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
              { pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
               return ar; } \

            主要是加入了對>>的重載,但是沒有重載<<,MFC僅提供了CObject對<<的重載,如下,

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

            這是因為在序列化讀和寫的時候,都需要具體類的CRuntimeClass信息。相應的GetRuntimeClass()是一個虛函數,CObject重載<<,在寫類信息時調用到該函數,由于虛函數機制,寫入的是具體類的信息。但是這里隱含著一個條件,就是調用<<和GetRuntimeClass()時,具體類對象已經存在了,而調用>>和讀入類信息時,該類對象還未被創建,所以無法利用這種機制,只能在每個具體類中都重載一次>>。我覺得《深入解析MFC》對這個問題的解釋不正確。

            這里有一個問題需要明確一下,序列化為什么要寫入類信息?一是它應該保存完整的能夠獨立恢復對象的信息,二是在程序讀入對象時,要把它的類信息和程序中期望的(所能處理的)類信息相比較,進行檢驗。

            看IMPLEMENT_SERIAL宏對重載>>的實現,是提供一個期望的CRuntimeClass結構(用于檢驗),委托CArchive進行對象讀取。因為讀對象時首先要跟文件打交道,所以交給CArchive處理,隨后把讀出的數據寫入對象時,CArchive再調用具體類的Serialize(),如此合作是十分恰當的。在這里,CArchive還負責了讀出和檢驗類信息,然后創建對象的過程。因為一方面具體類對象還不存在,另一方面這些操作對所有具體類都沒有分別,應該提出來,在類級別實現或者讓合作者實現。實際上,MFC先把這個過程交給CArchive::ReadClass(),后者又調用CRuntimeClass::Load()。 

            對于序列化來說,搞清它的概念以后,就是實現Serialzie(),重載<<和>>。對<<和>>的重載涉及很多工作,MFC已經幫我們實現了,我們也看見了大概的設計,主要是與CArchive分工合作,其次是CRuntimeClass。

            現在看到CRuntimeClass結構體在MFC對RTCI,動態創建和序列化的實現中都起著重要的作用,重新認識一下這個數據結構很有必要。

            CRuntimeClass包含了關于類的各種信息和有關操作。把類及其基類的CRuntimeClass連成一個鏈表,就可以很方便地實現RTCI的IsKindOf();把所有類的CRuntimeClass連成一個鏈表,再加上一個簡單的CreateObject函數,就可以對以任意類名進行動態創建的企圖做出反應;CRuntimeClass還實現了向文件讀寫類信息的Load(),Store(),配合序列化的實現。

            在分析消息映射和命令傳遞機制之前,需要對Windows程序模型有很好的理解。

            未完待續...


            參考:

            《深入解析MFC》/中國電力出版社
            《深入淺出MFC》/華中科大出版社
            《Windows程序設計》/北大出版社

            posted @ 2006-05-15 19:33 依舊的博客 閱讀(1151) | 評論 (0)編輯 收藏

            2006年5月12日 #

            欣賞好的思路是一件愉快的事,特別是對我不會做的題目。

            1. 問題:對32位的二進制整數,不用循環,求出其中1的個數。

            #define?POW(c)?(1<<(c))
            #define?MASK(c)?(((unsigned?long)-1)?/?(POW(POW(c))?+?1))
            #define?ROUND(n,?c)?(((n)?&?MASK(c))?+?((n)?>>?POW(c)?&?MASK(c)))

            int?bit_count(unsigned?int?n)
            {
            ????n?
            =?ROUND(n,?0);
            ????n?
            =?ROUND(n,?1);
            ????n?
            =?ROUND(n,?2);
            ????n?
            =?ROUND(n,?3);
            ????n?
            =?ROUND(n,?4);
            ????
            return?n;
            }

            基本的想法是把所有的1加起來,得到的就是1的個數。我們需要把這些1分離出來,每個1都是平等的,與其位置無關。難題在于不能一個一個去取,那就用到了循環,當然遞歸也是不允許的。需要有一種統一的辦法,可是很難想象具體該怎樣。我們逐步地做這件事,假設前16位和后16位分別求得了1的個數,那么加起來就行了。16位二進制中的1仍然是未知的,隨機出現的,問題的性質沒有變,但我們可以繼續分解,這種逐步的做法不一定就意味著遞歸。每個16位分解為兩個8位,...,每個2位分解為兩個1位,把兩個1位上的數相加就是這兩位上1的個數。現在需要取出每一位上的數嗎?如果想到了這個問題,就離最終的思路不遠了。現在32位已經分成了16個兩位,很容易將其看作兩個16位,一個是所有奇數位,一個是所有偶數位。我們不難把這兩個16位分開,然后移位相加,就求出了每兩位中1的個數。到了這一步,以后的思路就很自然了。


            參考:

            《計算二進制位'1'的個數》來自?http://kaikai.cnblogs.com
            posted @ 2006-05-12 23:14 依舊的博客 閱讀(662) | 評論 (2)編輯 收藏

            僅列出標題  下一頁
            久久久久久噜噜精品免费直播| 亚洲午夜精品久久久久久浪潮| 久久精品国产亚洲AV嫖农村妇女 | 精品无码久久久久久久久久| 久久99精品国产麻豆蜜芽| 国产精品一区二区久久精品涩爱| 久久精品国产第一区二区三区| 精品乱码久久久久久夜夜嗨| 色偷偷久久一区二区三区| 久久精品夜色噜噜亚洲A∨| 久久精品国产亚洲AV蜜臀色欲 | 午夜精品久久久久久99热| 久久久精品一区二区三区| 午夜精品久久久久久| 亚洲国产成人久久精品影视| 日韩精品久久无码人妻中文字幕| 久久精品国产色蜜蜜麻豆| 久久精品a亚洲国产v高清不卡 | 精品久久久无码中文字幕天天| 色欲综合久久躁天天躁蜜桃| 久久久久综合中文字幕 | 久久成人精品| 久久被窝电影亚洲爽爽爽| 亚洲精品乱码久久久久久蜜桃不卡 | 婷婷综合久久狠狠色99h| 成人午夜精品无码区久久| 午夜精品久久久久久久无码| 成人亚洲欧美久久久久 | 国产精品一久久香蕉国产线看观看| 少妇久久久久久被弄到高潮 | 久久91精品国产91| 久久亚洲欧洲国产综合| 久久久久99精品成人片三人毛片| 99久久精品无码一区二区毛片 | 青青热久久国产久精品| 久久久久久av无码免费看大片| 国产精品亚洲综合专区片高清久久久 | 中文字幕乱码久久午夜| 综合人妻久久一区二区精品| 伊人久久大香线蕉亚洲| 久久久久久国产精品免费无码|