天天看點

MFC集合類2

  許多C++程式員都使用标準模闆庫(STL),因為用它很容易實作數組、連結清單、映射以及其它容器。STL語言中“容器”指的是儲存“資料集合”的對象。但是在有STL之前,已經有MFC了。在稱為“MFC集合類”的一系列類中,MFC提供了自己的數組、連結清單、以及映射的實作途徑。雖然在MFC中使用STL非常安全,但許多MFC程式員還是更喜歡用MFC集合類,一方面原因是更熟悉MFC,另一方面原因是不願意連結2個獨立的類庫增加應用程式的exe的尺寸。 STL中的集合類跨平台好,有較多的泛型算法。

1、數組

      C和C++的一個最大缺陷是數組不進行邊界檢查,如下代碼,它反映了C和c++應用程式中最常見的一種錯誤:

MFC集合類2

此代碼出錯是由于for循環中的最後一次疊代指派超出了數組的範圍。在運作時會産生非法存取錯誤。

   C++程式員經常通過編寫數組類并在類内部進行邊界檢查來解決此問題。下面給出的數組類有Get和Set函數,用了檢查傳遞給它們的下标,如果傳遞來的下标無效就進行斷言處理:

MFC集合類2

這樣就會避免非法存取錯誤的發生。

1.1MFC數組類

      你不必親自編寫數組類,MFC已經提供了各種各樣的數組,首先是一般的CArray類,它實際上是一個模闆類,利用它可以建立任何資料類型的“類型安全數組”。在頭檔案Afxtempl.h中定義了CArray。其次是非模闆化的數組類,分别為儲存特定類型的數組而設計。這些類在Afxcoll.h中定義,下面說明了非模闆化的數組類以及它們所儲存的資料類型:

CByteArray          8位位元組(BYTE)

CWordArray        16位位元組(WORD)

CDWordArray      32位雙位元組(DWORD)

CUIntArray          無符号整型(UINT)

CStringArray        CString

CObArray             CObject指針

CPtrArray             void指針

      隻要學會使用這些數組類中的一種,也就會用其它數組類了,因為它們共享公共的一組成員函數。下例聲明一個包含10個UINT的數組并用數字1-10對它進行了初始化:

MFC集合類2

在這兩個例子中,都是用SetSize來指定數組包含10個元素;重載[]運算符調用數組的SetAt函數,該函數将 值 複制到 數組中 指定位置處的 元素 中;如果數組邊界非法,程式将執行斷言處理。邊界檢查内置在SatAt代碼中:ASSERT(nIndex>=0 && nIndex<=m_nSize);在MFC源程式檔案Afxcoll.inl中可以看到此代碼。

      可以使用InsertAt函數在不覆寫已有數組項的情況下給數組插入元素項。與SetAt不同,SetAt隻是給已存在的數組元素指派,InsertAt還要給新的元素配置設定空間,通過把插入點後面的元素向後移動來完成。InsertAt是那些便于使用的函數之一,它們在新的元素項添加到數組中時指定增加數組尺寸。使用[]運算符将調用GetAt函數,該函數将從數組中的指定位置取回一個值(當然要進行邊界檢查)。如果願意可以直接調用GetAt而不是通過[]運算符。

      要确定數組包含元素的個數,可以調用數組的GetSize函數,還可以調用GetUpperBound傳回數組的上界 下标,因為下标從0開始,是以其值為數組元素總數減1。

      MFC的數組類為從數組中删除元素提供了2個函數:RemoveAt和RemoveAll。RemoveAt從數組中删除一個及一個以上的元素,并将被删元素後面的所有元素前移。RemoveAll清空整個數組。兩個函數都将調整數組的上界 進而反映出被删除的元素項個數。如果被删除的元素是指針,它并不删除指針所指的對象。如果數組是CPtrArray或CObArray類型的,要清空數組并删除指針所指的對象就應該寫成:

MFC集合類2

如果對位址儲存在指針數組中的對象删除失敗,就會導緻記憶體洩露。

1.2動态調整MFC數組大小

      除了可以邊界檢查外,MFC數組類還支援 動态調整大小。由于 為儲存數組元素 而配置設定的記憶體可以“根據 元素 的 添加或删除 而”增大或縮小。是以沒有必要事先預見動态調整尺寸的數組具有多少元素。

      一種動态增大MFC數組的方法是調用SetSize。可以在任何需要的時候調用SetSize來配置設定額外的記憶體。假設開始的時候給數組設定了10個元素項,後來卻發現需要20個,這時隻要第二次調用SetSize給額外的項配置設定空間即可。用此方法調整數組大小時,原來的項仍舊保持它們的值。是以,在調用SetSize之後新項需要明确的初始化。

      另一種增大數組的方法是調用SetAtGrow而不是SetAt來添加元素項。例如:CUIntArray array;array.SetAt(0,1);此代碼會執行斷言處理。因為數組大小為0,SetAt不會自動增大數組來容納新的元素。但是,将SetAt更改為SetAtGrow後,程式将順利執行。與SetAt不同,SetAtGrow會在必要時自動增大數組的記憶體配置設定空間。Add函數也是這樣,他将元素添加到數組的末尾。其它可以自動增大數組來容納新的元素項的函數還包括:InsertAt、Append(将一個數組附加給另一個數組)以及Copy(将一個數組複制到另一個數組)

      由于每當數組尺寸增加時都要配置設定新的記憶體,是以太頻繁的增大數組會對操作産生不好的影響并有可能導緻産生記憶體碎片。如下代碼:

