參數
KEY對象的類,用作映射的關鍵碼。ARG_KEY參數KEY使用的數據類型,通常為KEY的參考。VALUE存儲在映射中對象的類。ARG_VALUE參數VALUE使用的數據類型,通常為VALUE的參考。
說明
CMap是把唯一關鍵碼映射到值的字典收集類。一旦在映射中插入了一個關鍵碼值對(元素),就可以使用這些關鍵碼,有效地獲取或刪除對。同樣,也可以反覆使用映射中的所有元素。
POSITION類型變數用於替換所有映射變數的入口。可以使用POSITION來“記憶”入口和映射中的遍歷。可能認為這種遍歷是通過關鍵碼值來依次進行的,但其實不是。獲取元素的次序沒有確定。
該類的某些成員函式調用了全局的幫助函式,它們必須定製,以滿足CMap類的更多用途。請參閱“Microsoft Visual C++ MFC庫參考”中的“宏和全局”部分中的“收集類幫助程式”。
CMap引入了宏IMPLEMENT_SERIAL,支持其元素的串列化和轉儲。如果映射存儲到檔案檔案中,那么每一元素都可利用載入插入(<<)操作符或Serialize成員函式來依次進行串列化。如果要了解有關在映射中進行個別元素的診斷轉儲,那么轉儲內容的深度必須為1或更大。當CMap對象刪除或其元素被刪除,那么關鍵碼和值都將被刪除。映射類的派生與列表的派生相似。
請參閱在線上文檔“Visual C++程式設計師指南”中的“收集”部分,以進一步了解特殊用途的列表類的派生。
#include <afxtempl.h>
CMap類的成員
構造函式
CMap構造一個映射關鍵碼為值的收集
操作符
Lookup查找與指定關鍵碼對應的值;SetAt在映射中插入一個元素,但假如發現了相匹配的關鍵碼,則替換已經存在的元素;operator []在映射中插入一個元素,它是代替SetAt的操作符;RemoveKey刪除關鍵碼指定的元素;RemoveAll刪除映射中所有的元素;GetStartPosition返回第一個元素的位置;GetNextAssoc獲取循環中下一個元素;GetHashTableSize返回散列表的大小(元素的個數);InitHashTable初始化散列表,並指定其大小。
狀態
GetCount返回映射中元素的個數;IsEmpty測試是否為空映射(即沒有元素)。
CMap用法
歸根到底,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&,原因我稍後說明。
我如何將CMap用於我自己的類ClassX正如我剛才提到的,CMap是一種Hash Map,Hash Map要求每個元素都要有一個Hash值——一個關於KEY的函式,Hash Map用這個值作為Hash表的索引。如果有多個KEY的Hash值相同,它們將以鍊表的方式存儲。所以,你要做的第一件事就是提供一個Hash函式。
CMap會調用模板函式HashKey()來計算Hash值。
默認的實現以及對於LPCSTR和LPCWSTR的專門實現如下:
// inside <afxtemp.h>
template<class ARG_KEY>
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
...{
// default identity hash - works for most primitive values
return (DWORD)(((DWORD_PTR)key)>>4);
}
// inside <strcore.cpp>
// specialized implementation for LPCWSTR
#if _MSC_VER >= 1100
template<> UINT AFXAPI HashKey<LPCWSTR> (LPCWSTR key)
#else
UINT AFXAPI HashKey(LPCWSTR key)
#endif
...{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}
// specialized implementation for LPCSTR
#if _MSC_VER >= 1100
template<> UINT AFXAPI HashKey<LPCSTR> (LPCSTR key)
#else
UINT AFXAPI HashKey(LPCSTR key)
#endif
...{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}
如你所見,預設行為會“假定”KEY是一個指針,並將它轉換為DWORD類型,這就是為什麼當你沒有為你的ClassX提供一個專門的HashKey()時你會得到“error C2440: ''type cast'': cannot convert from ''ClassXXX'' to ''DWORD_PTR''”錯誤的原因。
同時,因為MFC只是實際了LPCSTR和LPCWSTR的專門化,並沒有實現CStringA和CStringW的專門化,因此如果你想使用CString作為CMap的鍵類型,你要聲明為CMap<CString, LPCTSTR, ……>。
好了,現在你知道CMap如何計算Hash值了,但是由於可能會有多個鍵的Hash值相同,CMap需要遍歷整個鍊表來找到要求的數據,而不只是在相同的Hash值中。並且當CMap進行匹配時,會調用CompareElements(),這是另一個模板函式。
// inside <afxtemp.h>
// noted: when called from CMap,
// TYPE=KEY, ARG_TYPE=ARG_TYPE
// and note pElement1 is TYPE*, not TYPE
template<class TYPE, class ARG_TYPE>
BOOL AFXAPI CompareElements(const TYPE* pElement1,
const ARG_TYPE* pElement2)
...{
ASSERT(AfxIsValidAddress(pElement1,
sizeof(TYPE), FALSE));
ASSERT(AfxIsValidAddress(pElement2,
sizeof(ARG_TYPE), FALSE));
// for CMap<CString, LPCTSTR...>
// we are comparing CString == LPCTSTR
return *pElement1 == *pElement2;
}
因此,如果你想讓CMap用於你自己的類ClassX,就必須提供HashKey()和CompareElements()的專門化實現。
示例:CMap用於CString*作為一個例子,以下說明了要將CMap用於CString*前你需要做的。當然了,是使用字元串的值作為鍵(KEY),而不是用指針的地址。
template<>
UINT AFXAPI HashKey<CString*> (CString* key)
...{
return (NULL == key) ? 0 : HashKey((LPCTSTR)(*key));
}
// I don''t know why, but CompareElements can''t work with CString*
// have to define this
typedef CString* LPCString;
template<>
BOOL AFXAPI CompareElements<LPCString, LPCString>
(const LPCString* pElement1, const LPCString* pElement2)
...{
if ( *pElement1 == *pElement2 ) ...{
// true even if pE1==pE2==NULL
return true;
} else if ( NULL != *pElement1 && NULL != *pElement2 ) ...{
// both are not NULL
return **pElement1 == **pElement2;
} else ...{
// either one is NULL
return false;
}
}
Main函式如下:
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
...{
CMap<CString*, CString*, int, int> map;
CString name1 = "Microsoft";
CString name2 = "Microsoft";
map[&name1] = 100;
int x = map[&name2];
printf("%s = %d ", (LPCTSTR)name1, x);*/
return 0;
}
--------- console output ---------
Microsoft = 100
注意即使你沒有提供HashKey()和CompareElements()的專門化編譯器也不會報錯,但這樣的話輸出為0,或許不是你想要的。
關於CMap的總結CMap是一種Hash Map而STL::map是Tree Map,比較兩者的效率是沒有意義的(如同比較蘋果和桔子!)。但是如果你要按順序取得關鍵字,你需要使用STL::map。
HashKey()的設計是效率的關鍵。你應該提供一個低碰撞(即不同的關鍵字產生相同的Hash值)率、容易計算(而不是像MD5那樣)的HashKey()。我們必須注意這點——至少對於某些類來說——並不是件容易的事。
當使用CMap(以及STL::hash_map)時,注意Hash表的大小。引用一段MSDN的說明:“Hash表的大小應該是一個質數。為了減少碰撞,Hash表的大小應該超出最大預計容量的20%。默認情況下,CMap的Hash表大小為17,這對於10個關鍵字左右的數據很合適。你可以用InitHashTable(UINT nHashSize)來改變Hash表的大小,並且只能在加入第一個元素之前這樣做。你可能在這裡找到很多質數。(不要與CMap(UINT nBlockSize)弄混了,nBlockSize用於獲得多個CAssoc來加速創建新結點。)