C++BUILDER中幾種容器的使用

huigezi123發表於2010-03-22

C++BUILDER中幾種容器的使用 -------BCB中控制元件陣列的實現 C++BUILDER是Borland公司基於C++的快速開發工具,它簡單的使用方法和強大的功能一直深受很多程式設計人員的好評。C++BUILDER(以下簡稱BCB)的元件庫跟DELPHI一樣,都是VCL。跟微軟的MFC不同,VCL完全是用Object Pascal語言編的。也因此,使BCB同時獲得了Pascal和C++的強大功能。 介紹在BCB中實現控制元件陣列的文章不少,但是實現方法上,大多用VCL自帶的Tlist類實現。實際上,除了這種方法,在BCB中實現控制元件陣列還是有不少其他方法的。這裡,我就談談這幾種實現控制元件陣列的方法,實際上,也是對BCB中常用的容器做一個小結。 BCB完全支援VCL和標準C++,這就導致了存在有VCL版本和標準C++兩個版本的容器。在這裡,我會給出三種實現方法,一種是VCL的TList方法,一種是VCL的DynamicArray(動態陣列)方法,還有一種就是標準C++的STL實現方法。相信這些基本已經涵蓋了BCB中常用的幾種容器型別。 我設計了一個例子來演示我說的容器以及它們的使用方法。 首先先讓我介紹一下我的例程。很簡單卻很有代表性的一個小程式,下面是它的介面: 你可以看到,在這個例程裡,我們管理了一組TImage控制元件(就是那裡面的“山”圖示),我們可以往我們這堆“山”裡隨意新增新的“山”(會以隨機的形式在Panel中出現),我們還可以刪除任意一個我們不想要的“山”。同時,我還提供了“全部清除”,可以用遍歷的方式全部清除所有的“山”。我要講的這三個容器的都是以這個程式作為例程,外表上唯一的不同就是這個窗體的標題會因例而異。 我就先說說大家用的最多的Tlist類。 VCL提供的Tlist類實現了可以動態儲存的線形連結串列物件。它實際上是以儲存指標(鏈式儲存)的方式實現的,但它同時提供了順序儲存的查詢方式。可以通過Items屬性像訪問一個陣列那樣訪問Tlist物件的每一項。除此之外,Tlist類提供方便全面的連結串列功能。例如,全套插入刪除查詢排序的物件方法以及包含Count屬性訪問連結串列中專案的個數。 概念說完了,我們就來看看我這個TList版本的“今天能看見山”是怎麼實現的。窗體是再簡單不過了,無非是一個Form,一個Panel,一個ListBox,三個Button而已。這幾個控制元件的簡單堆砌我就不在贅述了。 TList是一個容器,但是它本身也是一個類,因此我們在使用之前也得先建立這個類。為了簡單起見,我把這個TList類的指標定義成了一個私有指標以便這個類中的所有函式都能方便地訪問到它。因此我在標頭檔案的Private段裡有如下定義: TList *HillList; 接下來我們再定義三個私有函式用來響應三個不同的按鍵。於是我的Private段中又多了這樣一些東西: void __fastcall AddHill(); void __fastcall DeleteHill(); void __fastcall ClearAll(); 好了,現在我們該來關係函式的具體實現了。首先,我得讓我的容器建立起來,於是我在窗體的建構函式中執行了: HillList = new TList(); HillList記載了指向這個容器的指標。下面我給出函式實現的原始碼並在其中加以說明。 /**往控制元件陣列中增加成員**/ void __fastcall TfrmHill::AddHill() { TImage *temImage; //定義臨時TImage指標 HillList->Add(new TImage(this)); //構造新Image物件並把它加入控制元件陣列內 HillListBox->Items->Add("A Hill"); //向ListBox中寫入“A Hill” temImage = (TImage *)HillList->Items[(HillList->Count-1)]; //獲取剛構造的Image物件 temImage->Picture->LoadFromFile("hill.ico"); //裝入圖片 temImage->Top = random(HillPanel->Height); //將新Image的位置定為 temImage->Left = random(HillPanel->Width); // Panel中的隨機值 temImage->Parent = HillPanel; //此句必不可少,用來說明 } // Image是在Panel中出現。 //--------------------------------------------------------------------------------------------------------------------- /**刪除控制元件陣列中選定的成員**/ void __fastcall TfrmHill::DeleteHill() { if(HillListBox->ItemIndex>=0) //判斷ListBox中是否有選中的物件 { TImage *temImage = (TImage *)HillList->Items[HillListBox->ItemIndex]; //裝入臨時指標 delete temImage; //刪除指標所指物件 HillList->Delete(HillListBox->ItemIndex); //刪除控制元件陣列中的指標記錄 HillListBox->Items->Delete(HillListBox->ItemIndex); //在ListBox中刪除一個物件 } } 注意,在上面刪除物件的操作中,我們先得到指向控制元件陣列中物件的指標,然後用delete操作符呼叫物件的解構函式銷燬該物件,這一步千萬不能省,這就是一個可能發生記憶體洩露的機會。接著,還得在控制元件陣列中刪除指向該物件的指標,這樣一來,如同誅滅九族一般所有跟這個物件有關係的就全部被銷燬了。 //--------------------------------------------------------------------------------------------------------------------- /**清空控制元件陣列**/ void __fastcall TfrmHill::ClearAll() { TImage *temImage; //建立臨時指標 for(int i = 0;iCount;i++) //便歷控制元件陣列 { temImage = (TImage *)HillList->Items[i]; //將臨時指標指向待操作物件 delete temImage; //銷燬該物件 } HillList->Clear(); //清空控制元件陣列,此時控制元件陣列內的所有指標指向的物件 //都已經被銷燬,如果在這時訪問這些地址,將會引起一 //個AV錯誤 HillListBox->Items->Clear(); //清空ListBox } 刪除全部物件,同樣也需要兩步,因此我先遍歷控制元件陣列,銷燬裡面的所有物件,然後再清空控制元件陣列,如此才真正實現了清空。 主要函式講完了,這個時候還有一個工作要做,別忘了,我們的容器類是動態申請的,這樣一來,跟其他的類一樣,我們的這個容器也需要銷燬,這樣,我們必須在窗體的解構函式中加入: delete HillList; 以上這些就是用TList實現控制元件陣列的方法。可以看到,這個方法很簡單,陣列以指標的形式儲存(TList裡也只能以指標的形式儲存)。新增和刪除物件都很方便且有效率。但是,TList卻不是一個型別安全的類,它的指標都是以void型別存放的。所以我們也可以看到在呼叫其中的物件的時候我們都使用了強制型別轉換。因為這個原因,就為我們的程式帶來了不安全的隱患。還有,當它儲存5000以上的物件時,效率會有很大的下降。TList類是VCL定義的類,因此它的可移植性就很差。 下面我們介紹一個型別安全的容器,這就是VCL提供的動態陣列(DynamicArray)。VCL的動態陣列是以類的方式實現的。它提供了DynamicArray類别範本,使用這個類别範本可以宣告實際動態陣列類。動態陣列通過設定Length值可以在執行時動態改變陣列元素個數。而且它還過載了C風格陣列的“[]”運算子,所以就可以像訪問普通陣列那樣訪問動態陣列。 下面我為你展示“今天能看見山”的DynamicArray實現版本。 使用動態陣列,我們就要先宣告這個容器。在標頭檔案的Private段中: DynamicArrayHillDA; 然後是那三個重要函式: void __fastcall AddHill(); void __fastcall DeleteHill(); void __fastcall ClearAll(); 這都跟我們的第一個版本沒有什麼區別。下面來看看程式的實現。下面的程式中有很多操作跟第一個版本中相同的就不再解釋了。 因為動態陣列是一個模板類,因此在使用前我們不需要先建立它的物件。 //--------------------------------------------------------------------------------------------------------------------- /**往控制元件陣列中增加成員**/ void __fastcall TfrmHill::AddHill() { TImage *temImage; HillDA.Length+=1; //動態陣列長度加一給新物件留出空間 HillDA[HillDA.High] = (new TImage(this)); //將新空間裝入一個指向新建立的Image //物件的指標 HillListBox->Items->Add("A Hill"); //向ListBox中寫入“A Hill” temImage = HillDA[HillDA.High]; //獲取剛構造的Image物件 temImage->Picture->LoadFromFile("hill.ico"); temImage->Top = random(HillPanel->Height); temImage->Left = random(HillPanel->Width); temImage->Parent = HillPanel; } //--------------------------------------------------------------------------- /**刪除控制元件陣列中選定的成員**/ void __fastcall TfrmHill::DeleteHill() { if(HillListBox->ItemIndex>=0) { delete HillDA[HillListBox->ItemIndex]; //銷燬選中的物件 for(int i = HillListBox->ItemIndex;i < HillDA.High;i++) //被銷燬物件後的記錄一 { //一前移覆蓋被刪除物件的 HillDA[i] = HillDA[i+1]; //指標 } HillDA.Length-=1; //控制元件陣列總長度減一 HillListBox->Items->Delete(HillListBox->ItemIndex); } } //-------------------------------------------------------------------------------------------------------------- /**清空控制元件陣列**/ void __fastcall TfrmHill::ClearAll() { for(int i = 0;iItems->Clear(); } //-------------------------------------------------------------------------------------------------------------- DynamicArray的方法講完了,細心的讀者看出什麼來了嗎?如果我再告訴你一個事實,你一定會看出來的。DynamicArray是以連續空間儲存的,它實現長度在執行時改變的機制是這樣的:當你在執行的時候改變了它的長度後,它將依據新長度從新分配一塊記憶體,然後把原來的資料全部Copy到新的地址中去。我的天~~~~,這樣你總知道了吧,我們這個DynamicArray的控制元件陣列開銷太大了,在我們新增和刪除的時候,它總是在不停的Copy來Copy去,在物件少的時候還不明顯,當物件變的很多的時候,這樣的系統開銷實在讓人難以接受。 那好,前兩種方法都有一定的缺陷,那我來講講第三種方法:STL方法! Standard Template Library(STL)是C++標準庫的一部分,它定義了一組容器和對容器進行操作的演算法。這些演算法都是具有很高效率的。Borland C++ Builder 6內建了STL的執行器“STLport 4.5”。通過使用這些泛型的容器和演算法,可以使我們的程式更健壯而且效率大大提高。 根據控制元件陣列的要求,我準備選用STL中的vector容器。Vector為內建陣列提供了一種替代表示。它同樣採用連續的記憶體空間存放資料。這樣的結構就造成了它隨機訪問的效率很高但是在它的任意位置,而不是vector末尾插入資料,則效率很低(這一點到跟我們的DynamicArray差不多,只是DynamicArray在末尾插入資料時效率一樣低),那vector會不會就跟DynamicArray一樣了?當然不會,vector在末尾插入資料的時候要比DynamicArray效率高的多,因為它使用了自增長機制。就是說,為了提高效率,實際上vector並不是隨每 元素的插入而增長自己,而是當vector需要增長自身時,它實際分配的空間比當前分配的空間要多一些,也就是說,它分配了一些額外的記憶體空間,或者說它預留了這些儲存區(分配的額外空間的具體數目由具體實現定義)。我們這個存放指標的vector,在第一個元素被插入的時候容量就會擴充套件到256。而當我們插入的元素到了256個的時候,它就會申請雙倍於現在容量的空間,把原有的值拷貝到新分配的記憶體空間中,釋放原來的記憶體空間。 無論如何,如果你準備往你的控制元件陣列中插入的物件在(至少在)一千萬個以內,vector的效率都是最高的(跟其他STL容器和其他傳統形容器比)。但是,如果你對控制元件陣列內元素的操作要求很苛刻,那你可以選用STL中的List容器。我對這個要求沒那麼苛刻,所以我的程式用vector來實現。 在BCB中要實現vector,別忘了包含它的標頭檔案: #include "HillSTL.h" 接著我們還要宣告我們的名字空間: using namespace std; 在標頭檔案的Private段中定義vector: vector HillVec; 三個函式: void __fastcall AddHill(); void __fastcall DeleteHill(); void __fastcall ClearAll(); //-------------------------------------------------------------------------------------------------------------- /**往控制元件陣列中增加成員**/ void __fastcall TfrmHill::AddHill() { TImage *temImage; HillVec.push_back(new TImage(this)); //在控制元件陣列中加入新物件的指標 HillListBox->Items->Add("A Hill"); temImage = *(HillVec.end() - 1); //取得剛加入物件的指標 temImage->Picture->LoadFromFile("hill.ico"); temImage->Top = random(HillPanel->Height); temImage->Left = random(HillPanel->Width); temImage->Parent = HillPanel; } //-------------------------------------------------------------------------------------------------------------- /**刪除控制元件陣列中選定的成員**/ void __fastcall TfrmHill::DeleteHill() { if(HillListBox->ItemIndex>=0) { TImage *temImage = *(HillVec.begin() + HillListBox->ItemIndex); delete temImage; HillVec.erase( HillVec.begin() + HillListBox->ItemIndex); //刪除控制元件陣列中指定的值 HillListBox->Items->Delete(HillListBox->ItemIndex); } } //-------------------------------------------------------------------------------------------------------------- /**清空控制元件陣列**/ void __fastcall TfrmHill::ClearAll() { vector :: iterator iter; //定義迭帶器 for(iter = HillVec.begin();iter !=HillVec.end();iter++) //遍歷銷燬物件 { delete *iter; } HillVec.erase(HillVec.begin(),HillVec.end()); //清空vector HillListBox->Items->Clear(); } 好了,這就是我們的第三種方法了,這種方法相對而言是最好的,其強壯性好,可移植性強。但其一點不足就是在容器內進行操作的時候效率不佳。而STL的List在隨機訪問的時候效率又不好了。可見,世上萬物,無一完美啊!你知道更好的方法嗎?來告訴我。 上面的三種方法中,你可以看到,除TList只能儲存指標外,DynamicArray和vector都是可以儲存任意型別的容器,那我們為什麼還非要儲存指標呢?是這樣的,如果我們儲存了類,則實際上是儲存了物件的副本,這時,當我們進行插入操作的時候,物件就會呼叫自己的拷貝建構函式以生成物件的副本。這樣的效率就又降低了,因此我們以指標形式儲存,這樣的設計方式同樣增加了程式處理的效率。

相關文章