CUIntArray array;

for(int i=0;i<100000;i++)

      array.Add(i+1);

這些語句看上去非常正确,但它們效率卻不高,要申請配置設定成千上萬個獨立的記憶體。這也正是MFC讓你在SetSize中可選的第二個參數指定“增加量”的原因。下面的代碼更有效的初始化了一個數組,它告訴MFC在需要申請更多的記憶體時,每次配置設定10000個UINT的記憶體空間。

CUIntArray array;

array.SetSize(0,10000);

for(int i=0;i<100000;i++)

      array.Add(i+1);

當然,要是預先給100000個元素配置設定空間,那麼程式的效率會更高一些。但事先不可能預見到數組要儲存的元素的數量。如果能預見到能給數組增加許多元素卻不能确定到底需要多少空間,那麼指定大的增加量是有益的。如果你沒有指定增加量,MFC會通過“基于數組尺寸 得到的 簡單公式”為你選擇一個值。數組越大,增加量也越大。如果指定數組尺寸為0,并且根本沒有調用SetSize,那麼預設增加量為4項。

      同樣一個用來增大數組的SetSize函數也可以用來減少數組元素。但是,當它減少數組時,SetSize并不會自動縮小儲存數組資料的緩沖區,除非調用FreeExtra函數之後。

1.3用CArray建立“類型安全”數組類

      CUIntArray、CStringArray、以及其它MFC數組類都是針對特定資料類型的。如果假設需要一個其它資料類型的數組,例如CPoint對象的數組,由于不存在CPointArray類,是以必須從CArray類中自己建立了。CArray是一個模闆類,用它可以為任意的 資料類型 建立 “類型安全”數組類。

      為了明了起見,下面用一個自己聲明的CPoint類來建立CPoint類型的“類型安全”數組,并對類進行執行個體化。

CArray<CPoint,CPoint&> array;

模闆中的第一個參數指定了儲存在數組中的資料類型,第二個參數指定“類型在參數清單中”的表示方法。

      使用CArray和其它基于模闆的MFC集合類工作的時候,在建立的類中包含預設的構造函數很重要,因為MFC在類似的InsertAt這樣的函數被調用時會使用類的預設構造函數來建立新的元素項。

      有了可以随意處理的CArray,如果願意的話,你可以不使用項CUIntArray這樣的老式MFC數組類而隻使用模闆。下面語句用typedef定義了一個CUIntArray資料類型,功能與MFC的CUIntArray等價:typedef CArray<UINT,UINT> CUIntArray;最終選擇哪個類取決于你自己。但是MFC資料中卻建議盡可能的使用模闆類,因為這樣做可以與現代C++程式設計慣例保持一緻。

2、連結清單

      Insert和RemoveAt函數使得給數組添加和删除元素非常友善。但這種插入和删除的簡便方法也是有代價的:如果在數組的中間插入或删除元素,數組高端元素就會在記憶體中向上或向下移動。在用此方法處理大型的數組時,這種操作付出的代價是十分昂貴的。

2.1MFC連結清單類

      MFC的模闆類CList實作了一般的連結清單,用它可以自定義處理任何資料類型。MFC還提供了下面列出的處理特定資料類型的非模闆連結清單類。這些類主要用于與MFC舊版本相容,在現代的MFC應用程式中并不經常使用。

    類名             資料類型

CObList          CObject指針

CPtrList          void指針

CStringList     CString

MFC連結清單是雙向連結的,便于前後移動操作。連結清單中的位置由抽象數值POSITION辨別。對于連結清單,POSITION實際上是指向CNode資料結構的指針(該結構代表了連結清單中的連結清單項)。CNode包含3個字段:1、一個指向連結清單中下一個CNode結構的指針2、一個指向連結清單中上一個CNode結構的指針3、連結清單項的資料。無論是在連結清單頭還是連結清單尾,或是在POSITION指定的任何位置,插入操作都是快速高效的。還可以對連結清單進行查詢操作,但是由于查詢涉及到順序周遊連結清單并逐個檢查連結清單項,是以要是連結清單很大的話會占用很多時間。

      AddTail函數在連結清單結尾處添加一個連結清單項。要給連結清單頭部添加連結清單項可以使用AddHead函數。在連結清單頭或尾删除連結清單項同樣簡單,隻要調用RemoveHead或RemoveTail即可。RemoveAll函數一下删除所有的連結清單項。

      例如:每次給CStringList添加一個字元串時,MFC都會将字元串 複制 給CString并在相應的CNode結構中儲存它。是以,用來初始化 連結清單 的字元串 超出 建立連結清單時設定的範圍 是完全可以接受的。

      一旦連結清單建立成功,就可以使用GetNext和GetPrev函數通過疊代在連結清單中前後移動了。兩個函數都接受“表示連結清單中目前位置的POSITION值”并傳回該位置處的連結清單項。兩者都要更新POSITION值來引用下一個或上一個連結清單項。可以使用GetHeadPosition或GetTailPosition來檢索連結清單中連結清單頭或者連結清單尾的POSITION。如果隻是希望得到連結清單頭或連結清單尾的連結清單項,可以使用GetHead或GetTail函數,由于位置已經隐含在調用中了,是以它們都不需要輸入POSITION值。

      如果給定辨別特别連結清單項的POSITION值,就可以使用連結清單的At函數來檢索、修改、或删除它:

