STL之顺序容器


一、容器简介

一个容器保存着一个对象序列。容器可分类为:

  • 顺序容器提供对元素序列的访问
  • 关联容器提供基于关键字的关联查询
    此外,标准库还提供了一些保存元素的对象类型,它们并未提供顺序容器或关联容器的全部功能:
  • 容器适配器提供对底层容器的特殊访问
  • 拟容器保存元素序列,提供容器的大部分但非全部功能

1.1、顺序容器

在这里插入图片描述

1.2、关联容器

在这里插入图片描述
在这里插入图片描述

1.3、容器适配器

在这里插入图片描述

1.4、拟容器

在这里插入图片描述

二、顺序容器

2.1、vector

2.1.1、vector概述

vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间运用的灵活性。array是静态空间,一旦配置了就不能改变;若要改变,则需要:首先配置一块新空间,然后将元素从旧址一一复制到新址,再把原来的空间释还给系统。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。vector的实现技术,关键在于其对空间大小的控制以及重新配置时的数据移动效率。

2.1.2、vector的数据结构

vector所采用的数据结构非常简单:线性连续空间。它以两个指针_M_start和_M_finish分别指向配置得来的连续空间中目前已被使用的范围,并以指针_M_end_of_storage指向整块连续空间(含备用空间)的尾端:

在gcc中设置了一个vector的基类:_Vector_base,在其中将数据结构设置为一个嵌套类:

  template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
    {
    ...
    }
template<typename _Tp, typename _Alloc>
  struct _Vector_base
  {
      ...
    struct _Vector_impl_data
    {
        pointer _M_start;
        pointer _M_finish;
        pointer _M_end_of_storage;
    }

    struct _Vector_impl
	: public _Tp_alloc_type, public _Vector_impl_data
      {
          ...
      }

    public:
      _Vector_impl _M_impl;
    ...
  };

2.1.3、vector的构造

vector提供了多个构造函数,其中一个允许我们指定空间大小及初值:

vector(size_type __n, const value_type &__value,
       const allocator_type &__a = allocator_type())
    : _Base(_S_check_init_len(__n, __a), __a)
{
    _M_fill_initialize(__n, __value);
}

void _M_fill_initialize(size_type __n, const value_type &__value)
{
    this->_M_impl._M_finish =
        std::__uninitialized_fill_n_a(this->_M_impl._M_start, __n, __value,
                                      _M_get_Tp_allocator());
}

template <typename _ForwardIterator, typename _Size, typename _Tp,
          typename _Tp2>
inline _ForwardIterator
__uninitialized_fill_n_a(_ForwardIterator __first, _Size __n,
                         const _Tp &__x, allocator<_Tp2> &)
{
    return std::uninitialized_fill_n(__first, __n, __x);
}

template <typename _ForwardIterator, typename _Size, typename _Tp>
inline _ForwardIterator
uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp &__x)
{
    typedef typename iterator_traits<_ForwardIterator>::value_type
        _ValueType;
#if __cplusplus < 201103L
    const bool __assignable = true;
#else
    // Trivial types can have deleted copy constructor, but the std::fill
    // optimization that uses memmove would happily "copy" them anyway.
    static_assert(is_constructible<_ValueType, const _Tp &>::value,
                  "result type must be constructible from input type");

    // Trivial types can have deleted assignment, so using std::fill
    // would be ill-formed. Require assignability before using std::fill:
    const bool __assignable = is_copy_assignable<_ValueType>::value;
#endif
    return __uninitialized_fill_n < __is_trivial(_ValueType) && __assignable > ::
    __uninit_fill_n(__first, __n, __x);
}

