STL之map

        map 是一种关联式容器,它的所有元素都是 pairpair 的第一元素为键值 key,第二元素为实值 value,不允许两个元素有相同的键值,且所有元素都会根据元素的键值自动排序。下面从源码角度看看 map 是如何实现的,假设有这么段代码:

map<int, int> testMap;
testMap[1] = 2;
testMap[2] = 4;
testMap[3] = 6;

        代码首先定义了个键值和实值都为 int 类型的 testMap,先看下 map 的定义:

template <class _Key, class _Tp, class _Compare = less<_Key>,
          class _Allocator = allocator<pair<const _Key, _Tp> > >
class _LIBCPP_TEMPLATE_VIS map
{
public:
    // types:
    typedef _Key                                     key_type;
    typedef _Tp                                      mapped_type;
    typedef pair<const key_type, mapped_type>        value_type;
    ...
private:
    ...
    __base __tree_;
    ...
}

        此处 __base_ 类型定义暂时省略,可以看出以下几点:

1. 模板类的第三个参数 _Compare 默认采用键值的递增排序;

2. 分配器的参数是个 pair,第一个参数是经 const 修饰的键值,第二个是对应的实值;

3. 有个私有类型 __tree_,通过此成员与红黑树关联。

        testMap 定义好后,通过 [] 运算符添加了三个元素,这里 mapoperator[] 有进行重载:

template <class _Key, class _Tp, class _Compare, class _Allocator>
_Tp&
map<_Key, _Tp, _Compare, _Allocator>::operator[](const key_type& __k)
{
    return __tree_.__emplace_unique_key_args(__k,
        _VSTD::piecewise_construct,
        _VSTD::forward_as_tuple(__k),
        _VSTD::forward_as_tuple()).first->__get_value().second;
}

        看方法体也只是转调用了红黑树的 __emplace_unique_key_args() 方法:

template <class _Tp, class _Compare, class _Allocator>
template <class _Key, class... _Args>
pair<typename __tree<_Tp, _Compare, _Allocator>::iterator, bool>
__tree<_Tp, _Compare, _Allocator>::__emplace_unique_key_args(_Key const& __k, _Args&&... __args)
{
    __parent_pointer __parent;
    __node_base_pointer& __child = __find_equal(__parent, __k);
    __node_pointer __r = static_cast<__node_pointer>(__child);
    bool __inserted = false;
    if (__child == nullptr)
    {
        __node_holder __h = __construct_node(_VSTD::forward<_Args>(__args)...);
        __insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__h.get()));
        __r = __h.release();
        __inserted = true;
    }
    return pair<iterator, bool>(iterator(__r), __inserted);
}

        该方法返回的是个 pair,第一个元素是迭代器,第二个元素为 bool 类型,若为真则表示插入成功,反之插入失败。方法体中首先调用 __find_equal_() 方法判断当前插入的元素键值是否已存在,若存在则 __child 为空,且将 bool 型设为 false;若不存在则分别调用 __construct_node() 方法创建节点和 __insert_node_at() 方法插入节点。

template <class _Tp, class _Compare, class _Allocator>
void __tree<_Tp, _Compare, _Allocator>::__insert_node_at(
    __parent_pointer __parent, __node_base_pointer& __child,
    __node_base_pointer __new_node) _NOEXCEPT
{
    __new_node->__left_   = nullptr;
    __new_node->__right_  = nullptr;
    __new_node->__parent_ = __parent;
    // __new_node->__is_black_ is initialized in __tree_balance_after_insert
    __child = __new_node;
    if (__begin_node()->__left_ != nullptr)
        __begin_node() = static_cast<__iter_pointer>(__begin_node()->__left_);
    _VSTD::__tree_balance_after_insert(__end_node()->__left_, __child);
    ++size();
}

        __insert_node_at() 方法实际上不仅仅只是插入节点,插入后还会调用方法 __tree_balance_after_insert() 进行再平衡,以符合红黑树的内在要求。元素添加以后可对其遍历:

map<int, int>::iterator it;
for (it = testMap.begin(); it != testMap.end(); ++it) {
    cout << "key: " << it->first << ", value: " << it->second << endl;
}

        这里先是定义了个迭代器,并将初始值赋为 testMap.begin(),然后依次遍历。那么迭代器是怎么和 pair 关联起来的呢?看下 begin() 方法实现:

// map
typedef __map_iterator<typename __base::iterator>         iterator;
iterator begin() _NOEXCEPT {return __tree_.begin();}

// __tree
typedef __tree_iterator<value_type, __node_pointer, difference_type> iterator;
iterator begin()  _NOEXCEPT {return       iterator(__begin_node());}

__iter_pointer& __begin_node() _NOEXCEPT {return __begin_node_;}

        __begin_node_ 是类 __tree_ 的一个 __iter_pointer 类型的私有成员,在给迭代器赋初始值过程中由 __begin_node() 方法包装返回,它始终指向红黑树中的最左节点,也就是键值的最小值节点,这点可以在上文提到的 __insert_node_at() 方法中看到。

        另外 map 文件和 __tree 文件中的 begin() 方法返回的虽然都是 iterator 类型,但根据类型定义其实是不同的。那这俩又是怎么关联起来的呢?可以看下类模板 __map_iterator 定义:

template <class _TreeIterator>
class _LIBCPP_TEMPLATE_VIS __map_iterator
{
    typedef typename _TreeIterator::_NodeTypes           _NodeTypes;
    typedef typename _TreeIterator::__pointer_traits     __pointer_traits;

    _TreeIterator __i_;
    ...
}

        它有个私有成员 __i_,实际上就是 __tree_iterator 类型,此处可通过 gdb 调试来进行验证。

        现在我们知道迭代器实际上就是个 __map_iterator 类型的对象,而这个对象可直接调用 pair 的数据成员 firstsecond,这里就要看声明 __map_iterator 对象时的 __base 的定义了。

// map
public:
    // types:
    typedef _Key                                     key_type;
    typedef _Tp                                      mapped_type;
private:
    typedef _VSTD::__value_type<key_type, mapped_type>    __value_type;
    typedef __map_value_compare<key_type, __value_type, key_compare> __vc;    
    typedef typename __rebind_alloc_helper<allocator_traits<allocator_type>, __value_type>::type __allocator_type;
    typedef __tree<__value_type, __vc, __allocator_type>   __base;

        其中类 _VSTD::_value_type 定义如下:

template <class _Key, class _Tp>
struct __value_type
{
    typedef _Key                                     key_type;
    typedef _Tp                                      mapped_type;
    typedef pair<const key_type, mapped_type>        value_type;
    typedef pair<key_type&, mapped_type&>            __nc_ref_pair_type;
    typedef pair<key_type&&, mapped_type&&>          __nc_rref_pair_type;
    
private:
    value_type __cc;
    ...
}

        类 __value_type 有个 value_type 类型的私有成员 __cc,实际上也就是 pair 类型,至此就可以将迭代器 __map_iteratorpair 关联起来了,而 pair 定义如下:

template <class _T1, class _T2>
struct _LIBCPP_TEMPLATE_VIS pair
#if defined(_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR)
: private __non_trivially_copyable_base<_T1, _T2>
#endif
{
    typedef _T1 first_type;
    typedef _T2 second_type;

    _T1 first;
    _T2 second;
    ...
    explicit pair(_T1 const& __t1, _T2 const& __t2)
        _NOEXCEPT_(is_nothrow_copy_constructible<first_type>::value &&
                   is_nothrow_copy_constructible<second_type>::value)
        : first(__t1), second(__t2) {}
    ...
}

        在构造函数中将模板参数 _t1  和 _t2 分别赋值给数据成员 firstsecond,自然也就可以通过它俩分别访问元素的键值和实值了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值