CSting str=list.GetAt(pos);//Retrieve the item

list.SetAt(pos,"florida state");//Chage it

list.RemoveAt(pos);//Delete it

還可以使用InsertBefor或InsertAfter在連結清單中插傳入連結表項:

list.InsertBefore(pos,"Florida state");

list.InsertAfter(pos,"Florida state");

連結清單的特性決定了這樣進行插入和删除操作效率非常高。

      MFC的連結清單類還包含這樣2個成員函數,可以用來執行查找操作。FindIndex接受從0開始的索引号并傳回 連結清單 中相應位置處的 連結清單項的POSITION值。Find查找與指定輸入比對的連結清單項,并傳回它的POSITION。對于字元串連結清單它比較字元串,對于指針連結清單它比較指針,但并不尋找和比較指針所指的連結清單項。要在字元串連結清單中查找“Tennessee”隻需調用一個函數:

POSITION pos=list.Find(“Tennessee”);預設狀态下,Find從頭至尾查找連結清單。如果願意,可以在第二個參數指定查找的起始點。

      可以用GetCount函數了解連結清單中元素的個數。如果GetCount傳回0,說明連結清單是空的,而檢測空連結清單的最好方法是調用IsEmpty。

2.2用CList建立“類型安全”連結清單類

      可以利用MFC的CList類為所選的任何資料類型建立 類型安全 的連結清單類。如:CList<CPoint,CPoint&> list;與CArray一樣,第一個參數指定了資料類型,第二個參數指定了參數清單中連結清單項的傳遞方式(通過引用)。

      如果在CList中使用了類而不是原始資料類型并且調用連結清單了Find函數,除非下列條件之一成立,否則程式不會得到編譯:

@類具有重載了的==運算符,執行與相似對象的比較

@用特殊類型的版本覆寫了模闆函數CompareElements,執行對2個執行個體的比較。

第一種方法更常用,在MFC類如CPoint和CString中已經為你實作了。如果自己親自編寫一個類,就必須進行運算符重載

MFC集合類2
MFC集合類2

覆寫CompareElements消除了對重載運算符的需要。

4、類型指針類

      名字中帶有Ptr和Ob的MFC集合類(如:CPtrArray、CObArray、CPtrList、CObList)可以友善的 實作 儲存一般指針(void)的容器和 儲存 指向MFC對象指針(由CObject派生類建立的對象)的容器。使用Ptr和Ob類的問題出在它們太一般了。通常要求許多強制類型的轉換,這對于許多C++程式員而言是令人生厭的,而且也是糟糕的程式設計風格。

      MFC的“類型指針類”用來以安全的方式處理指針集合,它為 儲存指針 而不 危害類型安全,提供了一種簡便的解決方法。如下“類型安全指針類”:

         類名                            說明

CTypedPtrArray             管理指針數組

CTypedPtrList                管理指針連結清單

CTypedPtrMap               管理 使用指針做為項目或關鍵字 的映射表

      假設你編寫了一個繪圖程式,并且建立了一個名為CLine的類來代表螢幕上繪制的線段。每次使用者繪制一條線就建立一個新的CLine對象。如果需要一個地方來儲存CLine指針,而且希望能夠在集合的任何位置添加和删除指針都不會造成操作沖突,是以你決定使用連結清單,因為CLine是從CObject派生來的,是以CObList好像是個自然的選擇。

      CObList可以完成任務,但是每次從連結清單中檢索到一個CLine指針,都必須将它強制轉換為CLine*,因為CObList傳回的是CObject指針。CTypedList是一個很好的選擇,它不需要類型強制轉換,代碼如下:

CTypedPtrList<COblist,CLine*> list;

當你使用GetNext檢索一個CLine指針時,得到的就是一個CLine指針而不需要強制轉換,這就是類型安全。

      CTypedPtrList和其它“類型安全指針類”一樣要從“第一個模闆參數指定的類”中派生實作。

所有儲存指針的MFC集合類,它們從數組、連結清單或映射表删除指針,但絕不會删除指針所指的項目。是以,在清空一個CLine指針連結清單之前,也有必要删除CLines:

POSITION pos=list.GetHeadPosition();

while(pos!=NULL)

      delete list.GetNext(pos);

list.RemoveAll();

記住:如果你不删除CLines,沒有人會為你删除。不要以為集合類會為你幹這種事。

繼續閱讀