C++ STL 容器设计实现 -- map/set

6、map/multimap/set/multiset

6.1、rb_tree

map 和 multimap 以及 set 和 multiset 底层数据结构都是红黑树,如果对红黑树数据结构感兴趣,可以参考关于红黑树的分享

一文搞懂红黑树

这里简单介绍一下 map 和 multimap 底层红黑树的布局。rb_tree 有一个 rb_tree_impl 类型的数据成员,rb_tree_impl 实现红黑树,rb_tree 是对 rb_tree_impl 的封装。

header 是实现的技巧,header.parent 保存根结点 root,header.left 保存最左元素,是中序遍历的首元素,header.right 保存最右元素,是中序遍历的最末元素,root->parent 指向的是 header。

向 rbtree 插入元素时,需要确定插入的位置,保持二叉搜索树的性质。rbtree 提供两个接口:_M_get_insert_unique_pos() 和 _M_get_insert_equal_pos() 函数。前者适用于插入后所有元素必须唯一,否则插入失败;后置没有这个限制,是普通插入情况。

_M_get_insert_equal_pos() 函数定义如下,比较直观。在 rbtree 从根节点开始搜索,如果当前结点大于 k,在其左子树继续搜索,否则在其右子树搜索,直到 x 为空,k 插入 y 的左子树或者右子树。

/// stl_tree.h
  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
    pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::_Base_ptr,
     typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::_Base_ptr>
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
    _M_get_insert_equal_pos(const key_type& __k)
    {
      typedef pair<_Base_ptr, _Base_ptr> _Res;
      _Link_type __x = _M_begin();
      _Base_ptr __y = _M_end();
      while (__x != 0)
    {
      __y = __x;
      __x = _M_impl._M_key_compare(__k, _S_key(__x)) ?
        _S_left(__x) : _S_right(__x);
    } // x == null,退出循环
      return _Res(__x, __y);
    }

_M_get_insert_unique_pos() 函数就复杂一点,因为要确保插入 k 之后,rbtree 没有重复的节点。首先和 _M_get_insert_equal_pos() 函数相同,也是找到执行插入的结点 y。然后检查,rbtree 中是否已经存在某个节点,其值和 k 相同。

  • 如果 k 插入到 y 的左子树,k < y 已经符合,还需要核查 k 是否大于 y 中序遍历前一个结点的值;
  • 如果 k 插入到 y 的右子树,需要确认 y < k;
/// stl_tree.h
  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
    pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::_Base_ptr,
     typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::_Base_ptr>
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
    _M_get_insert_unique_pos(const key_type& __k)
    {
      typedef pair<_Base_ptr, _Base_ptr> _Res;
      _Link_type __x = _M_begin();
      _Base_ptr __y = _M_end();
      bool __comp = true;
      while (__x != 0)
    {
      __y = __x;
      __comp = _M_impl._M_key_compare(__k, _S_key(__x)); // k < x/y --> true
      __x = __comp ? _S_left(__x) : _S_right(__x);
    }
      iterator __j = iterator(__y);
      if (__comp) // k < y,插入后 y->left = x
    {
      if (__j == begin())
        return _Res(__x, __y);
      else
        --__j; // 找到 y 中序遍历的前节点
    }
      if (_M_impl._M_key_compare(_S_key(__j._M_node), __k)) // j < k
    return _Res(__x, __y);
      return _Res(__j._M_node, 0);
    }

为了方便处理插入,rbtree 定义了 _Audo_node 类,使用 RAII,管理 node 的申请于释放。

/// stl_tree.h
      // An RAII _Node handle
      struct _Auto_node
      {
    template<typename... _Args>
      _Auto_node(_Rb_tree& __t, _Args&&... __args)
      : _M_t(__t),
        _M_node(__t._M_create_node(std::forward<_Args>(__args)...))
      { }

    ~_Auto_node()
    {
      if (_M_node)
        _M_t._M_drop_node(_M_node);
    }

    _Auto_node(_Auto_node&& __n)
    : _M_t(__n._M_t), _M_node(__n._M_node)
    { __n._M_node = nullptr; }

    const _Key&
    _M_key() const
    { return _S_key(_M_node); }

    iterator
    _M_insert(pair<_Base_ptr, _Base_ptr> __p)
    {
      auto __it = _M_t._M_insert_node(__p.first, __p.second, _M_node);
      _M_node = nullptr;
      return __it;
    }

    iterator
    _M_insert_equal_lower()
    {
      auto __it = _M_t._M_insert_equal_lower_node(_M_node);
      _M_node = nullptr;
      return __it;
    }

    _Rb_tree& _M_t;
    _Link_type _M_node;
      };

