CArray的類成員函數和使用方法詳解

CArray基礎

C++並不支持動態數組,MFC提供了一個CArray類來實現動態數組的功能。有效的使用CArray類,可以提高程序的效率。MFC提供了一套模板庫,來實現一些比較常見的數據結構如Array,List,Map。CArray即爲其中的一個,用來實現動態數組的功能。

一、CArray類的構造函數

CArray是從CObject派生,有兩個模板參數,第一個參數就是CArray類數組元素的變量類型,後一個是函數調用時的參數類型。有一個類 class Object,要定義一個Object的動態數組,那麼可以用以下兩種方法:

CArray<Object,Object> Var1;

CArray<Object,Object&> Var2;

Var2的效率要高。舉例說明如下:

 CArray <CPoint,CPoint&> m_Array;

該語句定義一個CArray數組對象,模板類CArray有兩個參數,第一個參數爲數組元素的類型,該例中是CPoint,即m_Array是 CPoint數組;第二個參數爲引用類型,一般有兩種選擇,一種選擇與第一個參數類型相同,它意味着數組對象作爲參數傳遞時,傳遞的是數組對象。第二種選擇是第一個參數類型的引用,它意味着數組對象作爲參數傳遞時,傳遞的是數組對象的指針。因此,尤其對於較複雜的數組結構類型,推薦使用引用傳遞,節約內存同時加快程序運行速度,正如本例使用的是CPoint&。

二、CArray類成員函數

1. 屬性
       GetSize()獲得此數組中的元素數
       GetUpperBound()返回最大的有效索引值
       SetSize()設置包含在此數組中的元素數

2. 操作
       FreeExtra()釋放大於當前上界的未使用的內存 
       RemoveAll()從此數組移去所有元素

3. 元素訪問 
       GetAt()返回在給定索引上的值
       SetAt()設定一個給定索引的值;數組不允許擴展
       ElementAt()返回一個對數組中元素指針的臨時參考
       GetData()允許對數組中的元素訪問。可以爲NULL

4. 擴展數組
      SetAtGrow()爲一個給定索引設置值;如果必要,擴展數組
      Add()在數組的末尾添加元素;如果必要,擴展數組 
      Append()在數組上附加另一個數組;如果必要,擴展數組
      Copy()把另一個數組拷貝到數組上;如果必要,擴展數組

5. 插入/移去
       InsertAt()在指定的索引上插入一個元素(或另一個數組中的所有元素)
       RemoveAt()在指定的索引上移去一個元素

6. 運算符
       [ ]在特定索引上設置或獲取元素

三、CArray類使用舉例
       1. 一條良好的使用原則:

在使用一個數組之前,使用SetSize建立它的大小和爲它分配內存。如果不使用SetSize,則爲數組添加元素就會引起頻繁地重新分配和拷貝。頻繁地重新分配和拷貝不但沒有效率,而且導致內存碎片。

2. Add()和SetSize()的使用舉例

SetSize()函數設定數組的大小,該函數有兩個參數,

(1) 第一個參數設定數組的大小;

(2) 第二個參數設定數組增長時內存分配的大小,缺省值是-1,使用缺省值可以保證內存分配得更合理。

本例中第二個參數是10,意即增加一個數組元素會分配10個元素大小的內存供數組使用。

您可以隨時使用SetSize函數設定數組的大小,如果第一個參數值小於數組已有成員數量,多於第一個參數值的成員將被截去並釋放相應內存。

再次強調:在使用CArray數組前,最好先使用SetSize確定其大小並申請存儲空間。如果不這樣做,向數組中增加元素時,需要不斷地移動和拷貝元素造成運行的低效率和內存碎塊。

3. SetAtGrow()和SetAt()函數

SetAtGrow有兩個參數,第一個參數決定數組元素的序號值,第二個參數是元素的值。該函數根據序號值設置相應數組元素的值,功能與SetAt相近,不同之處是使用該函數設置元素值時,如果序號值大於數組的上界,數組會自動增長。舉例如下:

編譯運行程序,細心的讀者您可能會看到,輸出結果如下:

第一行字符是“tiger”,第二行字符是“bear”,這是我們預料之中的,但第三行是空串,第四行是“dog”。空串是怎樣造成的呢?細分析下面三行代碼就可以知道:

m_string.SetAtGrow(0,sztiger);

m_string.SetAtGrow(2,szdog);

m_string.InsertAt(1,szbear);

第一行設定元素0爲“tiger”,這是沒有疑義的。

第二行設定元素2爲“dog”,但是在設定元素2的同時自動將元素1填充爲空串。

第三行插入“bear”爲元素1,同時原來的元素1和元素2後移爲元素2和元素3。

怎麼樣,這回明白了吧。

4. RemoveAt()和InsertAt()函數

InsertAt函數在指定序號處插入相應元素,該函數在執行過程中,插入點後面的元素會自動後移。

RemoveAt只有一個參數,即元素序號值。該函數根據元素序號值刪除相應元素值,後面的元素會自動前移。

最後再說明一點:RemoveAt,InsertAt函數操作時會使得數組元素移位,運行時間大於SetAt,RemoveAll,Add函數。

CArray使用詳解

MFC的數組類支持的數組類似於常規數組,可以存放任何數據類型。常規數組在使用前必須將其定義成能夠容納所有可能需要的元素,即先確定大小,而MFC數組類創建的對象可以根據需要動態地增大或減小,數組的起始下標是0,而上限可以是固定的,也可以隨着元素的增加而增加,數組在內存中的地址仍然是連續分配的。
  MFC定義了數組模板類CArray,並針對各種常用變量類型定義了CByteArray,CArray,CUIntArray,CDArray,CStringArray,CObArray,CPtrArray。詳見下表: 數組類
變量類型
變量數值範圍
   頭文件

CArray
通過模板類的參數類型設定各種類型 
      Afxtempl.h

CByteArray
8位無符號整數 BYTE類型
0—255
       Afxcoll.h

CArray
16位無符號整數 WORD類型
0—65535
       Afxcoll.h

CDArray
32位無符號整數 DWORD類型
0—4294967295
       Afxcoll.h

CUIntArray
32位無符號整數 UINT類型
0—4294967295
      Afxcoll.h

CStringArray
CString字符串 string字符串 
      Afxcoll.h

CObArray
CObject類及其派生類  
      Afxcoll.h

CPtrArray
void* 類型指針 
      Afxcoll.h


MFC數組類使用方法基本相同,下面分別以CArray和CUIntArray爲例演示說明數組類的使用方法。

使用 CArray 打開VC++ 6.0,創建基於對話框的工程Array。CArrayDlg類聲明文件(ArrayDlg.h)中添加語句:

#include <afxtempl.h>

 

請記住:使用CArray一定要包含頭文件afxtempl.h。

打開主對話框資源IDD_ARRAY_DIALOG,添加一個按鈕IDC_ARRAY_CPOINT,標題爲CArray_CPoint,雙擊該按鈕,在OnArrayCpoint()函數中添加如下代碼:

代碼簡要說明:

CArray <CPoint,CPoint&> m_Array;

  該語句定義一個CArray數組對象,模板類CArray有兩個參數,第一個參數爲數組元素的類型,該例中是CPoint,即m_Array是CPoint數組;第二個參數爲引用類型,一般有兩種選擇,一種選擇與第一個參數類型相同,它意味着數組對象作爲參數傳遞時,傳遞的是數組對象。第二種選擇是第一個參數類型的引用,它意味着數組對象作爲參數傳遞時,傳遞的是數組對象的指針。因此,尤其對於較複雜的數組結構類型,推薦使用引用傳遞,節約內存同時加快程序運行速度,正如本例使用的是CPoint&。

