摘 要:動態控件數組是應用程序設計中經常用到的重要手段之一。合理地使用動態控件數組不但能簡化程序、方便維護,還可以節約內存空間,對動態控件數組的研究有助于增加應用程序靈活性,能有效提高編程效率。鑒于此,提出了在C++Builder環境下基于容器對象的動態控件數組實現方案,分別給出利用三種容器對象TList,DynamicArray和Vector實現動態控件數組的思想,對三者的性能進行比較分析,并總結了實現動態控件數組的原理、方法和技巧。
關鍵詞:動態控件數組; 容器; TList; DynamicArray; Vector
中圖分類號:TN911.7-34; TP311.11
文獻標識碼:A
文章編號:1004-373X(2012)01-0146-04
Research on dynamic control arrays based on container object
LIU Gen, YANG Yu-qin, JIANG Tian-fa
(College of Computer Science, South-central University for Nationalities, Wuhan 430073, China)
Abstract:
Dynamic control arrays are often used in application programming. Reasonably using dynamic control arrays can simplify application procedures and their maintenance, save memory space, increase the flexibility of application procedure, and effectively improve the efficiency of application programming. The schemes of dynamic control arrays based on container object under C++Builder are presented. Three kinds of container objects TList, DynamicArray and Vector are used to realize dynamic control arrays, whose performance are analyzed and compared. The principle, methods and technique of dynamic control arrays are summarized.
Keywords: dynamic component arrays; container; TList; DynamicArray; Vector
收稿日期:2011-08-11
基金項目:國家自然科學基金項目(40571128);湖北省教育廳科研項目(B20110804)
0 引 言
動態數組[1]可以在運行期間改變數組大小,具有很強的靈活性,為開發者帶來極大便利,廣泛應用于各個領域[2-3]。動態控件數組就是一種特殊的動態數組,它的元素類型是控件類型。控件數組應該包含以下三個要素:
(1) 允許多個控件共享同一個事件句柄。即動態生成的多個同類型控件可以使用相同的事件名。這一點是BCB已經具備的功能。
(2) 可以在運行期間添加或者刪除控件。BCB已經提供了這種機制。
(3) 可以方便地將多個控件組合為控件數組進行管理和使用。要組合控件最好的方法就是使用容器。BCB中能夠實現控件數組的容器可以分為三種:第一種是VCL(Visual Componet Library,可視化組件庫)中的TList類;第二種是VCL中的DynamicArray類;第三種是STL[4](Standard Template Library,標準模板庫)中的Vector容器類。
文獻[5]提出一種基于TList類的控件數組實現方法,在其基礎上本文提出基于容器類的動態控件數組實現方案,并詳細分析了其性能效率。
1 基于TList類的控件數組
1.1 VCL中的TList類
TList是VCL提供的一個用于維護對象列表的類,它是以鏈式存儲指針的方式實現的。它存儲了用于維護各種類指針的索引,這些索引存有指向各種對象的指針。為方便在對象列表中添加、刪除、查找、訪問、排序或者重組對象,TList提供了方便全面的鏈表功能,包括Capacity,Count,Items等主要屬性和Add,Insert,Delete,Clear,First,Last等主要方法,各屬性和方法詳細信息如表1所示。
1.2 基于TList類的控件數組的實現思想
文獻[5]提出一種基于TList類的控件數組實現方案,下面簡要闡述其基本思想。
定義TList類型指針對象,建立控件數組CList,并調用構造函數對其進行初始化:
TList *CList = new TList() ;
動態生成控件對象Button,添加到CList中:
TButton *Button=new TButton(this) ;
CList->Add(Button) ;
表1 TList類的主要屬性和方法
名稱相關信息
Capacity標識TList對象維護的指針數組大小
Count標識Items數組中項的數量
Items可以獲得數組中指定對象的指針
Add在列表的末尾插入新項
Clear刪除列表中所有項
Delete刪除列表中指定位置的項
First返回Items數組中第一個指針
Insert向Items數組中指定位置插入新的項
Last返回Items數組中最后一個指針
為Button聲明并自定義事件MBClick,讓所有動態生成的控件共享事件句柄:
void __fastcall TForm1::MBClick(
TObject *Sender) ;
Button->OnClick=MBClick ;
通過TList對象的Items屬性訪問CList中的對象,甚至可以在自定義事件中訪問TList對象中動態生成的控件:
void __fastcall TForm1::MBClick(TObject
*Sender)
{
ShowMessage(((TButton*)(CList->Items[CList->Count-1]))->Caption;
}
刪除CList中指定成員對象(第i個)時,先將CList中該成員對象的類指針索引裝入臨時指針,然后刪除臨時指針所指對象,同時刪除控件數組中的指針記錄:
TButton *temp = (TButton *)CList->Items[i] ;
delete temp ;
CList->Delete(i) ;
清空整個CList時,先刪除其中所有成員對象,再用Clear方法清空。程序結束時CList需要銷毀,可在窗體的析構函數中加入以下代碼:
delete CList;
2 基于DynamicArray模板的控件數組
2.1 VCL中的DynamicArray模板類
DynamicArray是VCL提供的一個用于實現變長動態數組的模板類[6],使用二進制數據塊兼容Object Pascal動態數組實現。DynamicArray也可以看作是一個可以存放各種控件對象的容器,只需將動態數組聲明中的數據類型設置為相應控件類型即可。為方便管理動態數組,DynamicArray模板提供了Length,High,Low等屬性和Copy,CopyRange等方法,并重載了賦值運算符“=”、比較運算符“==”和訪問數組元素的“[]”運算符,如表2所示。
表2 DynamicArray類的主要屬性、方法和操作符
名稱相關信息
屬性
Length動態數組長度
High動態數組的下標上界Length-1
Low動態數組的下標下界零
方法
Copy將數組內容復制到指定數組中
CopyRange復制一段連續元素到指定數組中
操作符
“=”引用型賦值操作符
“==”判斷動態數組指針是否相同
“[]”用于訪問動態數組中的元素
2.2 基于DynamicArray模板的控件數組實現思想
利用DynamicArray模板實現動態控件數組時,需要將動態數組的類型設置為某一控件類型,通過在程序中動態修改動態數組的Length屬性來實現數組大小的動態變化。下面以動態按鈕數組(BArray)為例,詳細說明動態控件數組實現思想。
聲明動態按鈕數組:
DynamicArray
動態地向BArray中添加成員對象時,先將BArray的Length屬性增加1,以便為待添加的新對象預留空間:
BArray.Length = BArray.Length + 1 ;
創建待添加的控件對象并裝入新開辟的空間:
BArray[BArray.High] = (new TButton(this)) ;
獲取新添加的控件對象:
TButton* Button = BArray[BArray.High] ;
刪除指定成員(比如第i個)對象時,先刪除動態數組中第i個成員對象:
delete BArray[i] ;
然后使用循環語句將被刪除成員對象后面的元素全部前移:
for(int k = i ; k < BArray.High ; k++)
{
BArray[i] = BArray[i+1] ;
}
最后將BArray的Length屬性減少1:
BArray.Length = BArray.Length - 1 ;
如果要清空動態數組BArray中的所有成員,先要使用循環語句逐一刪除BArray中的所有元素:
for(int k = 0 ; k { delete BArray[k] ; } 然后將動態數組BArray的Length屬性設置為0,但無需使用delete語句銷毀BArray: BArray.Length = 0 ; 3 基于Vector容器類的控件數組 3.1 STL中的Vector容器類 Vector是STL容器類中最簡單的容器類,通常也是效率最高的容器類。Vector[7]是一個隨機存取的Sequence容器,采用連續的內存空間存放數據。它支持常數時間的尾部插入和刪除、線性時間的其他位置的插入和刪除。Vector模板類class vector接口成員(public)如表3所示。 表3 Vector接口類中包含的主要內容 類型定義value_type,allocator_type,reference, iterator,size_type,difference_type; 構造函數vector (size_type, const T, const Allocator = Allocator()); 拷貝構造函數vector (const vector 析構函數~vector (); 迭代器begin ();end (); 關于容量大小的函數size();max_size();resize(size_type, T);capacity () ;empty () ;reserve (size_type); 用于訪問的操作和函數operator[] (size_type);at (size_type);front();back(); 更改容器內容的函數push_back (const T);pop_back ();void insert (iterator, size_type, const T);erase (iterator);swap (vector 3.2 基于Vector容器類的控件數組的實現思想 基于Vector容器實現動態控件數組的思想[8],將Vector中元素類型設置為相應的控件類型,然后利用Vector提供的push_back或insert方法,將動態生成的控件對象裝入容器中,利用Vector提供的訪問容器中元素的方法訪問成員對象,或者利用Vector提供的pop_back或erase方法刪除容器中的元素。下面還是以動態按鈕數組(BVec)為例,詳細說明基于Vector的動態控件數組實現過程。 定義相應類型(比如TButton)的Vector容器: vector 創建Button控件對象,然后添加到容器中: BVec.push_back(new TButton(this)) ; 獲取剛加入的控件對象指針,方便后續訪問: TButton* Button = *(BVec.end() - 1) ; 在刪除容器中指定的元素(比如第i個元素)時,先要刪除容器中該成員對象的類指針所指的對象,然后清除容器中該成員對象的值: TButton* tempButton= *(BVec.begin() + i); delete tempButton ; BVec.erase(BVec.begin() + i) ; 清空容器時先刪除容器中所有成員的類指針所指的對象,這一點可以通過定義一個迭代器并遍歷容器所有元素來實現。然后再清除容器中所有元素的值: vector for(it = BVec.begin() ; it != BVec.end() ; it++) delete *it ; BVec.erase(BVec.begin(),BVec.end()) ; 4 基于容器對象的動態控件數組性能分析 4.1 基于TList類的控件數組性能分析 基于TList類的控件數組應用廣泛[9-10],實現方法簡單,添加或刪除對象方便而高效。但是這種方法存在以下4個方面的缺陷。 安全性問題 TList類本身就是一個缺乏類型安全支持的類。從其Add方法原型int_fastcall Add(void * Item)可以看出,TList對象存儲并維護的是void*空指針。但是BCB編譯器會將Add函數接收到的任何類型的指針轉換為void*類型,而不做任何類型檢查。同時,在引用控件數組中的一個對象時,需要將一個void*類型的指針強制轉換為相應控件類型的指針。如果控件數組包含多種類型的成員控件,這時就無法確定空指針該強制轉換為哪種類型的指針。一旦類型轉換錯誤,那么在訪問成員控件時就會產生嚴重的問題。 內存空間釋放問題 一方面,TList不能自動刪除列表中的指針;另一方面,由于列表中的指針是空指針,刪除空指針的時候不會調用析構器中的析構函數,那么就無法釋放內存空間,從而造成內存泄露。 效率問題 實驗表明,當TList存儲5 000個以上對象時,效率就會大幅下降。 移植性問題 由于BCB的VCL完全是用Object Pascal語言編的,使得BCB同時獲得了Pascal和C++的強大功能,同時可移植性就很差。 4.2 基于DynamicArray類的控件數組性能分析 用DynamicArray實現控件數組,原理簡單、代碼簡潔,不存在類型安全和內存釋放問題。但是,DynamicArray使用二進制數據塊兼容Object Pascal動態數組實現,這會帶來系統瓶頸,嚴重影響運行效率。DynamicArray的空間擴充機制也不盡完美。當數組長度超過固定大小時,就會頻繁地重新申請內存空間,并將原來內存空間中的二進制數據塊拷貝到新申請的內存空間中,然后將原空間釋放掉,這就會大大降低運行效率。特別是如果二進制數據塊比較大的時候,系統瓶頸尤其突出。另外,由于DynamicArray是VCL中的模板類,故移植性較差。 4.3 基于Vector容器類的控件數組性能分析 Vector采用線性連續的內存空間存放數據,因而隨機訪問數組元素和Vector尾部插入元素效率很高,但是在任意位置插入元素的效率比較低。為了提高效率,Vector使用了自增長機制,實際上就是運用動態彈性內存空間[8],隨著新元素的加入,自行擴充內存空間。為了合理控制空間大小、提高重新配置空間時的效率,Vector 實際配置的空間比需求空間大一些,以備將來可能的擴充。向Vector中插入元素時,首先考慮備用空間是否足夠,如果夠就插入到備用空間,否則,重新配置雙倍于現在容量的內存空間,然后將原來的數據拷貝到新空間中,最后釋放原內存空間。在控件數組中元素個數不超過1 000萬個時,Vector的空間擴充方式效率都是很高的。Vector的空間擴充過程如圖1所示。 圖1 Vector內存空間擴展示意圖 另外,當容器內元素操作比較復雜時,Vector效率不佳。 5 結 語 BCB是一個優秀的應用程序開發平臺,其提供的VCL庫更是為開發者帶來了極大便利。VCL提供的TList和DynamicArray都可以很容易地實現動態控件數組。但是,TList缺乏類型安全支持,而且Dynamic-Array也存在系統瓶頸問題,而且移植性都比較差,因此它們適合于小規模的局部應用的動態控件數組中。而標準模板庫STL提供的容器類Vector具有完備的類型安全機制,移植性比較好,執行效率也相對很高,但是在支持數組元素復雜操作方面欠佳,故這種方法適合于較大規模的通用動態控件數組中??傊?,沒有哪種方法一定是最好的,具體采用什么方法要根據問題規模、應用范圍等因素來決定。 參 考 文 獻 [1]陳鳳祥,李汪根.C++動態數組的實現與重用[J].計算機技術與發展,2010,20(2):79-82. [2]孫桂娟,張慶明,鄭全平,等.基于可變數組管理方法的震塌破壞數值仿真[J].北京理工大學學報,2009,29(6):492-496. [3]喬平安.動態統計圖控件的設計與實現[J].現代電子技術,2006,29(6):118-120. [4]葛建芳.C++標準模板庫與代碼重用[J].南通大學學報:自然科學版,2006,5(2):71-74. [5]許天然.在C++Builder中實現控件數組[J].瓊州大學學報,2006,13(5):27-29. [6]周熙,汪紅.C++Builder環境下MVC框架結構的應用[J].中南民族大學學報:自然科學版,2007,26(2):87-89. [7]宮護震,史云鵬,孫吉赟.一種基于STL(標準模板庫)的三維數據可視化容器設計[J].現代電子技術,2007,30(22):80-81. [8]帖軍,王小榮,金佳.移動實時環境下的數據一致性研究[J].中南民族大學學報:自然科學版,2011,30(2):92-95.. [9]景春國,舒冬梅.TList對象在監控軟件數據點的內存管理和處理方面的應用[J].現代電子技術,2003,26(2):7-12. [10]李繼良.用TList類構建粗糙集分類系統[J].計算機與現代化,2002(4):17-19. 作者簡介: 劉 艮 男,1987年出生,山西大同人,碩士研究生。主要研究方向為信息安全與分布式系統開發。 楊玉琴 女,1987年出生,湖北武漢人,碩士研究生。主要研究方向為分布式系統開發與數字水印。 蔣天發 男,1954年出生,湖北荊門人,教授,研究生導師。主要研究方向為網絡安全和空間數據組織與管理。