6.2、map

map 就是对 rbtree 的封装,只有一个 rbtree 类型的数据成员。rbtree 节点数据域 vaule_type 是 std::pair 类型,first 和 second 分别存放 key 和 value。

/// stl_map.h
  template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
        typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
    class map
    {
    public:
      typedef _Key					key_type;
      typedef _Tp					mapped_type;
      typedef std::pair<const _Key, _Tp>		value_type;
      typedef _Compare					key_compare;
      typedef _Alloc					allocator_type;

    public:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
      class value_compare
      : public std::binary_function<value_type, value_type, bool>
      {
    friend class map<_Key, _Tp, _Compare, _Alloc>;
      protected:
    _Compare comp;

    value_compare(_Compare __c)
    : comp(__c) { }

      public:
    bool operator()(const value_type& __x, const value_type& __y) const
    { return comp(__x.first, __y.first); }
      };
#pragma GCC diagnostic pop

    private:
      /// This turns a red-black tree into a [multi]map.
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
    rebind<value_type>::other _Pair_alloc_type;

      typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
               key_compare, _Pair_alloc_type> _Rep_type;

      /// The actual tree structure.
      _Rep_type _M_t; // 红黑树

      typedef __gnu_cxx::__alloc_traits<_Pair_alloc_type> _Alloc_traits;

#if __cplusplus >= 201703L
      template<typename _Up, typename _Vp = remove_reference_t<_Up>>
    static constexpr bool __usable_key
      = __or_v<is_same<const _Vp, const _Key>,
           __and_<is_scalar<_Vp>, is_scalar<_Key>>>;
#endif

    public:
      // many of these are specified differently in ISO, but the following are
      // "functionally equivalent"
      typedef typename _Alloc_traits::pointer		 pointer;
      typedef typename _Alloc_traits::const_pointer	 const_pointer;
      typedef typename _Alloc_traits::reference		 reference;
      typedef typename _Alloc_traits::const_reference	 const_reference;
      typedef typename _Rep_type::iterator		 iterator;
      typedef typename _Rep_type::const_iterator	 const_iterator;
      typedef typename _Rep_type::size_type		 size_type;
      typedef typename _Rep_type::difference_type	 difference_type;
      typedef typename _Rep_type::reverse_iterator	 reverse_iterator;
      typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;

#if __cplusplus > 201402L
      using node_type = typename _Rep_type::node_type;
      using insert_return_type = typename _Rep_type::insert_return_type;
#endif
      /* ... */
    };

6.2.1、operator[]

operator[] 用于访问 map 元素,如果元素不存在,则执行插入操作。

/// stl_map.h
      mapped_type&
      operator[](const key_type& __k)
      {
    // concept requirements
    __glibcxx_function_requires(_DefaultConstructibleConcept<mapped_type>)

    iterator __i = lower_bound(__k);
    // __i->first is greater than or equivalent to __k.
    if (__i == end() || key_comp()(__k, (*__i).first))
#if __cplusplus >= 201103L
      __i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,
                        std::tuple<const key_type&>(__k),
                        std::tuple<>());
#else
      __i = insert(__i, value_type(__k, mapped_type()));
#endif
    return (*__i).second;
      }

_M_emplace_hint_unique() 函数首先调用 _M_get_insert_hint_unique_pos() 查找插入位置,如果可以插入,则执行插入操作。

/// stl_tree.h
  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
    template<typename... _Args>
      auto
      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
      _M_emplace_hint_unique(const_iterator __pos, _Args&&... __args)
      -> iterator
      {
    _Auto_node __z(*this, std::forward<_Args>(__args)...);
    auto __res = _M_get_insert_hint_unique_pos(__pos, __z._M_key());
    if (__res.second)
      return __z._M_insert(__res);
    return iterator(__res.first);
      }