m_Array.SetSize(10,10);

  SetSize函數設定數組的大小,該函數有兩個參數,第一個參數設定數組的大小;第二個參數設定數組增長時內存分配的大小,缺省值是-1,使用缺省值可以保證內存分配得更加合理。本例中第二個參數是10,意即增加一個數組元素會分配10個元素大小的內存供數組使用。
  您可以隨時使用SetSize函數設定數組的大小,如果第一個參數值小於數組已有成員數量,多於第一個參數值的成員將被截去並釋放相應內存。
  在使用CArray數組前,最好先使用SetSize確定其大小並申請存儲空間。如果不這樣做,向數組中增加元素時,需要不斷地移動和拷貝元素造成運行的低效率和內存碎塊。

m_Array.Add(pt1);

Add函數添加數組元素。

int size=m_Array.GetSize();

GetSize返回數組元素的數目。

for(int i=0;i<size;i++){ pt=m_Array.GetAt(i);      dc.LineTo(pt);}

  爲了直觀顯示,該段代碼將各數組元素作成折線畫到屏幕上,其中GetAt(int index)通過index值得到相應的元素值。編譯並運行程序,觀察運行結果。

繼續演示如何使用CArray

  再次打開主對話框資源IDD_ARRAY_DIALOG,添加一個按鈕IDC_ARRAY_CSTRING,標題爲CArray_CString,雙擊該按鈕,在OnArrayCstring ()函數中添加如下代碼:

代碼簡要說明:

m_string.SetAtGrow(2,szdog);

  SetAtGrow有兩個參數,第一個參數決定數組元素的序號值,第二個參數是元素的值。該函數根據序號值設置相應數組元素的值,功能與SetAt相近,不同之處是使用該函數設置元素值時,如果序號值大於數組的上界,數組會自動增長。
  編譯運行程序,細心的讀者您可能會看到,第一行字符是“tiger”,第二行字符是“bear”,這是我們預料之中的,但第三行是空串,第四行是“dog”。空串是怎樣造成的呢?細分析下面三行代碼就可以知道:

m_string.SetAtGrow(0,sztiger);m_string.SetAtGrow(2,szdog);m_string.InsertAt(1,szbear);

第一行設定元素0爲“tiger”,這是沒有疑義的。
第二行設定元素2爲“dog”,但是在設定元素2的同時自動將元素1填充爲空串。
第三行插入“bear”爲元素1,同時原來的元素1和元素2後移爲元素2和元素3。

怎麼樣,這回明白了吧。

m_string.InsertAt(1,szbear);

  InsertAt函數在指定序號處插入相應元素,該函數在執行過程中,插入點後面的元素會自動後移。dc.TextOut(10,displayPos,m_string[x]);其中,m_string[x]是數組類對操作符[]的重載,數組類CArray允許使用[]操作符,類似於的常規數組。m_string[x]也可以用m_string.GetAt(x)替代。

m_string.RemoveAt(2);

RemoveAt只有一個參數,即元素序號值。該函數根據元素序號值刪除相應元素值,後面的元素會自動前移。

m_string.RemoveAll();

RemoveAll刪除所有元素值

  最後再說明一點:RemoveAt,InsertAt函數操作時會使得數組元素移位,運行時間大於SetAt,RemoveAll,Add函數。

 

從std::vector< std::string > 和 CArray< std::string >的性能差別

 

std::vector在移動元素時是挨個調用元素的拷貝函數。
CArray在移動元素時則是直接使用memcopy 和 memmove。

操作方式的不同導致了它們極大的性能差異。
我使用隨機方式在std::vector<std::string>和CArray<std::string>
裏插入字符串,經過測試,CArray平均比std::vector快十幾倍。

經分析發現,所有的c++類[類型]都可以分爲簡單和複雜兩種類型。
簡單類型定義:類的成員不可能引用該類對象自己的內存。否則爲複雜類。
任何從複雜類派生或包含複雜類成員的類都是複雜類。
例如,所有C++基本數據類型、std::string、std::vector、MFC的CString
及CArray等等都是簡單類型。

下面的類則是複雜類:
class CA {
char _buf[10];
char* _p;
public:
CA() : _p(_buf) {}
};

