VC的CMap类和STL的map的使用及使用过程中遇到的错误
1、 CMap类:
映射表类(CMap)是MFC集合类中的一个模板类,它是对Hash表的一种实现,也称作为“字典”,就像一种只有两列的表格,一列是关键字,一列是数据项,它们是一一对应的,。
关键字是唯一的,给出一个关键字,映射表类会很快找到对应的数据项。映射表的查找是以哈希表的方式进行的,因此在映射表中查找数值项的速度很快。映射类最适用于需要根据关键字进行快速检索的场合,他这个有点像数组,比如你要查找a[index],不必先遍历前面的index个元素,只不过数组的下标是哈希表键值,它是以键值对的形式出现的。
Class member:
Lookup | 查找与指定关键码对应的值 |
SetAt | 在映射中插入一个元素,但假如发现了相匹配的关键码,则替换已经存在的元素 |
operator [] | 在映射中插入一个元素,它是代替SetAt的操作符 |
RemoveKey | 删除关键码指定的元素 |
删除映射中所有的元素 | |
GetStartPosition | 返回第一个元素的位置 |
GetNextAssoc | 获取循环中下一个元素 |
GetHashTableSize | 返回散列表的大小(元素的个数) |
InitHashTable | 初始化散列表,并指定其大小 |
2、 使用CMap遇到的问题:
在使用过程中发现,Key只能是long型的或者是能转换成long型的数据,我使用CSting型时出现错误:cannot convert from 'class CString' to 'unsigned long',跟踪到错误处:
template<class ARG_KEY>
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{
// default identity hash - works for most primitive values
return ((UINT)(void*)(DWORD)key) >> 4;
}
就算是把Key设为INT64型的也只能比较低32位,所以准备改用STL的map。
3、 STL的map:
map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在处理一对一数据时,在编程上提供快速通道。介绍一下map内部数据的组织,map内部自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。
#include <map>
#include <string>
Using namespace std;
map<string,int> STRING2INT
基本操作:
[]:赋值或插入,find:查找,insert:插入,erase:删除。
用insert函数插入数据,在数据的插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作插入失败,但是用[ ]操作符,它可以覆盖以前该关键字对应的值。
2 、使用map遇到的问题:
(1)MSVCP60.dll错误:编译、链接都没错,Debug模式下运行时出错,MSVCP60.dll错误,网上查资料发现是使用string的原因,最后改为map<INT64,int> INT642INT,没问题了。
(2)warning 4678太多:标准库中的标志符超长了,在所有#include之前加入#pragma warning( disable : 4786 )
屏蔽掉这类warning,但是发现不管用,也许是我的工程中文件太多,包含关系太复杂。新建了一个最简单的工程试了一下可以。
Introduction
Programmers like me, learnt STL::map before CMap always think CMap is difficult to use, and always try to use CMap in the way as a STL::map. In this article, I will explain more information about CMap and what should you do to use it for your own custom class. And as the end of this article, I will show an example of how to use CMap correctly with CString* (note I mean CString pointer and not CString :> )
The first thing to be noted is that CMap is actually a hash map, and not a tree map (and usually a Red-black tree) as STL::map. Below shows the internal structure of a CMap.
Many people get confused about CMap's declaration CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>, why not just CMap<KEY, VALUE>?
If you use CString as KEY, you should use LPCTSTR as ARG_KEY and not CString&, we will talk more about this later
So what should I do to make CMap works with my ClassX
Well, as I mentioned earlier, CMap is a hash map, a hash map will try to get the "hash value" -- an UINT -- from the key and use that hash value as the index in the hash table (well, actually it is hash value % hash table size). If more then one key have the same hash value, they will be link in a link list. Therefore, the first thing you have to do is to provide a hash function.
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);
}
// 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;
}
#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;
}
As you can see, the default behavior is to "assume" key is a pointer, and convert it to DWORD, and that's why you will get "error C2440: 'type cast': cannot convert from 'ClassXXX' to 'DWORD_PTR'" if you don't provide a specialized HashKey() for your ClassX.
// 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));
// we are comparing CString == LPCTSTR
return *pElement1 == *pElement2;
}
Therefore, if you want to use CMap with you own custom ClassX, you will have to provide a specialized implementation for HashKey() and CompareElements().
Provided as an example, below is what you need to do to make CMap works with CString*, and of course, using the string content as the key, and not the address of the pointer.
UINT AFXAPI HashKey<CString*> (CString* key)
{
return (NULL == key) ? 0 : HashKey((LPCTSTR)(*key));
}
// have to define this
typedef CString* LPCString;
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;
}
}
And the main program is as simple as the following:
{
CMap<CString*, CString*, int, int> map;
CString name1 = "Microsoft";
CString name2 = "Microsoft";
map[&name1] = 100;
int x = map[&name2];
return 0;
}
Microsoft = 100
Please note that the program can compile without error even without the specialized HashKey() and CompareElements(), but of course, the out output will then be 0, probably not what you want.
CMap is a hash map and STL::map is a tree map, it is no meaning to compare the two performance (compare apple to orange!). But if you will retrieve the keys in sorted order, then you will have to use STL::map.
Reference
[MSDN] Collection Class Helper
[MSDN] CMap::InitHashTable
[NIST DADS] Red Black Tree
History
17th March 2006: Initial version uploaded.
这是另外一篇文章,一并转载于此:
如何声明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&,接下来我门会讨论原因。
好的,就象我前面说过的,CMap是一个哈西表,一个哈西表要有“哈西值“——一个UINT类型,用哈西值作为在哈西表中的序数。如果有更多的相同的关键字,他们会组成一个链表。因此,你应该首先构造哈西函数。CMap类会调用摸板函数HashKey()来构造哈西函数。缺省应用和特别版的LPCSTR和LPCWSTR如下:
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;
}
正如你所见到的,缺省行为是“假定“关键字是一个指针,并且转变成DWORD类型,这就是为什么会出现“error C2440:’type cast’:cannot convert from ‘ClassXXX’to ‘DWORD_PTR’”如果你不提供一个特别的HashKey()函数给你的类就会出现上述情况。并且由于MFC仅仅提供了特殊的工具LPCSTR和LPCWSTR,却没有提供CStringA或CStringW,如果你想要在CMap中用CString,就必须声明CMap<CString ,LPCSTR….>,OK,现在你知道怎么计算CMap的哈西值了,但是因为一个关键字可能对应多个哈西值,CMap就需要找遍整个链表来找到正确的“摸板”,不仅用同样的“哈西值”。当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,你不得不重写HashKey()和CompareElements()
结束语
1 CMap是一个哈西表,而STL::map是一个树表,对他们做比较是没有意义的。但是,如果你你要重新找到有序的关键字,你就得使用STL::map
2 HashKey()的设计是高效的。你应该提供一个较少冲突的HashKey(),并且容易计算。你要记注,对于有些类来说,这不容易。
3 当用Cmap(或STL::hash_map),要注意哈西表的大小。
using namespace std;
template<>
UINT AFXAPI HashKey<CString*> (CString* key)
{
return (NULL == key) ? 0 : HashKey((LPCTSTR)(*key));
}
// have to define this
typedef CString* LPCString;
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;
}
}