_M_get_insert_hint_unique_pos() 函数会先处理 easy-case

  • 可以插入在最右节点右子树,插入
  • 可以插入在最左节点左子树,插入
  • k < pos && --pos < k,插入
  • pos < k && ++pos > k,插入
  • 其他情况,调用 _M_get_insert_unique_pos() 函数搜索插入位置。

上面 -- 表示中序遍历前一个节点,++ 表示中序遍历后一个节点。

/// stl_tree.h
  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
    pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::_Base_ptr,
     typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::_Base_ptr>
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
    _M_get_insert_hint_unique_pos(const_iterator __position,
                  const key_type& __k)
    {
      iterator __pos = __position._M_const_cast();
      typedef pair<_Base_ptr, _Base_ptr> _Res;

      // end()
      if (__pos._M_node == _M_end())
    {
      if (size() > 0
          && _M_impl._M_key_compare(_S_key(_M_rightmost()), __k))
        return _Res(0, _M_rightmost()); // 在最右节点右子树插入
      else
        return _M_get_insert_unique_pos(__k); // 检索
    }
      else if (_M_impl._M_key_compare(__k, _S_key(__pos._M_node)))
    {
      // First, try before...
      iterator __before = __pos;
      if (__pos._M_node == _M_leftmost()) // begin()
        return _Res(_M_leftmost(), _M_leftmost()); // 在最左节点左子树插入
      else if (_M_impl._M_key_compare(_S_key((--__before)._M_node), __k))
        { // k < pos && --pos < k
          if (_S_right(__before._M_node) == 0)
        return _Res(0, __before._M_node);
          else
        return _Res(__pos._M_node, __pos._M_node);
        }
      else
        return _M_get_insert_unique_pos(__k);
    }
      else if (_M_impl._M_key_compare(_S_key(__pos._M_node), __k))
    {
      // ... then try after.
      iterator __after = __pos;
      if (__pos._M_node == _M_rightmost())
        return _Res(0, _M_rightmost()); // 在最右节点右子树插入
      else if (_M_impl._M_key_compare(__k, _S_key((++__after)._M_node)))
        { // pos < k && ++pos > k
          if (_S_right(__pos._M_node) == 0)
        return _Res(0, __pos._M_node);
          else
        return _Res(__after._M_node, __after._M_node);
        }
      else
        return _M_get_insert_unique_pos(__k);
    }
      else
    // Equivalent keys.
    return _Res(__pos._M_node, 0);
    }

6.2.2、insert()

insert() 函数返回一个 std::pair 遍历,first 是一个迭代器,second 是一个 bool 遍历,表示插入是否成功。如果成功,first 指向插入的元素。

/// stl_map.h
      std::pair<iterator, bool>
      insert(value_type&& __x)
      { return _M_t._M_insert_unique(std::move(__x)); }

_M_insert_unique() 函数逻辑比较简单,先调用 _M_get_insert_unique_pos() 函数搜索插入位置,如果可以插入,就执行插入函数。

/// stl_tree.h
  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
    template<typename _Arg>
#endif
    pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
               _Compare, _Alloc>::iterator, bool>
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L
    _M_insert_unique(_Arg&& __v)
#else
      /* ... */
#endif
    {
      typedef pair<iterator, bool> _Res;
      pair<_Base_ptr, _Base_ptr> __res
    = _M_get_insert_unique_pos(_KeyOfValue()(__v));

      if (__res.second)
    {
      _Alloc_node __an(*this);
      return _Res(_M_insert_(__res.first, __res.second,
                 _GLIBCXX_FORWARD(_Arg, __v), __an),
              true);
    }

      return _Res(iterator(__res.first), false); // 插入失败
    }

6.3、multimap

multimap 和 map 定义相同,知识处理插入操作时,不检查插入后是否有相同的元素。