template <bool _TrivialValueType>
struct __uninitialized_fill_n
{
    template <typename _ForwardIterator, typename _Size, typename _Tp>
    static _ForwardIterator
    __uninit_fill_n(_ForwardIterator __first, _Size __n,
                    const _Tp &__x)
    {
        _ForwardIterator __cur = __first;
        __try
        {
            for (; __n > 0; --__n, (void)++__cur)
                std::_Construct(std::__addressof(*__cur), __x);
            return __cur;
        }
        __catch(...)
        {
            std::_Destroy(__first, __cur);
            __throw_exception_again;
        }
    }
};

template <typename _T1, typename... _Args>
inline void
_Construct(_T1 *__p, _Args &&... __args)
{
    ::new (static_cast<void *>(__p)) _T1(std::forward<_Args>(__args)...);
}

2.1.4、vector空间管理

以push_back为例:

void push_back(value_type &&__x)
{
    emplace_back(std::move(__x));
}

#if __cplusplus >= 201103L
template <typename _Tp, typename _Alloc>
template <typename... _Args>
#if __cplusplus > 201402L
typename vector<_Tp, _Alloc>::reference
#else
void
#endif
vector<_Tp, _Alloc>::
    emplace_back(_Args &&... __args)
{
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
    {
        _GLIBCXX_ASAN_ANNOTATE_GROW(1);
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 std::forward<_Args>(__args)...);
        ++this->_M_impl._M_finish;
        _GLIBCXX_ASAN_ANNOTATE_GREW(1);
    }
    else
        _M_realloc_insert(end(), std::forward<_Args>(__args)...);
#if __cplusplus > 201402L
    return back();
#endif
}
#endif

#if __cplusplus >= 201103L
  template<typename _Tp, typename _Alloc>
    template<typename... _Args>
      void
      vector<_Tp, _Alloc>::
      _M_realloc_insert(iterator __position, _Args&&... __args)
#else
  template<typename _Tp, typename _Alloc>
    void
    vector<_Tp, _Alloc>::
    _M_realloc_insert(iterator __position, const _Tp& __x)
#endif
    {
      const size_type __len =
	_M_check_len(size_type(1), "vector::_M_realloc_insert");
      pointer __old_start = this->_M_impl._M_start;
      pointer __old_finish = this->_M_impl._M_finish;
      const size_type __elems_before = __position - begin();
      pointer __new_start(this->_M_allocate(__len));
      pointer __new_finish(__new_start);
      __try
	{
	  // The order of the three operations is dictated by the C++11
	  // case, where the moves could alter a new element belonging
	  // to the existing vector.  This is an issue only for callers
	  // taking the element by lvalue ref (see last bullet of C++11
	  // [res.on.arguments]).
	  _Alloc_traits::construct(this->_M_impl,
				   __new_start + __elems_before,
#if __cplusplus >= 201103L
				   std::forward<_Args>(__args)...);
#else
				   __x);
#endif
	  __new_finish = pointer();

#if __cplusplus >= 201103L
	  if _GLIBCXX17_CONSTEXPR (_S_use_relocate())
	    {
	      __new_finish = _S_relocate(__old_start, __position.base(),
					 __new_start, _M_get_Tp_allocator());

	      ++__new_finish;

	      __new_finish = _S_relocate(__position.base(), __old_finish,
					 __new_finish, _M_get_Tp_allocator());
	    }
	  else
#endif
	    {
	      __new_finish
		= std::__uninitialized_move_if_noexcept_a
		(__old_start, __position.base(),
		 __new_start, _M_get_Tp_allocator());

	      ++__new_finish;

	      __new_finish
		= std::__uninitialized_move_if_noexcept_a
		(__position.base(), __old_finish,
		 __new_finish, _M_get_Tp_allocator());
	    }
	}
      __catch(...)
	{
	  if (!__new_finish)
	    _Alloc_traits::destroy(this->_M_impl,
				   __new_start + __elems_before);
	  else
	    std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
	  _M_deallocate(__new_start, __len);
	  __throw_exception_again;
	}
#if __cplusplus >= 201103L
      if _GLIBCXX17_CONSTEXPR (!_S_use_relocate())
