映射表类(CMap)是MFC集合类中的一个模板类,也称作为“字典”,就像一种只有两列的表格,一列是关键字,一列是数据项,它们是一一对应的。关键字是唯一的,给出一个关键字,映射表类会很快找到对应的数据项。映射表的查找是以哈希表的方式进行的,因此在映射表中查找数值项的速度很快。映射类最适用于需要根据关键字进行快速检索的场合,我们的程序中就用映射表来保存计时器标志值和类实例指针,用计时器的标志值作为关键字。 他这个有点像数组,比如你要查找a[index],不必先遍历前面的index个元素,只不过数组的下标是哈希表键值,它是以键值对的形式出现的。举个例子来说吧,公司的所有职员都有一个工号和自己的姓名,工号就是姓名的关键字,给出一个工号,就可以很快的找到相应的姓名。
举例如下:
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; }
=======================================================================
现在,我们来学习MFC中,最常用的数据结构中的最后一个CMap模板。之前,我们已经依次学完了CArray,CList,并且也对它们进行了初步的剖析。
其实,我一直认为CMap是最简单的一个数据类型,如果说,大家对这个数据类型产生不良感觉的话,大多是因为对Hash表的陌生。
显然,CMap就是对Hash表的一种实现。对于Hash表来说,我们需要提供成对的Key与Value进行操作,其实,也就是将我们日常使用的数组下标替换成现在Key,至于MFC是采用了什么样的散列函数,我们不必知道。
Hash表可以认为是数组的一种优化,或者说是对数组缺陷的一种弥补,因为我们知道,数组在具备了高效存取性能的同时,无法动态的调整自身的大小,又严重的影响了它的使用效果。这给了Hash表可乘之机,Hash表总是使用了某种算法尽可能的来达到将成对的元素存储到一个额定的离散的内存空间,它既继承了链表对自身的动态调整,又尽可能的使读写维持在高速的水平,当然无论如何还是要比数组慢的多。
如果你非要让我告诉你,Hash表是什么样的一个数据结构的话,很遗憾,我无法准确的描述,这就相当于你问我“凤凰是什么样子”,不过我可以告诉你孔雀的样子。常用的Hash表非常像一个十字数组,似乎十字数组又成为了众多读者的障碍,如果你暂时还不能理解的话,请你去翻阅Hash表的详细论述,当然你也可以在不久之后,在本处看到这些经典数据结构的精讲。
现在,我们来看一个CMap的用法,至于它的参数,你可以看本空间一篇专门描述CArray,CList以及CMap参数用法的文章《CArray,CList,CMap如何实化(实例化)》。下面是我自己编写的例子:
class Point { public: Point() { m_x = 0; m_y = 0; } Point(int x, int y) { m_x = x; m_y = y; } public: int m_x; int m_y; }; typedef CMap<const char*, const char*, Point, Point&> CMapPnt; //请在使用之前定义 int main() { Point elem1(1, 100), elem2(2, 200), elem3(3, 300), point; CMapPnt mp; // insert 3 elements into map, #1 mp.SetAt("1st", elem1); mp.SetAt("2nd", elem2); mp.SetAt("3th", elem3); // search a point named "2nd" from map #2 mp.Lookup("2nd", point); printf("2nd: m_x: %d, m_y: %d\n", point.m_x, point.m_y); // insert a new pair into map #3 Point elem4(4, 400); mp["4th"] = elem4; cout<<"count: "<<mp.GetCount()<<endl; // traverse the entire map #4 size_t index = 0; const char* pszKey; POSITION ps = mp.GetStartPosition(); while( ps ) { mp.GetNextAssoc(ps, pszKey, point); printf("index: %d, m_x: %d, m_y: %d\n", ++index, point.m_x, point.m_y); } return 0; }
代码中,我已经给出了一些注释,我同样建议读者们,用英文在代码中注释,这样的好处实在是太多了。尤其在代码需要在不同编码的操作系统上调试的时候。
对于CMap这个类,我不得不着重啰嗦一下的是:遍历操作以及取下标[]操作,当然还有那个令很多人困惑不已的ARG_KEY到底应该如何选择的问题。
遍历,看注释#4,至于POSITION的含义,请在本空间,查看其它文章。先使用GetStartPosition()函数获得表头的位置,然后,我们可以使用GetNextAssoc函数来遍历。GetNextAssoc(POSITION& rNextPosition, KEY& rKey, VALUE& rValue)函数的参数值得说明一下,大家看到,3个参数都是引用,而第一个是rNextPosition,顾名思义,在函数返回之后,它将会指像下一个元组,当然这是在表还未遍历完的时候,否则,它将被置为空(NULL)。
[],利用下标取元素的这个操作符,在CMap中被重载,用来返回指定Key值数据的引用,不过在注释#3处,对于先取"4th"这个Point的引用然后赋值的用法,看起来,似乎有点聪明过了头,因为在这之前,我们还没有插入"4th"所对应的元组,但是,程序却能正常的运行!为什么?其实,这样的用法是十分正确的,因为CMap毕竟不是数组,它是没有边界的,当CMap在获得一个它无法查询到的Key值的时候,它会将这个Key以及一个空的数据类型追加到Hash表中去,从而保证了上面的程序可以无误的运行。
我们已经说过,ARG_KEY是作为类型参数传入CMap的,但并不是任何类型都可以作为ARG_KEY传入的。为什么?看样子,这次不得不简单的说说Hash表的散列函数了。每个Hash表,总会使用一些散列函数,用来查找Key所对应的Value,理想状态下,我们当然希望Hash表,就是一个数组,虽然这不可能,不过这样理解,可以帮助我们更好的理解Hash表的物理结构,就让我们暂时把它看成一个数组吧。数组总是使用下标来直接获取元素的存储地址,而下标,显然应该是个非负整数,从而Hash表,也应该具备这样的特性,至少必须存在某种算法可以使传入的Key可以直接的转化为一个非负的整数,这也就是ARG_KEY的选择标准。从而对象、引用无论如何都不应该作为ARG_KEY成为CMap的类型参数,而int、unsigned int、指针以及地址就成为了ARG_KEY的常用类型参数,其实也就是那些类似于整型的数据类型。常常看到一些人在用CMap的时候,试图使用CString作为CMap中ARG_KEY的类型参数,这是应该被纠正的方向性错误,但有些人似乎会理直气壮的反驳我,因为他们发现类型参数KEY是可以使用CString的,这很奇怪吗?我说过KEY不能使用CString吗?之所以KEY可以使用CString而ARG_KEY却用的是LPCTSTR,那是因为CString重载了operator==(const char*)这个判等操作符,当CMap从Hash表中获得KEY之后,它会将ARG_KEY与KEY直接相比较。真正存于CMap内部的是KEY,也就是CString。这也就是为什么,我们经常会看到CMap被实化成CMap<CString, LPCTSTR/*相当于const char*,非Unicode情况下*/, CString,CString&>这样的一个四不像实化类的原因,至于CMap的效率优化问题,我们会在以后的文章中继续与大家探讨。
CMap的确是个很不错的数据结构,尤其在你建立一个字典的时候。比如idealsoft的含义是"曳光科技",这就是一个元组,也就是一个Pair,Key是"idealsoft",而Value是"曳光科技"。
====================================================================
#include <afxwin.h> #include <afxtempl.h> void main() { AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0); CMap <int, int, CString, CString> m_cMap; m_cMap.SetAt(9923033, "张三 "); m_cMap.SetAt(9826033, "张A "); m_cMap.SetAt(9923063, "张B "); m_cMap.SetAt(9923093, "张C "); CString strName; m_cMap.Lookup(9923063, strName); AfxMessageBox(strName); }