/// stl_multimap.h
  template <typename _Key, typename _Tp,
        typename _Compare = std::less<_Key>,
        typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
    class multimap
    {
    public:
      typedef _Key					key_type;
      typedef _Tp					mapped_type;
      typedef std::pair<const _Key, _Tp>		value_type;
      typedef _Compare					key_compare;
      typedef _Alloc					allocator_type;

    private:
#ifdef _GLIBCXX_CONCEPT_CHECKS
      // concept requirements
      typedef typename _Alloc::value_type		_Alloc_value_type;
# if __cplusplus < 201103L
      __glibcxx_class_requires(_Tp, _SGIAssignableConcept)
# endif
      __glibcxx_class_requires4(_Compare, bool, _Key, _Key,
                _BinaryFunctionConcept)
      __glibcxx_class_requires2(value_type, _Alloc_value_type, _SameTypeConcept)
#endif

#if __cplusplus >= 201103L
#if __cplusplus > 201703L || defined __STRICT_ANSI__
      static_assert(is_same<typename _Alloc::value_type, value_type>::value,
      "std::multimap must have the same value_type as its allocator");
#endif
#endif

    public:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
      class value_compare
      : public std::binary_function<value_type, value_type, bool>
      {
    friend class multimap<_Key, _Tp, _Compare, _Alloc>;
      protected:
    _Compare comp;

    value_compare(_Compare __c)
    : comp(__c) { }

      public:
    bool operator()(const value_type& __x, const value_type& __y) const
    { return comp(__x.first, __y.first); }
      };
#pragma GCC diagnostic pop

    private:
      /// This turns a red-black tree into a [multi]map.
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
    rebind<value_type>::other _Pair_alloc_type;

      typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
               key_compare, _Pair_alloc_type> _Rep_type;
      /// The actual tree structure.
      _Rep_type _M_t;

      typedef __gnu_cxx::__alloc_traits<_Pair_alloc_type> _Alloc_traits;

    public:
      // many of these are specified differently in ISO, but the following are
      // "functionally equivalent"
      typedef typename _Alloc_traits::pointer		 pointer;
      typedef typename _Alloc_traits::const_pointer	 const_pointer;
      typedef typename _Alloc_traits::reference		 reference;
      typedef typename _Alloc_traits::const_reference	 const_reference;
      typedef typename _Rep_type::iterator		 iterator;
      typedef typename _Rep_type::const_iterator	 const_iterator;
      typedef typename _Rep_type::size_type		 size_type;
      typedef typename _Rep_type::difference_type	 difference_type;
      typedef typename _Rep_type::reverse_iterator	 reverse_iterator;
      typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;

#if __cplusplus > 201402L
      using node_type = typename _Rep_type::node_type;
#endif
      /* ... */
    };

6.3.1、insert

insert() 函数返回一个迭代器,指向插入的元素。

/// stl_multimap.h
      iterator
      insert(const value_type& __x)
      { return _M_t._M_insert_equal(__x); }

      iterator
      insert(value_type&& __x)
      { return _M_t._M_insert_equal(std::move(__x)); }

_M_insert_equal() 函数首先调用 _M_get_insert_equal_pos() 函数检索插入位置,然后执行插入。

/// stl_tree.h
  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
    template<typename _Arg>
#endif
    typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L
    _M_insert_equal(_Arg&& __v)
#else
      /* ... */
#endif
    {
      pair<_Base_ptr, _Base_ptr> __res
    = _M_get_insert_equal_pos(_KeyOfValue()(__v));
      _Alloc_node __an(*this);
      return _M_insert_(__res.first, __res.second,
            _GLIBCXX_FORWARD(_Arg, __v), __an);
    }

6.3.2、lower_bound()

lower_bound() 函数返回一个迭代器,迭代器指向第一个不小于 x 的元素。

/// stl_multimap.h
      iterator
      lower_bound(const key_type& __x)
      { return _M_t.lower_bound(__x); }

rbtree 红黑树 lower_bound() 函数实现比较简单,当 k <= x 时,在左子树查找,否则在右子树查找。与 k 相等的元素,就在 y 的右边,也就是中序遍历在 y 的后续节点。

