關於CMap的小知識

最近在mfc中用到字典,自己不會在網上查了資料。簡單總結一下:
一,CMap是什麼?
      映射(Map),又稱爲字典(Dictionary),是由關鍵字(Key)及其對應的元素值(Value)所組成的元素單

元(Element)的表單式集合。CMap是一個mfc的模板類,可以建立一個從任意類型的變量到另外一個任意類型的

變量的映射(map),用的是哈希表作存儲,因此速度較快。對於要求查找速度快一般用數組,對於增加/刪除操作

方便的都用鏈表,但要是兩者綜合一下,最好還是用合希表。

 

二,要注意的幾個地方:
   1.如何聲明CMap
          許多人對Cmap的聲明模式CMap<KEY,ARG_KEY,VALUE,ARG_VALUE>感到迷惑,爲什麼不用CMap<KEY,VALUE> 

   呢?實際上,CMap中的的數據最終會是CPair,而CPair內部是(KEY,VALUE)。因此,CMap其實存儲的是KEY

   ,而非ARG_KEY。然而,如果你查看MFC的源代碼,幾乎CMap所有的內部參數傳遞都是訪問ARG_KEY和ARG_VALUE,因此,使用KEY&來代替ARG_KEY似乎是正確的,除了在這些情況下:
     1 應用簡單的數據類型,如int ,char用值傳遞與參數傳遞沒有什麼不同
     2 如果用CString作爲KEY,你應該用LPCTSTR做ARG_KEY而非CString&。

  2.有哪些與Map相關的典型操:

  1 向Map中插入具有給定關鍵字的元素單元。

  2 在Map中查找具有給定關鍵字的元素單元。

  3 在Map中刪除具有給定關鍵字的元素單元。

  4 枚舉(遍歷)Map中的所有元素單元。

   
三,簡單的例子:
   例子一: 我們來看一個CMap的用法,下面示例代碼:

 CMap<int,int&,CPoint,CPoint&> myMap;

 //初始化哈希表,並指定其大小(取奇數)。MyMap.InitHashTable(257);

 //向myMap中添加元素單元。
 for (int i=0;i < 200;i++)
   myMap.SetAt( i, CPoint(i, i) );

  // 刪除實際值爲偶數的關鍵字所對應的的元素單元。
 POSITION pos = myMap.GetStartPosition();
 int nKey;
 CPoint pt;
 while (pos != NULL)
 {
  myMap.GetNextAssoc( pos, nKey, pt );

  if ((nKey%2) == 0)
   myMap.RemoveKey( nKey );
 }

 #ifdef _DEBUG
  afxDump.SetDepth( 1 );
  afxDump << "myMap: " << &myMap << "/n";CMap<int,int&,CPoint,CPoint&> myMap;

//初始化哈希表,並指定其大小(取奇數)。MyMap.InitHashTable(257);

//向myMap中添加元素單元。
for (int i=0;i < 200;i++)
 myMap.SetAt( i, CPoint(i, i) );

 // 刪除實際值爲偶數的關鍵字所對應的的元素單元。
 POSITION pos = myMap.GetStartPosition();
 int nKey;
 CPoint pt;
 while (pos != NULL)
 {
  myMap.GetNextAssoc( pos, nKey, pt );

  if ((nKey%2) == 0)
   myMap.RemoveKey( nKey );
 }

 #ifdef _DEBUG
  afxDump.SetDepth( 1 );
  afxDump << "myMap: " << &myMap << "/n";


CMap是個很不錯的數據結構,尤其在你建立一個字典的時候。比如idcountry的含義是"中國",這就是一個元組

,也就是一個Pair,Key是"idcountry",而Value是"中國"。

例子二:

1、定義一個CMAP,向這個CMAP中增加數據項(鍵-值對)。
CMap<CString, LPCTSTR, CString, LPCTSTR>m_ItemMap;
CString strKey = _T(""), str = _T("");
int i;
for(i = 0; i < 5; i++)
    {
        strKey.Format("%d", i);              //這個是鍵
        str.Format("A%d", i);               //鍵對應的值 
        m_ItemMap.SetAt(strKey, str);
    }
2、遍歷正個CMAP的常用方法。
    POSITION pos = m_ItemMap.GetStartPosition();
    while(pos)
    {
        m_ItemMap.GetNextAssoc(pos, strKey, str);
        cout<< strKey<< ":"<< str<< endl;
    }
3、在CMAP中查找相應的數據項。
    CString pReset;
    if(m_ItemMap.Lookup("1", pReset))
    {
        cout<<pReset<<endl;
    }

 

cmap用法,很詳細(轉)

 

一、 Map的基本知識

  MFC中的提供了基於模板的CMap類。利用CMap模板類,可以處理特定的數據類型,例如用戶自定義的類或結構體等。同時,MFC也提供了基於指定 數據類型的非模板類,其中包括: 
 

類名 關鍵字類型 元素值類型
CMapWordToPtr WORDS Void pointers
CMapPtrToWord Void pointers WORDS
CMapPtrToPtr Void pointers Void pointers
CMapWordToOb WORDS Objects
CMapStringToOb Strings Objects
CMapStringToPtr Strings Void pointers
CMapStringToString Strings String

二、 Map的工作原理 

在MFC的CMap及其相關的Map類中,只要對Map進行正確設置,Lookup函數通常能夠一次到位的查找到任意元素,而很少需要進行兩次或者三 次以上的查找比對。 

