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

 

C++BUILDER中幾種容器的使用<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

                             -------BCB中控件數組的實現

C++BUILDERBorland公司基於C++的快速開發工具,它簡單的使用方法和強大的功能一直深受很多編程人員的好評。C++BUILDER(以下簡稱BCB)的組件庫跟DELPHI一樣,都是VCL。跟微軟的MFC不同,VCL完全是用Object Pascal語言編的。也因此,使BCB同時獲得了PascalC++的強大功能。

介紹在BCB中實現控件數組的文章不少,但是實現方法上,大多用VCL自帶的Tlist類實現。實際上,除了這種方法,在BCB中實現控件數組還是有不少其他方法的。這裏,我就談談這幾種實現控件數組的方法,實際上,也是對BCB中常用的容器做一個小結。

BCB完全支持VCL和標準C++,這就導致了存在有VCL版本和標準C++兩個版本的容器。在這裏,我會給出三種實現方法,一種是VCLTList方法,一種是VCLDynamicArray(動態數組)方法,還有一種就是標準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;i     //同樣遍歷控件數組銷燬所有的類

  {

    delete HillDA[i];

  }

  HillDA.Length = 0;                //將控件數組的Length設爲0以釋放該控件數組

  HillListBox->Items->Clear();

}

//--------------------------------------------------------------------------------------------------------------

DynamicArray的方法講完了,細心的讀者看出什麼來了嗎?如果我再告訴你一個事實,你一定會看出來的。DynamicArray是以連續空間存儲的,它實現長度在運行時改變的機制是這樣的:當你在運行的時候改變了它的長度後,它將依據新長度從新分配一塊內存,然後把原來的數據全部Copy到新的地址中去。我的天~~~~,這樣你總知道了吧,我們這個DynamicArray的控件數組開銷太大了,在我們新增刪除的時候,它總是在不停的CopyCopy去,在對象少的時候還不明顯,當對象變的很多的時候,這樣的系統開銷實在讓人難以接受。

那好,前兩種方法都有一定的缺陷,那我來講講第三種方法:STL方法!

Standard Template LibrarySTL)是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();

}

好了,這就是我們的第三種方法了,這種方法相對而言是最好的,其強壯性好,可移植性強。但其一點不足就是在容器內進行操作的時候效率不佳。而STLList在隨機訪問的時候效率又不好了。可見,世上萬物,無一完美啊!你知道更好的方法嗎?來告訴我。

上面的三種方法中,你可以看到,除TList只能存儲指針外,DynamicArrayvector都是可以存儲任意類型的容器,那我們爲什麼還非要存儲指針呢?是這樣的,如果我們存儲了類,則實際上是存儲了對象的副本,這時,當我們進行插入操作的時候,對象就會調用自己的拷貝構造函數以生成對象的副本。這樣的效率就又降低了,因此我們以指針形式存儲,這樣的設計方式同樣增加了程序處理的效率。

以上我結合控件數組的實現介紹了BCB中常用的三種容器。你可以在實際應用中考慮實際情況選擇適合自己的容器使用。

本文中所有程序均在Windows2000+BCB5下調試通過。

  如對本文所談技術有任何問題,歡迎跟我討論,地址:[email protected]

北京聯合大學信息學院  張琦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章