/// stl_tree.h
      iterator
      lower_bound(const key_type& __k)
      { return _M_lower_bound(_M_begin(), _M_end(), __k); }

  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
    typename _Rb_tree<_Key, _Val, _KeyOfValue,
              _Compare, _Alloc>::iterator
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
    _M_lower_bound(_Link_type __x, _Base_ptr __y,
           const _Key& __k)
    {
      while (__x != 0)
    if (!_M_impl._M_key_compare(_S_key(__x), __k)) // k <= x
      __y = __x, __x = _S_left(__x);
    else
      __x = _S_right(__x); // x < k
      return iterator(__y);
    }

6.3.3、upper_bound()

upper_bound() 函数返回一个迭代器,迭代器指向第一个大于 x 的元素。

/// stl_multimap.h
      iterator
      upper_bound(const key_type& __x)
      { return _M_t.upper_bound(__x); }

_M_upper_bound() 函数当 k < x 时,在左子树查找,否则在右子树查找。这样子,与 k 相等元素,在 y 的左边,中序遍历时,是 y 的前序节点。

/// stl_tree.h
     iterator
      upper_bound(const key_type& __k)
      { return _M_upper_bound(_M_begin(), _M_end(), __k); }

  template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
    typename _Rb_tree<_Key, _Val, _KeyOfValue,
              _Compare, _Alloc>::iterator
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
    _M_upper_bound(_Link_type __x, _Base_ptr __y,
           const _Key& __k)
    {
      while (__x != 0)
    if (_M_impl._M_key_compare(__k, _S_key(__x)))
      __y = __x, __x = _S_left(__x);
    else
      __x = _S_right(__x);
      return iterator(__y);
    }

6.4、set

set 和 map 的定义基本相同,唯一的不同是 rbtree 的元素类型是 value_type。除此之外,其他定义相同,没有特殊之处。

/// stl_set.h
  template<typename _Key, typename _Compare = std::less<_Key>,
       typename _Alloc = std::allocator<_Key> >
    class set
    {
    public:
      // typedefs:
      ///@{
      /// Public typedefs.
      typedef _Key     key_type;
      typedef _Key     value_type;
      typedef _Compare key_compare;
      typedef _Compare value_compare;
      typedef _Alloc   allocator_type;
      ///@}

    private:
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
    rebind<_Key>::other _Key_alloc_type;

      typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
               key_compare, _Key_alloc_type> _Rep_type;
      _Rep_type _M_t;  // Red-black tree representing set.

      typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;

    public:
      ///@{
      ///  Iterator-related typedefs.
      typedef typename _Alloc_traits::pointer		 pointer;
      typedef typename _Alloc_traits::const_pointer	 const_pointer;
      typedef typename _Alloc_traits::reference		 reference;
      typedef typename _Alloc_traits::const_reference	 const_reference;
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 103. set::iterator is required to be modifiable,
      // but this allows modification of keys.
      typedef typename _Rep_type::const_iterator	 iterator;
      typedef typename _Rep_type::const_iterator	 const_iterator;
      typedef typename _Rep_type::const_reverse_iterator reverse_iterator;
      typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
      typedef typename _Rep_type::size_type		 size_type;
      typedef typename _Rep_type::difference_type	 difference_type;
      ///@}

#if __cplusplus > 201402L
      using node_type = typename _Rep_type::node_type;
      using insert_return_type = typename _Rep_type::insert_return_type;
#endif
      /* ... */
    };

insert() 函数也和 map::insert 定义相同

/// stl_set.h
      std::pair<iterator, bool>
      insert(const value_type& __x)
      {
    std::pair<typename _Rep_type::iterator, bool> __p =
      _M_t._M_insert_unique(__x);
    return std::pair<iterator, bool>(__p.first, __p.second);
      }

6.5、multiset

multiset 也和 multimap 定义基本相同,只是 rbtree 节点元素是 value_type。

