CArray
需要包含的頭檔案 <afxtempl.h>
提示:
在使用一個數組之前,使用SetSize建立它的大小和為它分配記憶體。如果不使用SetSize,則為數組添加元素就會引起頻繁地重新分配和拷貝。頻繁地重新分配和拷貝不但沒有效率,而且導致記憶體碎片。
如果需要一堆數組中的個別數據,必須設定CDumpContext對象的深度為1或更大。
此類的某成員函式調用全局幫助函式,它必須為CArray的大多數使用而定製。請參閱宏和全局量部分中的“類收集幫助器”。
當從一個CArray對象中移去元素時,幫助函式DestructElements被調用。
當添加元素時,幫助函式ConstructElements被調用。
數組類的派生與列表的派生相似。
MFC提供了一套模板庫,來實現一些比較常見的數據結構如Array,List,Map。CArray即為其中的一個,用來實現動態數組的功能。CArray是從CObject派生,有兩個模板參數,第一個參數就是CArray類數組元素的變數類型,後一個是函式調用時的參數類型。有一個類 class Object,要定義一個Object的動態數組,那么可以用以下兩種方法:
CArray<Object,Object> Var1;
CArray<Object,Object&> Var2;
Var2的效率要高。
int m_nSize; // 用戶當前定義的數組的大小
int m_nMaxSize; // 當前實際分配的數組的大小
int m_nGrowBy; // 分配記憶體時增長的元素個數
構造函式,對成員變數進行了初始化。
CArray<TYPE, ARG_TYPE>::CArray()
{
m_pData = NULL;
m_nSize = m_nMaxSize = m_nGrowBy = 0;
}
SetSize成員函式是用來為數組分配空間的。SetSize的函式定義如下:
void SetSize( int nNewSize, int nGrowBy = -1 );
nNewSize 指定數組的大小
nGrowBy 如果需要增加數組大小時增加的元素的個數。
對SetSize的代碼,進行分析。
void CArray<TYPE, ARG_TYPE>::SetSize(int nNewSize, int nGrowBy)
{
if (nNewSize == 0)
{
// 第一種情況
// 當nNewSize為0時,需要將數組置為空,
// 如果數組本身即為空,則不需做任何處理
// 如果數組本身已含有數據,則需要清除數組元素
if (m_pData != NULL)
{
//DestructElements 函式實現了對數組元素析構函式的調用
//不能使用delete m_pData 因為我們必須要調用數組元素的析構函式
DestructElements<TYPE>(m_pData, m_nSize);
//現在才能釋放記憶體
delete[] (BYTE*)m_pData;
m_pData = NULL;
}
m_nSize = m_nMaxSize = 0;
}
else if (m_pData == NULL)
{
// 第二種情況
// 當m_pData==NULL時還沒有為數組分配記憶體
//首先我們要為數組分配記憶體,sizeof(TYPE)可以得到數組元素所需的位元組數
//使用new 數組分配了記憶體。注意,沒有調用構造函式
m_pData = (TYPE*) new BYTE[nNewSize * sizeof(TYPE)];
//下面的函式調用數組元素的構造函式
ConstructElements<TYPE>(m_pData, nNewSize);
//記錄下當前數組元素的個數
m_nSize = m_nMaxSize = nNewSize;
}
else if (nNewSize <= m_nMaxSize)
{
// 第三種情況
// 這種情況需要分配的元素個數比已經實際已經分配的元素個數要少
if (nNewSize > m_nSize)
{
// 需要增加元素的情況
// 與第二種情況的處理過程,既然元素空間已經分配,
// 只要調用新增元素的構造函式就Ok
ConstructElements<TYPE>(&m_pData[m_nSize], nNewSize-m_nSize);
}
else if (m_nSize > nNewSize)
{
// 現在是元素減少的情況,我們是否要重新分配記憶體呢?
// No,這種做法不好,後面來討論。
// 下面代碼釋放多餘的元素,不是釋放記憶體,只是調用析構函式
DestructElements<TYPE>(&m_pData[nNewSize], m_nSize-nNewSize);
}
m_nSize = nNewSize;
}
else
{
//這是最糟糕的情況,因為需要的元素大於m_nMaxSize,
// 意味著需要重新分配記憶體才能解決問題
// 計算需要分配的數組元素的個數
int nNewMax;
if (nNewSize < m_nMaxSize + nGrowBy)
nNewMax = m_nMaxSize + nGrowBy;
else
nNewMax = nNewSize;
// 重新分配一塊記憶體
TYPE* pNewData = (TYPE*) new BYTE[nNewMax * sizeof(TYPE)];
//實現將已有的數據複製到新的的記憶體空間
memcpy(pNewData, m_pData, m_nSize * sizeof(TYPE));
// 對新增的元素調用構造函式
ConstructElements<TYPE>(&pNewData[m_nSize], nNewSize-m_nSize);
//釋放記憶體
delete[] (BYTE*)m_pData;
//將數據保存
m_pData = pNewData;
m_nSize = nNewSize;
m_nMaxSize = nNewMax;
}
}
下面是ConstructElements函式的實現代碼template<class TYPE>
AFX_INLINE void AFXAPI ConstructElements(TYPE* pElements, int nCount)
{
// first do bit-wise zero initialization
memset((void*)pElements, 0, nCount * sizeof(TYPE));
for (; nCount--; pElements++)
::new((void*)pElements) TYPE;
}
ConstructElements是一個模板函式。對構造函式的調用是通過標為黑體的代碼實現的。可能很多人不熟悉new 的這種用法,它可以實現指定的記憶體空間中構造類的實例,不會再分配新的記憶體空間。類的實例產生在已經分配的記憶體中,並且new操作會調用對象的構造函式。因為vc中沒有辦法直接調用構造函式,而通過這種方法,巧妙的實現對構造函式的調用。
再來看DestructElements 函式的代碼template<class TYPE>
AFX_INLINE void AFXAPI DestructElements(TYPE* pElements, int nCount)
{
for (; nCount--; pElements++)
pElements->~TYPE();
}
DestructElements函式同樣是一個模板函式,實現很簡單,直接調用類的析構函式即可。
如果定義一個CArray對象 CArray<Object,Object&> myObject ,對myObject就可象數組一樣,通過下標來訪問指定的數組元素。
CArray[]有兩種實現,區別在於返回值不同。
template<class TYPE, class ARG_TYPE>
AFX_INLINE TYPE CArray<TYPE, ARG_TYPE>::operator[](int nIndex) const
{ return GetAt(nIndex); }
template<class TYPE, class ARG_TYPE>
AFX_INLINE TYPE& CArray<TYPE, ARG_TYPE>::operator[](int nIndex)
{ return ElementAt(nIndex); }
前一種情況是返回的對象的實例,後一種情況是返回對象的引用。分別調用不同的成員函式來實現。
TYPE GetAt(int nIndex) const
{ ASSERT(nIndex >= 0 && nIndex < m_nSize);
return m_pData[nIndex]; }
TYPE& ElementAt(int nIndex)
{ ASSERT(nIndex >= 0 && nIndex < m_nSize);
return m_pData[nIndex]; }
除了返回值不同,其它都一樣.
CArray<int,int&> arrInt;
arrInt.SetSize(10);
int n = arrInt.GetAt(0);
int& l = arrInt.ElementAt(0);
cout << arrInt[0] <<endl;
n = 10;
cout << arrInt[0] <<endl;
l = 20;
count << arrInt[0] << endl;
結果會發現,n的變化不會影響到數組,而l的變化會改變數組元素的值。實際即是對C++中引用運算符的運用。
CArray下標訪問是非安全的,它並沒有超標預警功能。雖然使用ASSERT提示,但下標超範圍時沒有進行處理,會引起非法記憶體訪問的錯誤。
Add函式的作用是向數組添加一個元素。下面是它的定義: int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement).Add函式使用的參數是模板參數的二個參數,也就是說,這個參數的類型是我們來決定的,可以使用Object或Object&的方式。熟悉C++的朋友都知道,傳引用的效率要高一些。如果是傳值的話,會在堆疊中再產生一個新的對象,需要花費更多的時間。
template<class TYPE, class ARG_TYPE>
AFX_INLINE int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
{
int nIndex = m_nSize;
SetAtGrow(nIndex, newElement);
return nIndex;
}
它實際是通過SetAtGrow函式來完成這個功能的,它的作用是設定指定元素的值。
template<class TYPE, class ARG_TYPE>
void CArray<TYPE, ARG_TYPE>::SetAtGrow(int nIndex, ARG_TYPE newElement)
{
if (nIndex >= m_nSize)
SetSize(nIndex+1, -1);
m_pData[nIndex] = newElement;
}
SetAtGrow的實現也很簡單,如果指定的元素已經存在,就把改變指定元素的值。如果指定的元素不存在,也就是 nIndex>=m_nSize的情況,就調用SetSize來調整數組的大小
首先定義
CArray<char *> arryPChar;
這裡以定義char*的為例子。
接下來我們來熟悉CArray這個類里的函式。
INT_PTR GetCount() const;
獲得當前這個數組有多少個元素。
void SetSize(INT_PTR nNewSize, INT_PTR nGrowBy = -1);
設定數組的大小。
TYPE& GetAt(INT_PTR nIndex);
void SetAt(INT_PTR nIndex, ARG_TYPE newElement);
獲得/設定序列的元素
INT_PTR Add(ARG_TYPE newElement);
在數組的末尾添加一個元素,數組的長度加1。如果之前使用SetSize是nGrowBy大於1,則記憶體按照nGrowBy增加。函式返回newElement的數組元素索引
void RemoveAt(INT_PTR nIndex, INT_PTR nCount = 1);
從指定的nIndex位置開始,刪除nCount個數組元素,所有元素自動下移,並且減少數組的上限,但是不釋放記憶體。這裡我們自己手動的申請的就必須自己釋放。new對應delete相信大家都知道的。
void RemoveAll();
從數組中移除素有的元素,如果數組為空,該行數也起作用。
INT_PTR Append(const CArray& src);
將同個類型的一個數組A附加到本數組的尾部,返回A第一數組元素在本數組的索引
void InsertAt(INT_PTR nIndex, ARG_TYPE newElement, INT_PTR nCount = 1);
void InsertAt(INT_PTR nStartIndex, CArray* pNewArray);
在指定的nIndex或者nStartIndex位置插入nCount個newElement數組元素或者pNewArray數組
下面是我套用的實例:
view plaincopy to clipboardprint?
CArray <char*>arrPChar;
//初始化元素
arrPChar.SetSize(10);
for (int i=0;i<10;i++)
{
char *aChar=new char[10];
strcpy_s(aChar,10,"hello arr");
arrPChar.SetAt(i,aChar);
}
//在數組的末尾插入一個元素
char *bChar = new char[10];
strcpy_s(bChar,10,"asdfefdsd");
arrPChar.Add(bChar);
//在索引2的位置插入一個元素,即在第三位插入一個元素
char *cChar=new char[5];
strcpy_s(cChar,5,"aidy");
arrPChar.InsertAt(2,cChar);
for (int j=0;j<arrPChar.GetCount();j++)
{
TRACE("%d,%s\n",j,arrPChar.GetAt(j));
}
//刪除數組里的所有元素,要釋放記憶體,如果單單Remove的話則記憶體不會被釋放
//這裡因為使用RemoveAll的話記憶體無法被釋放,所以沒有給實例。
int count = arrPChar.GetCount();
for (int k=0; k<count; k++)
{
char *dChar=arrPChar.GetAt(0);
arrPChar.RemoveAt(0);
delete dChar;
}