#endif
	std::_Destroy(__old_start, __old_finish, _M_get_Tp_allocator());
      _GLIBCXX_ASAN_ANNOTATE_REINIT;
      _M_deallocate(__old_start,
		    this->_M_impl._M_end_of_storage - __old_start);
      this->_M_impl._M_start = __new_start;
      this->_M_impl._M_finish = __new_finish;
      this->_M_impl._M_end_of_storage = __new_start + __len;
    }


size_type
_M_check_len(size_type __n, const char *__s) const
{
    if (max_size() - size() < __n)
        __throw_length_error(__N(__s));

    const size_type __len = size() + (std::max)(size(), __n);
    return (__len < size() || __len > max_size()) ? max_size() : __len;
}

vector的动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大空间,将原内容拷贝过来,然后才开始在原内容之后构造新元素,当添加新元素成功后,再释放原空间。

2.2、list

2.2.1、list概述

相较于vector的连续线性空间,list就显得复杂许多,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素删除,list永远是常数时间。

2.2.2、list的数据结构

list被设计为一个双向循环链表,所以它只需要一个指针,便可以完整表现整个链表。

struct _List_node_base
{
  _List_node_base *_M_next;
  _List_node_base *_M_prev;
  ...
};

struct _List_node_header : public _List_node_base
{
#if _GLIBCXX_USE_CXX11_ABI
  std::size_t _M_size;
#endif

  _List_node_header() _GLIBCXX_NOEXCEPT
  {
    _M_init();
  }

  void
  _M_init() _GLIBCXX_NOEXCEPT
  {
    this->_M_next = this->_M_prev = this;
#if _GLIBCXX_USE_CXX11_ABI
    this->_M_size = 0;
#endif
  }

  ...
};

template <typename _Tp>
struct _List_node : public __detail::_List_node_base
{
  ...
};

template <typename _Tp, typename _Alloc>
class _List_base
{
  struct _List_impl
      : public _Node_alloc_type
  {
    __detail::_List_node_header _M_node;
    ...
  };
  ...
};

template <typename _Tp, typename _Alloc = std::allocator<_Tp>>
class list : protected _List_base<_Tp, _Alloc>
{
protected:
  typedef _List_node<_Tp> _Node;
  ...
};

如果设置一个指针刻意指向尾端的一个空白节点,那么该指针便能符合STL对于"前闭后开"区间的要求。

iterator
begin() _GLIBCXX_NOEXCEPT
{
  return iterator(this->_M_impl._M_node._M_next);
}

iterator
end() _GLIBCXX_NOEXCEPT
{
  return iterator(&this->_M_impl._M_node);
}

2.2.3、list空间管理

以push_back为例:

void push_back(value_type &&__x)
{
  this->_M_insert(end(), std::move(__x));
}

template <typename... _Args>
void _M_insert(iterator __position, _Args &&... __args)
{
  _Node *__tmp = _M_create_node(std::forward<_Args>(__args)...);
  __tmp->_M_hook(__position._M_node);
  this->_M_inc_size(1);
}

template <typename... _Args>
_Node *_M_create_node(_Args &&... __args)
{
  auto __p = this->_M_get_node();
  auto &__alloc = _M_get_Node_allocator();
  __allocated_ptr<_Node_alloc_type> __guard{__alloc, __p};
  _Node_alloc_traits::construct(__alloc, __p->_M_valptr(),
                                std::forward<_Args>(__args)...);
  __guard = nullptr;
  return __p;
}

void _List_node_base::
    _M_hook(_List_node_base *const __position) _GLIBCXX_USE_NOEXCEPT
{
  this->_M_next = __position;
  this->_M_prev = __position->_M_prev;
  __position->_M_prev->_M_next = this;
  __position->_M_prev = this;
}

2.3、 deque

2.3.1、deque概述

deque是一种双向开口的连续线性空间,即可以在头尾两端分别做元素的插入和删除操作。

