CMap如何使用,如何实例化?

 

 

昨天,去CSND论坛看有人在问,关于CMap的用法,由于平时能用到,感觉的确非常好,很有用。
就在百度空间找到了有关CMpa使用的文章,我把两篇文章整合了。

 

(一)《CArray,CList,CMap如何实化(实例化)》

哎,这是乎又是一篇没有什么技术含量的应用性文章,但我,又能怎么样?虽然,我们在《CMap如何使用,用法举例》中,已经非常详细的说明了CMap参数的用法,也在其他的专题中介绍过了其它模板类的实化过程,不过我还是担心是否每个人都有足够的耐心去看完那些蜿蜒之天际的文章。所以,我们把CArray、CList以及CMap的参数问题独立出来,在此着重的讲解一番。
还是先看一个简单的实化例子吧:
typdedef CMap<CString, LPCTSTR,CString, CString&> CStrMap;
显然,向这样的用法,是无可厚非的,就像我们国家的中医,在经过无数次致命的尝试之后,得到了这个不再苦涩的,似乎也可以包治百病的灵丹妙药。可是,这毕竟是个令人心惊胆战的用法,尤其对于那些并不熟悉并试图熟悉CMap的小家伙们。我们还是先来看看CMap类型参数列表吧:
template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
它继承了CArray以及CList的参数风格,将传入类型与返回类型分开,其中带有ARG_前缀的是传入类型,另外一个则是返回类型,这已经是我们不止一次的抱怨为什么要将类型参数分开了,其实实践证明,它们的确可以被合并起来,不过参数传入的灵活性将会受到部分的影响。
我们在《CMap如何使用,用法举例》中,已经比较详细的讨论过为什么CMap的ARG_KEY与KEY可以是不同的参数类型,其实这可以推广到所有类似的模板之中。不得不声明的一点是:在CMap中,实际存储的是KEY以及VALUE。那么,现在我们来看看CStrMap到底是如何实化的。
我们看到在CMap中,类型参数ARG_KEY的引参是LPCTSTR,而与之对应的KEY的引参却又是CString,它们是如此的固执,既然都指代了同样的内容,为什么又偏偏将对方视若不见呢?其实,这与CMap的内部实现存在着密不可分的关系,由于在CMap的内部,建立了一个Hash表,所以,必须将所有的传入参数置换为类似于int类型的整数,这样才可以符合散列函数的参数类型,否则CMap在编译时会报错。那么又是为什么?我们可以将ARG_KEY使用LPCTSTR来实化呢?其实,这又牵扯到了Hash表的实现问题,当然其中也包含了CString的实现问题。我们知道,存储在CStrMap内部的KEY仍然是CString,而我们对Hash表的操作,无非也就是给出一个整型的数,然后根据散列函数来寻找一个存储地址而已,尤其,当我们使用operator[]操作符的时候,如果我们给定的KEY所对应的VALUE并不存在的话,CMap会自动的为我们生成一个相应的元组,而如果你继而对它赋值的话,它会将ARG_KEY直接赋值给KEY,而这里就涉及到KEY的赋值构造函数了(其实,我更想叫它赋值操作符),现在我们使用的KEY是CString,而我们却要将一个LPCTSTR赋值于它,所以,我们就必须重载这个函数,其实也就是这样的一个函数:
CString& operator=(LPCTSTR lpsz);
显然,这立即解决了我们的问题,不过,这仅仅是从逻辑功能层面,如果你传入的KEY是个很长的字符串的话,那么必然会对CStrMap的性能造成影响,其实这样的隐患也同样存在于VALUE身上,乃至其它类似的模板类上,这不得不引起我们的足够重视!
C++,这门完美的近乎没有任何瑕疵的OOP语言,无疑就是闪耀星空的旷世巨献,然而,这似乎并没有引起每个人的共鸣。C++从C中的诞生,似乎就注定了,它永远都不可能像JAVA那样血统纯正,我不得不承认,它并非一个严格意义上的OOP(Oriented-object language,面向对象设计语言),然而就是这种近乎不合理的缺陷,却又造就了它近乎不合理的适应性,我是向来瞧不起解释性语言的,因为我本人就编写过脚本的解释器,实践证明,它们的效率要远远的落后于编译性语言,虽然它们常常号称自己如何的平台无关,如何的小而强大。
但,对于绝大多数的程序员来说,JAVA的确有很多的优势,也许其中之一,便是简单易学,其实这似乎更像一个借口。指针一度是困扰相当一部分程序员的暗物质,而C++更将这种震慑发挥到极致。显然C++的确很难,难到令这个世界上,至少50%以上的程序员,望而却步。然而一个几乎包罗万象的程序设计语言,几乎能容忍一切的全能语言,它到底有着什么样的魔力,却令另外50%的人如此的钟意,如此的着迷?
偏题三千里,言归正传。
至于C++的性能,似乎一直被OOP的性格所掩盖,而OOP主张的面向对象编程,更让许多窃窃私语的程序员有了足够的底气发言,同时他们宣称,C++显然就是应该由对象统治的世界,其实--大错特错,简直错的一败涂地。C++,归根结底,还是由指针执政,以致我们可以直接的操纵内存,直接和系统底层会话,并且还能实现我们空前强大的令人炫目的多态。所以,我们必须要利用这个得天独厚的资源。
偏题一千里,再言归正传。
现在,我已经知道,直接在C++程序中使用对象,是何等的不专业,显然,我们更应该使用指针,曾经有一位高人,不无自我解嘲的说,他写程序的第一年连一个指针都不敢用,后来又在他的婴幼儿奶粉级的大作中大肆声讨指针,可想他曾经遭受了指针怎样的折磨。
在程序中使用指针,几乎可以使你的程序恢复到C的性能级别,显然我不会回避你正在思考命题,不错,C的性能的确要高于C++,而汇编语言的性能显然要进一步的高于C,只要学过一点编译原理的人,应该不会反对这个早已过时的辩论焦点。关键在于,从C过度到C++,已经是一种编程思维的转变。然而很多人,既没有继承C的高性能性,又未能领悟C++的封装、继承与多态性,却妄图从C直接飞到C++上,却被挂在了C与C++的中间,左右为难,大概停留到了C+这门语言去了。
言归正传。
我们在定义函数的时候,应该尽可能的少传对象,而更应该传指针或者引用,我们知道对于编译器内部来说,引用其实就是一个指针。这最主要的原因是,我们不必再为函数参数的圧栈而苦恼不已;同样,我们也不应该在函数结束之后,用对象来返回,这同样增加了函数圧栈的开销,而更应该适可的返回一个指针或者一个引用,当然,这样的指针和引用,绝对不能指向一个函数体内的临时对象。然而传入CMap中的类型参数,到底应该是什么,这似乎已经上升到了算法的层次,从而使我们对于实化本身的讨论变的毫无意义。
我也许会主张,将CMap实化成这种模样:
typedef CMap<CString*, CString*, CString*, CString*> CStrPtrMap;
个中缘由,当然还是出于对效率的考虑,我们在SetAt之前先new出一个CString对象,然后将其填入CStrPtrMap,这样的好处在于我们在SetAt的时候不需要去调用CString的赋值构造函数,而仅仅作简单的数据拷贝,这样的行为几乎适合所有的复杂数据结构,包括类,结构体。当然,唯一不能忘记的是,必须在必要的时候释放那些new出来的CString对象,否则,将会导致严重的内存泄露!
当然,你可以根据自己的兴趣,去总结到底应该选择怎么样的KEY与ARG_KEY。由于一般来说,KEY都不可能是太大的字符串,从而为了兼顾效果与效率,作为一种折中的方案就是定义这样的一个类似于字典的CMap实化类:
typedef CMap<CString, LPCTSTR, CString*, CString*> CStrPtrMap;
啊,这又是一个长着人身猪脸的八戒啊!不过,既然要去西天取经,八戒就八戒吧。
当然,千叮万嘱的还是,别忘记释放为CString*申请的内存!至于另外两个模板的参数选择,基本上大同小异,并且要比CMap简单,就不再罗嗦了。


(二)《CMap如何使用?用法举例》
现在,我们来学习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是"曳光科技"。

 

参考网址:http://hi.baidu.com/idealsoft/blog/item/e24ae0c736274dd9d10060c2.html

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值