VC的CMap类和STL的map的使用及使用过程中遇到的错误

VC的CMap类和STL的map的使用及使用过程中遇到的错误

1、 CMap类:

映射表类(CMap)是MFC集合类中的一个模板类,它是对Hash表的一种实现,也称作为“字典”,就像一种只有两列的表格,一列是关键字,一列是数据项,它们是一一对应的,。

关键字是唯一的,给出一个关键字,映射表类会很快找到对应的数据项。映射表的查找是以哈希表的方式进行的,因此在映射表中查找数值项的速度很快。映射类最适用于需要根据关键字进行快速检索的场合,他这个有点像数组,比如你要查找a[index],不必先遍历前面的index个元素,只不过数组的下标是哈希表键值,它是以键值对的形式出现的。

 

Class member

Lookup

查找与指定关键码对应的值

SetAt

在映射中插入一个元素,但假如发现了相匹配的关键码,则替换已经存在的元素

operator []

在映射中插入一个元素,它是代替SetAt的操作符

RemoveKey

删除关键码指定的元素

RemoveAll

删除映射中所有的元素

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:

mapSTL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在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,但是发现不管用,也许是我的工程中文件太多,包含关系太复杂。新建了一个最简单的工程试了一下可以。

********************************************************************************
来篇英文原作的:
CMap 使用注意说明(English) 收藏
 
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 :> )
CMap Internal
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.
 
How to declare a CMap
Many people get confused about CMap's declaration CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>, why not just CMap<KEY, VALUE>? 
In fact, the ultimate data container in CMap is CPair, and the internal of CPair is {KEY, VALUE}. Therefore, CMap will really store a KEY, and not ARG_KEY. However, if you check with the MFC source code, almost all the internal parameters passing within CMap itself is called with ARG_KEY and ARG_VALUE, therefore, using KEY& as ARG_KEY seems always be a correct answer, except
You are using primitive date types like int, char, where pass-by-value makes no difference (may be even faster) with pass-by-reference
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.
CMap will call a templated function HashKey() to do the hashing. The default implementation and specialized version for LPCSTR and LPCWSTR are listed as follows:
// 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;
}
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.
And because MFC only have specialized implementation for the LPCSTR and LPCWSTR, and not for CStringA nor CStringW, this is why if you want to use CString in CMap, you have to declare CMap<CString, LPCTSTR....>
Ok, now you know how CMap calculate the hash value, but since more than one key may have the same hash value, CMap needs to traverse the whole link list to find the one with exactly the same key "content", not only with the same "hash value". And when CMap do the matching, it will call CompareElements(), another templated function.
// 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;
}
Therefore, if you want to use CMap with you own custom ClassX, you will have to provide a specialized implementation for HashKey() and CompareElements().
Example: CMap with CString*
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.
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;
 }
}
And the main program is as simple as the following:
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/n", (LPCTSTR)name1, x);*/
 return 0;
}
--------- console output ---------
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.
My final note about CMap
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.
The design of HashKey() is critical to the overall performance. You should provide a HashKey() that has low collision rate (different key generally would have different hash value) AND is easy to calculate (not a MD5 of the string, etc..). And we have to note that -- at least for some of the classes -- this is not easy.
When using CMap (as well as STL::hash_map), always beware of the hash table size. As quoted in MSDN "the hash table size should be a prime number. To minimize collisions, the size should be roughly 20 percent larger than the largest anticipated data set". By default, CMap hash table size is 17, which should be ok for around 10 keys. You can change the hash table size with InitHashTable(UINT nHashSize), and only can do so before the first element is added to the map. You can find more prime numbers at here. (And don't mix-up with CMap(UINT nBlockSize), nBlockSize is to acquire more than one CAssoc to speed up the creation of a new node.)
Reference
[MSDN] Collection Class Helper
[MSDN] CMap::InitHashTable
[NIST DADS] Red Black Tree 
History
17th March 2006: Initial version uploaded.
这是另外一篇文章,一并转载于此:
 
CMap详解
如何声明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类为自己工作
好的,就象我前面说过的,CMap是一个哈西表,一个哈西表要有“哈西值“——一个UINT类型,用哈西值作为在哈西表中的序数。如果有更多的相同的关键字,他们会组成一个链表。因此,你应该首先构造哈西函数。CMap类会调用摸板函数HashKey()来构造哈西函数。缺省应用和特别版的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;
}
正如你所见到的,缺省行为是“假定“关键字是一个指针,并且转变成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),要注意哈西表的大小。
附能用于CString的CMap重写的HashKey()和CompareElements()
using namespace std;
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;
 }
}
 
---END---
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值