deque和vector的最大差异,一在于deque允许于常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。

2.3.2、deque的迭代器

deque是分段连续空间,维持其“整体连续”假象的任务,落在了迭代器的operator++和operator–两个运算符身上。

作为deque的迭代器,首先,它必须能够指出分段连续空间(亦即缓冲区)在哪里,其次它必须能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或后退就必须跳跃至下一个或上一个缓冲区。为了能够正确跳跃,deque必须随时掌握管控中心(map)。

template <typename _Tp, typename _Ref, typename _Ptr>
struct _Deque_iterator
{
  ...
  typedef __ptr_to<_Tp> _Elt_pointer;
  typedef __ptr_to<_Elt_pointer> _Map_pointer;
  typedef _Deque_iterator _Self;

  _Elt_pointer _M_cur;//此迭代器所指缓冲区中的当前元素
  _Elt_pointer _M_first;//此迭代器所指缓冲区的头
  _Elt_pointer _M_last;//此迭代器所指缓冲区的尾
  _Map_pointer _M_node;//指向管控中心

  _Self &
  operator++() _GLIBCXX_NOEXCEPT
  {
    ++_M_cur;
    if (_M_cur == _M_last)
    {
      _M_set_node(_M_node + 1);
      _M_cur = _M_first;
    }
    return *this;
  }

  _Self &
  operator--() _GLIBCXX_NOEXCEPT
  {
    if (_M_cur == _M_first)
    {
      _M_set_node(_M_node - 1);
      _M_cur = _M_last;
    }
    --_M_cur;
    return *this;
  }

  /**
       *  Prepares to traverse new_node.  Sets everything except
       *  _M_cur, which should therefore be set by the caller
       *  immediately afterwards, based on _M_first and _M_last.
       */
  void
  _M_set_node(_Map_pointer __new_node) _GLIBCXX_NOEXCEPT
  {
    _M_node = __new_node;
    _M_first = *__new_node;
    _M_last = _M_first + difference_type(_S_buffer_size());
  }
  ...
};

2.3.3、deque的数据结构

deque是连续空间(至少逻辑上看来如此),其实deque是由一段一段的定量连续空间构成。一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。deque的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取的接口。避开了“重新配置、复制、释放”的轮回,代价则是复杂的迭代器架构。

由于deque是分段连续的,因此deque采用一块所谓的map(注意,不是STL的map容器)作为主控,其实质为一个索引。这里的map是一小块连续空间,其中每个元素都是指针,指向另一段连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。

deque除了维护一个指向map的指针外,也维护_M_start、_M_finish两个迭代器,分别指向第一个缓冲区的第一个元素和最后一个缓冲区的最后一个元素(的下一个位置)。此外,它还必须记住目前的map大小。因为一旦map所提供的节点不足,就必须重新配置更大的一块map。

template <typename _Tp, typename _Alloc>
class _Deque_base
{
  ... struct _Deque_impl
      : public _Tp_alloc_type
  {
    _Map_pointer _M_map;//指向map
    size_t _M_map_size;//map内有多少指针
    iterator _M_start;//指向第一个元素
    iterator _M_finish;//指向最后一个元素的下一个位置
  };
  enum
  {
    _S_initial_map_size = 8
  };

  _Deque_impl _M_impl;
};

2.3.4、deque的构造

deque提供多个构造函数,其中一个允许指定deque的大小和初始值

template <typename _Tp, typename _Alloc = std::allocator<_Tp>>
class deque : protected _Deque_base<_Tp, _Alloc>
{
  deque(size_type __n, const value_type &__value,
        const allocator_type &__a = allocator_type())
      : _Base(__a, _S_check_init_len(__n, __a))
  {
    _M_fill_initialize(__value);
  }
  ...
};

template <typename _Tp, typename _Alloc>
class _Deque_base
{
  _Deque_base(const allocator_type &__a, size_t __num_elements)
      : _M_impl(__a)
  {
    _M_initialize_map(__num_elements);
  }