特別的,對於簡單類型,以下操作是安全的:

struct CSimp
{
char* _p;

CSimp()
{
_p = new char[100];
strcpy(_p, "constructed");
}
~CSimp()
{
delete[] _p;
}
};

void test()
{
CSimp* p = new CSimp();
CSimp* p2 = (CSimp*)new char[sizeof(CSimp)];
memcpy(p2, p, sizeof(CSimp));

// 以後可以把p2當作CSimp*安全的使用,但p2必須析構
// 但p以後就不能再安全的使用了,也不能析構,但只需釋放內存即可
strcpy(p2->_p, "use it");
// ...

delete p2;
delete (char*)p;
}

這就是CArray中在插入、刪除[簡單類型的]元素時可以不調用
元素的拷貝函數的設計原理。

std::vector是按照通用類考慮設計的,所有類型的都可以安全的使用它。
CArray則只可以使用簡單類型,使用複雜類型一定出錯。

但在使用簡單類型是顯然CArray的方式效率要高得多,因爲在插入、刪除數
組成員時少去了大量的元素拷貝函數的調用。

所以當元素是複雜類型時,必須使用std::vector,當元素是簡單類型是,則
應該使用CArray,如果不是MFC環境,可以弄個和CArray同樣設計原理的數組。

CArray如何使用

首先大家應該知道MFC爲我們提供了一個極其有用的模板類庫,但是很多初學者,不但對這個庫不太瞭解,甚至就連模板的含義,都成問題,所以,在此,我先不談具體的模板定義,只告訴大家如何簡單的使用一個模板類。

    先以CArray爲例,下面是我編寫的一小段測試代碼:

    struct Node

    {

        int index;

        char name[16];

    };

    以下是類模板實現爲一個類的標準語法。其中,Node是出參,Node&是入參,其實也就是說:當你使用Add函數的時候,所傳入的參數是後者,也就是Add(Node& node)這樣的形式,而當你GetAt()取出一個元素的時候,返回的是一個Node結構體。    

這段代碼,所描述的是最簡單的CArray的用法,如果插入CArray中的元素不是一個C格式的結構體,或者內置類型的話,你必須要給這個元素(可能是一個類)追加必要的構造函數和析構函數。下面,我們來看另外的一個例子:

    假如,我們建立這樣的一個動態數組,它會穩定良好的運行嗎?CStrx是一個字符串類,它將會完成一些類似CString的功能。

    class CStrx

    {

    public:

        CStrx() :m_pdata(NULL), m_size(0) //初始化成員

    {

    }

        ~CStrx(){}

    private:

        char* m_pdata;

        size_t m_size;

    };

    typedef CArray<CStrx, CStrx&>                    CArrStrx;

    很遺憾,它無法按照你的意圖去安全的工作,原因都在CArray的內部,暫時,我先不鋪開,但我要說明的就是,在CArray的內部,需要調用元素的賦值構造函數,那麼爲什麼我們剛剛在上個例子中,卻沒有爲Node建立一個賦值構造函數呢?原因很簡單,那就是,如果,你不提供賦值構造函數的話,C++編譯器將會默認按位拷貝,而我們給出的是一個規則的結構體,並且不存在指針(像本例中的m_pdata,它可能指向一塊動態分配的內存),所以不會出現問題,但一旦需要向本例這樣需要深拷貝的話,那麼CArray將會無法工作。所以,我們必須要爲這個類,提供一個賦值構造函數。這個函數可以像這樣:

    CStrx& operator=CStrx(const CStrx& str)

    {

        ……

        return *this;

    }

    然而,我們爲什麼要用指針作爲元素傳入CArray呢?其實,主要是爲了效率的考慮,如果你傳入的是一個對象,或者對象的引用,那麼CArray將不得不動用你的賦值構造函數,可能會進行大規模的數據賦值,然而對於指針來說,它就相當於一個整數,效率將會顯著提高,但是入CArray析構之前,你必須要對這些元素進行釋放。

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