struct CAssoc
{
CAssoc* pNext;
UINT nHashValue;
CString key;
CString value;
};

 

nHashTableSize是哈希表中元素的數目(默認情況下,哈希表的大 小爲17)。

如果在哈希表中的索引值爲i的位置已經容納了一個CAssoc指針,那麼MFC將建立一個單獨的CAssoc結構體的鏈表(List),鏈表中的第一 個CAssoc結構體的地址被存儲到哈希表中,而將第二個CAssoc結構體的地址存儲到前一個CAssoc結構體的pNext域,以此類推。

  但是,正如我們先前所討論的那樣,只要正確設置Map,鏈表中的元素一般就不會超過三個,這就意味着,查找通常可 以在三次元素比對操作之內完成。

三、 優化查找效率

  微軟推薦將哈希表的大小設置爲Map中所存儲元素數目的 110% ~120%,以使得Map的應用性能在內存消耗和查找效率之間取得相對平衡。

在MFC中,指定哈希表大小,可調用InitHashTable()函數:

map.InitHashTable(1200);

從統計學上考慮,用奇數作爲哈希表的大小也將有助於減少衝突的發生。因此,初始化一個存儲1000個元素的哈希表的InitHashTable() 函數可以如下形式使用:

map.InitHashTable(1201);

同時,在InitHashTable()函數的調用時機上,應該注意的是,該函數應當在map包含有任何元素之前使。如果map中已經包含了一個或者 更多的元素,那麼,重新改變map的大小,將會引發斷言(Assertion)錯誤。

儘管MFC中所使用的哈希算法能夠適應於大多數場合,但如果您真的有所需要,或者,只要你願意,用戶也可以使用自己的算法來取代原有的算法。對於一個 輸入的關鍵字的值,要計算出它的哈希值,MFC通常調用一個全局模板函數HashKey(),對於大多數數據類型而言,HashKey()函數是以下面的 方式實現的:
 


AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{
//一般情況的默認算法。
return ((UINT)(void*)(DWORD)key) >> 4;
}

但對於字符串而言,其具體的實現方式如下:

UINT AFXAPI HashKey(LPCWSTR key) // Unicode 編碼字符串
{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}

UINT AFXAPI HashKey(LPCSTR key) // ANSI編碼字符串
{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}


要實現對應於特定數據類型的用戶自定義哈希算法,您可以使用上述的字符串版本的HashKey()函數作爲參考,寫一個類似的特定類型的 HashKey()函數。

 

四、 使用MFC中的CMap類

  構造函數:

CMap 構造一個關鍵字和元素值映射的集合類。

操作: 

Lookup 通過給定的關鍵字查找相應的元素值。
SetAt 向Map中插入一個元素單元;若存在匹配鍵字,則替代之。
operator [] 向Map中插入一個元素 -SetAt的子操作
RemoveKey 移除由關鍵字標示的元素單元
RemoveAll 移除Map中的所有元素單元
GetStartPosition 返回第一個元素單元的位置
GetNextAssoc 讀取下一個元素單元
GetHashTableSize 返回哈希表的大小(元素單元的數目)
InitHashTable 初始化哈希表,並指定它的大小

狀態: 

GetCount 返回Map中元素的數目
IsEmpty 檢查Map是否爲空(無元素單元)

應用實例如下: 


CMap myMap;
//初始化哈希表,並指定其大小(取奇數)。

MyMap.InitHashTable(257);

//向myMap中添加元素單元。
for (int i=0;i < 200;i++)
myMap.SetAt( i, CPoint(i, i) );

// 刪除實際值爲偶數的關鍵字所對應的的元素單元。
POSITION pos = myMap.GetStartPosition();
int nKey;
CPoint pt;
while (pos != NULL)
{
myMap.GetNextAssoc( pos, nKey, pt );

if ((nKey%2) == 0)
myMap.RemoveKey( nKey );
}

#ifdef _DEBUG
afxDump.SetDepth( 1 );
afxDump << "myMap: " << &myMap << "/n";
#endif


2、選擇適當大小的奇數-- 或者,有可能的話,使用素數的效果會更好一些--來作爲哈希表的初始值。 

3、然後,向myMap中添加元素單元。 

4、使用myMap進行數據映射、查找、遍歷等操作。 

5、調用myMap.RemoveAll()函數移除所有元素,釋放myMap佔用的內存空間。 

CMap對應IMPLEMENT_SERIAL,從而支持用戶對其元素進行串行化(Serialization)以及傾注(Dumping)操作。在 對CMap的獨立元素進行傾注操作時,應該注意的是,你必須將傾注環境(Dump Context)的深度設置爲1或者更大的數字。 


CArray

 播報編輯討論上傳視頻
計算機科學領域術語
本詞條缺少概述圖,補充相關內容使詞條更完整,還能快速升級,趕緊來編輯吧!
CArray類支持與C arrays相似的數組,但是必要時可以動態壓縮並擴展。數組索引從0開始。可以決定是固定數組上界還是允許當添加元素時擴展當前的邊界。內存對上界是連續地分配空間,甚至一些元素可爲空。
和C arrays一樣,CArray索引元素的訪問時間是不變的,與數組大小無關。
 
 
外文名
CArray
屬    性
計算機科學領域術語
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的效率要高。
先了解一下CArray中的成員變量及作用。TYPE* m_pData; // 數據保存地址的指針
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;
}

 

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