文章的由来是由于今天自己的一个非常低级的错误,我需要以一个双CString作为key来插入到set中,所以自己定义了了如下:
struct MsgSubscribeName
{
MsgSubscribeName(){}
MsgSubscribeName(CString _systemName, CString _msgName):SystemName(_systemName), MsgName(_msgName)
{}
bool operator< (const MsgSubscribeName& right)const
{ if(SystemName != right.SystemName || MsgName != right.MsgName)
{
return true;
}
else
{
return false;
}
}
CString SystemName;
CString MsgName;
};
之所以那么去定义operator < 是由于如果不定义这个,很抱歉,是没有办法插入的,因为无法为operator <(const set& XX,const set& XXXX)操作进行模板推演的,当时没也没考虑到判等操作,想反正operator只用来定位插入位置,那么大小也没关。。。结果插入正常,开心,运行起后,发现代码居然阻塞在set<MsgSubscribeName>::count。。。悲剧了,第一反应是死循环了,但是比较纳闷为什么count会死循环。
开始怀疑set和map之类的定位判等操作,网上搜索,无过,问好友set如何利用less进行key的判等定位,也是表示不清楚,只好硬着头皮看STL的源码。。。
size_type count(const _K& _Kv) const
{return (_Tr.count(_Kv)); }
size_type count(const _K& _Kv) const
{
_Paircc _Ans = equal_range(_Kv);
size_type _N = 0;
_Distance(_Ans.first, _Ans.second, _N);
return (_N); }
_Pairii equal_range(const _K& _Kv)
{return (_Pairii(lower_bound(_Kv), upper_bound(_Kv))); }
const_iterator upper_bound(const _K& _Kv) const
{return (iterator(_Ubound(_Kv))); }
_Nodeptr _Ubound(const _K& _Kv) const
{_Nodeptr _X = _Root();
_Nodeptr _Y = _Head;
while (_X != _Nil)
if (key_compare(_Kv, _Key(_X)))
_Y = _X, _X = _Left(_X);
else
_X = _Right(_X);
return (_Y); }
const_iterator lower_bound(const _K& _Kv) const
{return (const_iterator(_Lbound(_Kv))); }
这是整个调用栈,可以清晰的看出,count是基于UBound和LBound来进行断定,根据我手工模拟草算结果Ubound相当于标定了end迭代的效果,而LBound相当于标定beign迭代的效果,此时,计算这对所谓的迭代器就可以得到当前count的元素的个数,在set和map中,这个显然是0或1.UBound == Lbound时,就相当于标定了一对end迭代器效果。
重点就放在_Ubound函数,看看这个函数是如何推到出_Kv的最大下标(即end迭代器),首先我们必须要明白一个众所周知的基本,STL中的set以及map在多数STL版本中是基于RBTree实现的,那么此时看这个代码,就相当明晰,_X定位到Root,进入while后,如果_Kv小于_X,则_X置为左子树根节点,因为所有的左子树节点均要小于根,这个是排序二叉树的基本常识,此时,继续往下走,如果大于则去右子树。这样就能得到end迭代。 同理推导_Lbound得到begin迭代
最后总结一下,根据源码分析可以看出,其实count的计算是基于一组迭代器的减法操作,而这组迭代器的获取是基于上述的_Lbound和_Ubound得出,可以看出,set和map利用RBTree是十分高效的进行二分查找定位,因为RBTree是平衡树!!
擦,8点了,要回去了