/// stl_multiset.h
  template <typename _Key, typename _Compare = std::less<_Key>,
        typename _Alloc = std::allocator<_Key> >
    class multiset
    {
    public:
      // typedefs:
      typedef _Key     key_type;
      typedef _Key     value_type;
      typedef _Compare key_compare;
      typedef _Compare value_compare;
      typedef _Alloc   allocator_type;

    private:
      /// This turns a red-black tree into a [multi]set.
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
    rebind<_Key>::other _Key_alloc_type;

      typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
               key_compare, _Key_alloc_type> _Rep_type;
      /// The actual tree structure.
      _Rep_type _M_t;

      typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;

    public:
      typedef typename _Alloc_traits::pointer		 pointer;
      typedef typename _Alloc_traits::const_pointer	 const_pointer;
      typedef typename _Alloc_traits::reference		 reference;
      typedef typename _Alloc_traits::const_reference	 const_reference;
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 103. set::iterator is required to be modifiable,
      // but this allows modification of keys.
      typedef typename _Rep_type::const_iterator	 iterator;
      typedef typename _Rep_type::const_iterator	 const_iterator;
      typedef typename _Rep_type::const_reverse_iterator reverse_iterator;
      typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
      typedef typename _Rep_type::size_type		 size_type;
      typedef typename _Rep_type::difference_type	 difference_type;

#if __cplusplus > 201402L
      using node_type = typename _Rep_type::node_type;
#endif
      /* ... */
    };

insert()/lower_bound()/upper_bound() 函数也和 multimap 对应的相同。

/// stl_multiset.h
      iterator
      insert(const value_type& __x)
      { return _M_t._M_insert_equal(__x); }

      iterator
      lower_bound(const key_type& __x)
      { return _M_t.lower_bound(__x); }

      iterator
      upper_bound(const key_type& __x)
      { return _M_t.upper_bound(__x); }
#晒一晒我的offer##实习,投递多份简历没人回复怎么办##c++#
C/C++基础 文章被收录于专栏

C/C++ 语言基础

全部评论

相关推荐

10-09 00:50
已编辑
长江大学 算法工程师
不期而遇的夏天:1.同学你面试评价不错,概率很大,请耐心等待;2.你的排名比较靠前,不要担心,耐心等待;3.问题不大,正在审批,不要着急签其他公司,等等我们!4.预计9月中下旬,安心过节;5.下周会有结果,请耐心等待下;6.可能国庆节前后,一有结果我马上通知你;7.预计10月中旬,再坚持一下;8.正在走流程,就这两天了;9.同学,结果我也不知道,你如果查到了也告诉我一声;10.同学你出线不明朗,建议签其他公司保底!11.同学你找了哪些公司,我也在找工作。
点赞 评论 收藏
分享
joe2333:怀念以前大家拿华为当保底的日子
点赞 评论 收藏
分享
工作基本定了,比较满意。这深度学习真是难搞啊,论文看不懂,代码看不懂,不会改,创新点???&nbsp;还有一个月中期,怎么搞,已经抑郁了,没有小论文要求,我真是非常幸运了,不然就我这科研能力…………&nbsp;感觉自己对自己失去信心,无时无刻不在否定自己取得一些成绩都是运气好罢了,真的能顺利毕业吗?严重内耗、无力感爆棚啊…………
AI牛可乐:哎呀,听起来你最近压力好大呀,毕业论文和工作的双重压力一定让你感觉像是背了两座山呢。深度学习确实挺复杂的,就像是要在一片迷雾中找到出路一样。但是,你已经很棒了,工作都定好了,这是很大的成就呢! 论文的事情,别太担心。每个人都有看不懂代码和论文的时候,这是正常的。至于创新点嘛,有时候灵感就像小猫一样,需要一点一点地哄出来的。你可以先试试把你看过的资料整理一下,说不定就能找到新的思路呢。 中期答辩前,你可以列个计划,一步一步来,把大问题分解成小问题,一点一点克服。记得,你不是一个人在战斗,你的导师、同学都是你的战友哦。 至于信心嘛,别否定自己,每个人都会有低谷的时候。你已经取得的成绩,都是你实力的体现。毕业不是问题,只要你不放弃,就一定能顺利走过去的。加油哦,我相信你可以的! 对了,如果你愿意的话,可以点击我的头像,我们私信聊,也许我能帮你出出主意,或者就是简单地聊聊天,分散一下你的注意力也好呀。🐮💪🌟
点赞 评论 收藏
分享
2 1 评论
分享
牛客网
牛客企业服务