  struct _Deque_impl : public _Tp_alloc_type
  {
    _Deque_impl(const _Tp_alloc_type &__a) _GLIBCXX_NOEXCEPT
        : _Tp_alloc_type(__a), _M_map(), _M_map_size(0),
          _M_start(),_M_finish()
    {}
    ...
  };

  void _M_initialize_map(size_t __num_elements)
  {
    //需要的节点数=(元素个数/每个缓冲区可容纳的元素个数) + 1
    //如果刚好整除,会多配置一个节点
    const size_t __num_nodes = (__num_elements / __deque_buf_size(sizeof(_Tp)) + 1);

    //一个map要管理几个节点,最少为8个,最多是“所需节点数加2”
    //前后各预留一个,以便扩充时用
    this->_M_impl._M_map_size = std::max((size_t)_S_initial_map_size,
                                         size_t(__num_nodes + 2));
    this->_M_impl._M_map = _M_allocate_map(this->_M_impl._M_map_size);

    // For "small" maps (needing less than _M_map_size nodes), allocation
    // starts in the middle elements and grows outwards.  So nstart may be
    // the beginning of _M_map, but for small maps it may be as far in as
    // _M_map+3.
    //以下令__nstart和__nfinish指向map所拥有的全部节点的最中央区段
    //保持在最中央,可使头尾两端的扩充量一样大,每个节点可对应一个缓冲区
    _Map_pointer __nstart = (this->_M_impl._M_map
                             + (this->_M_impl._M_map_size - __num_nodes) / 2);
    _Map_pointer __nfinish = __nstart + __num_nodes;

    __try
    {
      _M_create_nodes(__nstart, __nfinish);
    }
    __catch(...)
    {
      _M_deallocate_map(this->_M_impl._M_map, this->_M_impl._M_map_size);
      this->_M_impl._M_map = _Map_pointer();
      this->_M_impl._M_map_size = 0;
      __throw_exception_again;
    }

    this->_M_impl._M_start._M_set_node(__nstart);
    this->_M_impl._M_finish._M_set_node(__nfinish - 1);
    this->_M_impl._M_start._M_cur = _M_impl._M_start._M_first;
    this->_M_impl._M_finish._M_cur = (this->_M_impl._M_finish._M_first
                      + __num_elements % __deque_buf_size(sizeof(_Tp)));
  }
};

2.3.5、deque的内存管理

以push_back为例,push_front与之类似

void push_back(value_type &&__x)
{
  emplace_back(std::move(__x));
}

void emplace_back(_Args &&... __args)
{
  if (this->_M_impl._M_finish._M_cur != this->_M_impl._M_finish._M_last - 1)
  {
    _Alloc_traits::construct(this->_M_impl,
                             this->_M_impl._M_finish._M_cur,
                             std::forward<_Args>(__args)...);
    ++this->_M_impl._M_finish._M_cur;
  }
  else
    _M_push_back_aux(std::forward<_Args>(__args)...);
#if __cplusplus > 201402L
  return back();
#endif
}

template <typename _Tp, typename _Alloc>
#if __cplusplus >= 201103L
template <typename... _Args>
void deque<_Tp, _Alloc>::
    _M_push_back_aux(_Args &&... __args)
#else
void deque<_Tp, _Alloc>::
    _M_push_back_aux(const value_type &__t)
#endif
{
  if (size() == max_size())
    __throw_length_error(
        __N("cannot create std::deque larger than max_size()"));

  _M_reserve_map_at_back();//判断是否需要重新配置map
  *(this->_M_impl._M_finish._M_node + 1) = this->_M_allocate_node();
  __try
  {
#if __cplusplus >= 201103L
    _Alloc_traits::construct(this->_M_impl,
                             this->_M_impl._M_finish._M_cur,
                             std::forward<_Args>(__args)...);
#else
    this->_M_impl.construct(this->_M_impl._M_finish._M_cur, __t);
#endif
    this->_M_impl._M_finish._M_set_node(this->_M_impl._M_finish._M_node + 1);
    this->_M_impl._M_finish._M_cur = this->_M_impl._M_finish._M_first;
  }
  __catch(...)
  {
    _M_deallocate_node(*(this->_M_impl._M_finish._M_node + 1));
    __throw_exception_again;
  }
}

