本文是《手把手教你调试STL容器》系列的下篇,阅读本文之前,请先阅读上篇《手把手教你调试STL容器(上)》。上篇中主要介绍了STL中string, vector, list, deque这些基本的容器。本篇将介绍由红黑树实现的map/set/multimap/multiset这些容器,以及由hashtable实现的hash_map/hash_set/hash_multimap/hash_multiset这些容器。
-
1. 红黑树(Red black tree)
我们知道, STL中的map/set/multimap/multiset,都是由红黑树实现的,因此我们要了解map/set/multimap/multiset这些容器是如何实现的,就首先要了解红黑树的基本组成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
enum _Rb_tree_color { _S_red = false, _S_black = true }; struct _Rb_tree_node_base { typedef _Rb_tree_node_base* _Base_ptr; typedef const _Rb_tree_node_base* _Const_Base_ptr; _Rb_tree_color _M_color; _Base_ptr _M_parent; _Base_ptr _M_left; _Base_ptr _M_right; }; template< typename _Val> struct _Rb_tree_node : public _Rb_tree_node_base { typedef _Rb_tree_node< _val>* _Link_type; _Val _M_value_field; }; template< typename _Key_compare, bool _Is_pod_comparator = std::__is_pod< _Key_compare>::__value> struct _Rb_tree_impl : public _Node_allocator { _Key_compare _M_key_compare; _Rb_tree_node_base _M_header; size_type _M_node_count; // Keeps track of size of tree. }; _Rb_tree_impl< _compare> _M_impl;
从上面的代码中,我们知道,STL中红黑树中保存的基本信息包括:根结点对象_M_header和所有结点的个数_M_node_count,_M_header本身只是一个哨兵功能,它里面包含了指向它的左右子树的指针。在真实存放数据的结点中,每个结点都是_Rb_tree_node< _val>类型(_Val是数据类型),用户可以通过_M_value_field,得到存放的数据的值。
另外,我们也知道,红黑树本身是非线形结构,因此遍历的过程,还是需要迭代器来帮忙的,下面的红黑树的迭代器的基本结构:
1 2 3 4 5 6
template< typename _Tp> struct _Rb_tree_iterator { typedef _Rb_tree_node_base::_Base_ptr _Base_ptr; _Base_ptr _M_node; };
从上面的定义可以看出,红黑树的迭代器本身只是保存了一个_Rb_tree_node_base的指针,我们只要按实际的数据类型,把它转型为_Rb_tree_node类型,就可以通过_M_value_field得到数据的值。
在STL中,map/set/multimap/multiset都是由红黑树实现的,这些容器,都是在红黑树的基础上,包裹了一层,实现了相关功能,在这些容器中,都有一个这样类似的成员:
从上面的代码我们可以知道,我们要想查看某个map/set/multimap/multiset里面存放的数据,只需要用_M_t,再结合前面对红黑树结构的介绍,便可以。另外,map/set/multimap/multiset的迭代器也是在红黑树迭代器基础上包裹的,这里不再做介绍。1 2
typedef _Rb_tree< ...> _Rep_type; _Rep_type _M_t;
-
2. HashTable
在STL中,hash_map/hash_set/hash_multimap/hash_multiset中由hashtable来实现的,虽说hash_map/hash_set/hash_multimap/hash_multise这些容器,目前还不是标准C++的内容,但是由于实际应用的需要,大家目前都把这些容器当做事实标准,而且大多数的编译器也支持。这里介绍的是GNU的stdext中的实现,下面是hashtable的基本组成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
template < class _Val> struct _Hashtable_node { _Hashtable_node* _M_next; _Val _M_val; }; template < class _Val, class _Key, class _HashFcn, class _ExtractKey, class _EqualKey, class _Alloc> class hashtable { public: typedef _Key key_type; typedef _Val value_type; typedef _HashFcn hasher; typedef _EqualKey key_equal; typedef _Hashtable_node< _Val> _Node; typedef vector< _Node*, _Nodeptr_Alloc> _Vector_type; hasher _M_hash; key_equal _M_equals; _ExtractKey _M_get_key; _Vector_type _M_buckets; size_type _M_num_elements; };
通过上面的代码,我们可以得知,hashtable是由vector+list实现的一个结构,纵向是一个vector,横向是一个list,这正是separate chain hash的实现。我们结合上篇中对vector的介绍,用_M_impl._M_start拿到hashtable中vector中数据的起始地址,然后通过下标便可得到每个横向链表的头指针。在每个横向的链表中,只要我们得到了某个结点的指针,便可以用_M_val来查看其数据值,可以用_M_next来查看下一个结点的情况。
另外,我们也可以通过hashtable的迭代器来查看hashtable中某个结点的数据值:
1 2 3 4 5 6 7
template < class _Val, class _Key, class _HashFcn, class _ExtractKey, class _EqualKey, class _Alloc> struct _Hashtable_iterator { _Node* _M_cur; _Hashtable* _M_ht; };
上面是hashtable的迭代器的定义,通过_M_cur便可得知当前位置的内容。
我们知道,hash_map/hash_set/hash_multimap/hash_multi_set都是在hashtable的基础上包裹实现的,在这些容器中,都有一个这样类似的成员:
1 2
typedef hashtable< ...> _Ht _Ht _M_ht;
从上面可以看出,只要我们拿到hash_map/hash_set/hash_multimap/hash_multiset这些容器的_M_ht成员,再结合前面对hashtable的介绍,我们便可以方便的查看到这些容器中的所有内容。
链接地址:http://www.wuzesheng.com/?p=1733