template <typename _Tp, typename _Alloc>
void deque<_Tp, _Alloc>::
    _M_reallocate_map(size_type __nodes_to_add, bool __add_at_front)
{
  const size_type __old_num_nodes = this->_M_impl._M_finish._M_node
                                  - this->_M_impl._M_start._M_node + 1;
  const size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;

  _Map_pointer __new_nstart;
  if (this->_M_impl._M_map_size > 2 * __new_num_nodes)
  {
    __new_nstart = this->_M_impl._M_map
                 + (this->_M_impl._M_map_size - __new_num_nodes) / 2
                 + (__add_at_front ? __nodes_to_add : 0);
    if (__new_nstart < this->_M_impl._M_start._M_node)
      std::copy(this->_M_impl._M_start._M_node,
                this->_M_impl._M_finish._M_node + 1,
                __new_nstart);
    else
      std::copy_backward(this->_M_impl._M_start._M_node,
                         this->_M_impl._M_finish._M_node + 1,
                         __new_nstart + __old_num_nodes);
  }
  else
  {
    size_type __new_map_size = this->_M_impl._M_map_size
            + std::max(this->_M_impl._M_map_size, __nodes_to_add) + 2;

    _Map_pointer __new_map = this->_M_allocate_map(__new_map_size);
    __new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2
                  + (__add_at_front ? __nodes_to_add : 0);
    std::copy(this->_M_impl._M_start._M_node,
              this->_M_impl._M_finish._M_node + 1,
              __new_nstart);
    _M_deallocate_map(this->_M_impl._M_map, this->_M_impl._M_map_size);

    this->_M_impl._M_map = __new_map;
    this->_M_impl._M_map_size = __new_map_size;
  }

  this->_M_impl._M_start._M_set_node(__new_nstart);
  this->_M_impl._M_finish._M_set_node(__new_nstart + __old_num_nodes - 1);
}

2.4、stack

stack是符合“先进后出”的数据结构。deque是双向开口的数据结构,因此以deque作为底部结构,将其接口改变,很容y在其上实现“先进后出”。STL默认以deque作为stack的底部结构,stack的操作通过调用deque的接口实现。

template <typename _Tp, typename _Sequence = deque<_Tp>>
class stack
{
  _Sequence c;

  empty() const
  {
    return c.empty();
  }

  size_type size() const
  {
    return c.size();
  }

  reference top()
  {
    __glibcxx_requires_nonempty();
    return c.back();
  }

  void push(value_type &&__x)
  {
    c.push_back(std::move(__x));
  }

  void pop()
  {
    __glibcxx_requires_nonempty();
    c.pop_back();
  }
  ...
};

2.5、queue

stack是符合“先进先出”的数据结构。deque是双向开口的数据结构,因此以deque作为底部结构,将其接口改变,很容器在其上实现“先进先出”。STL默认以deque作为queue的底部结构,queue的操作通过调用deque的接口实现。

template <typename _Tp, typename _Sequence = deque<_Tp>>
class queue
{
  _Sequence c;

  size_type size() const
  {
    return c.size();
  }

  reference front()
  {
    __glibcxx_requires_nonempty();
    return c.front();
  }

  reference back()
  {
    __glibcxx_requires_nonempty();
    return c.back();
  }

  void push(value_type &&__x)
  {
    c.push_back(std::move(__x));
  }

  void pop()
  {
    __glibcxx_requires_nonempty();
    c.pop_front();
  }
  ...
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值