STL之顺序容器

C++STL详解四:顺序容器 分析顺序容器array、vector、deque、list和heap的底层行为,简单实现vector 和 deque::iterator 阅读详情


一、容器简介

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

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

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();
  }
  ...
};
STL—— 3种顺序容器的基本功能和特性 1.顺序容器的基本功能          STL中的顺序容器包括向量(vector)、双端队列(deque)和列表(list) (1)构造函数 S s(n,t); //构造一个由n个t元素构成的容器实例s。 S s(n); //构造一个有n个元素的容器实例s,每个元素都是T()。 S s(q1,q2);//使用将[q1,q2)区间内的数据作为s的元素构造s。 (2)赋值函数 s. 阅读详情

相关推荐

C++ | STL之顺序容器、关联容器、容器适配器

文章目录STL简述STL六大组件顺序容器(序列式容器)vectordequelist关联容器容器适配器 STL简述 STL是Standard Template Library的简称,中文名标准模板库,从根本上说,STL是一些“容器”的集合,这些“容器”有list、vector、set、map等,STL也是算法和其他一些组件的集合。STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。...

ZY-JIMMY 2377

《STL源码分析》学习笔记 — 空间配置器 — 内存基本处理工具

《STL源码分析》学习笔记 — 空间配置器 — 内存基本处理工具一、uninitialized_copy二、uninitialized_fill三、uninitialized_fill_n四、stl_uninitialized.h 中的扩展内部函数五、stl_uninitialized.h 中的扩展外部函数 stl_uninitialized.h 文件中提供了用于处理未初始化内存的几个低层次函数:uninitialized_copy、uninitialized_fill 和 uninitialized_fi

coding-hwz 1167

3D打印切片软件Cura及CuraEngine原理分析

引言         年初开始进入3D打印行业,受命以Cura为基础,研发一款自主的3D打印切片软件。         自主研发要取其长处,补其不足,首先自然是要搞清楚Cura到底做了什么,读Cura的代码是必需的。我一向都觉得比起自己写代码来,读别人的代码是一个漫又而痛苦的过程,读者的思想与写者总有偏差,往往又无法验证自己的猜想是否正确,只叹人脑不是电脑,无法把眼前的代码从头到尾执行一遍...

AiDiShengDeXiaoWu的博客 4421

linux Clion cmake出现错误 undefined reference to `std::cout‘

Clion cmake出现错误 undefined reference to `std::cout'

m0_37651658的博客 4323

深入理解C++23的Deducing this特性(上):基础概念与语法详解

C++23标准中引入的"Deducing this"特性是一项重大革新,它不仅改变了我们编写成员函数的方式,还为模板元编程和设计模式实现带来了新的可能。

weixin_61470881的博客 1333

GAMES101:MacOS作业环境搭建

查看gcc/clang的位置产看gcc/clang的版本1.1.2 安装cmake。

weixin_38844255的博客 716

Docker 虚拟化容器

Docker简介 开源的应用容器引擎,基于 Go 语言 遵从 Apache2.0 协议开源。 打包应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 上,也可实现虚拟化。 容器完全使用沙箱机制,相互之间没有任何接口,容器性能开销极低。 Docker应用场景 1,Web 应用的自动化打包和发布。 2,自动化测试和持续集成、发布。 3,在服务型环境中部署和调整数据库或其他的后台应用。 4,从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 Pa

龙卷风摧毁停车场 6122

工程创新训练大赛智能网联车辆赛道LKA+自动驾驶9000+分数分案例

/获取主车在主车路径上的ST坐标。if (status == AVPStatus::eForwarding) // approaching the parking space接近阶段。if (status == AVPStatus::eForwarding) // approaching the parking space接近阶段。if (status == AVPStatus::eForwarding) // approaching the parking space接近阶段。

handsomeboyou的博客 1132

C++&Qt经验总结【三】

本期经验分享主要包括:c++模板匹配策略,forward完美转发,深入理解流对象,vector::emplace_back的性能分析,Qt事件系统之事件过滤器

学艺不精的Anton的博客 501

DRM驱动代码分析:展频 跳频

DRM驱动代码分析:展频 跳频

yuuu_cheer的博客 2679

深入解析LVDS电平标准在LCD屏接口设计中的关键作用

本文深入解析了LVDS电平标准在LCD屏接口设计中的核心作用。文章不仅阐释了LVDS低电压差分信号的工作原理、关键电气参数及抗噪优势,还详细对比了VESA与JEIDA两种主流数据格式,并提供了从PCB布局布线到信号调试的实战避坑指南,是确保高速、稳定显示的关键技术参考。

weixin_28419039的博客 334

C++ | STL标准库之顺序容器

前言 STL六大组件中的容器按照结构特征可分为顺序容器(链状)、关联式容器(树状)和无序式容器。本文就其中顺序容器进行总结和介绍。 一、顺序容器概述 标准库顺序容器有:vector、deque、list、forward_list、arry、string。六个顺序容器中,只有arry是固定大小。 vector: string和vector将元素保存在连续的内存空间中,因此由下标计算其地址很方便。但因为其连续的特性,在其中间位置添加或删除元素非常耗时,这可能会移动变动位置之后的所有元素。 注: vect

weixin_44742670的博客 1740

微信小程序 - 解决微信支付报错 requestPayment:fail no permission 原因分析及排查处理(支付失败出错,无法唤起支付弹框面板,微信支付API报错请求付款:失败无权限)

微信小程序,微信支付,支付报错,errMsg: "requestPayment:fail no permission",支付失败,支付权限没问题,微信小程序支付报错,检查了appid正确依旧不行,微信小程序支付异常,苹果ios手机,安卓手机,开发者工具模拟器,最新解决教程,小程序wx.requestPayment报错/无响应,小程序支付 requestPaym,微信小程序认证,真机运行,小程序微信支付提示错误,微信小程序里使用微信支付错误提示,微信小程序支付异常,微信支付弹框拉不起来,小程序支付api,都检

🏆 前端领域优质创作者 1万+

C++之STL顺序容器

STL容器是一个通用的数据结构,可以处理不同数据类型,包含基本的数据结构如链表、堆栈、队列等。可以分为顺序容器、关联容器、 容器适配器、特殊容器。本篇博客将简要介绍一下STL容器中的顺序容器。

m0_74265792的博客 1283

matlab 点云可视化(1)——pcshow()函数可视化单个点云

matlab可视化单个点云常用方法汇总。

点云侠的博客 5359

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

C++ STL顺序容器 保姆级详解

在C++的STL中,顺序容器是按顺序存储数据的容器类,用于存储和操作数据集合。顺序容器主要包括 vector、deque、list 和 forward_list,它们各自有不同的实现方式和应用场景。下面详细介绍每种顺序容器的特点、常用操作及其应用场景。

weixin_69480212的博客 1949

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

stl容器之顺序容器

STL容器分为顺序容器和关联容器 其中顺序容器最常用的由vector,list,queue 1.vector vector: 向量容器,可以看作变长数组,长度可根据需要自行变化。 使用的头文件 #include<vector> 定义方式: vector<typename> 数组名;//vector<int> ar; 访问容器内数据的方式和普通数组相同...

suyy_hui的博客 821

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

c++:STL之顺序容器,关联容器,顺序容器适配器

1.什么是STL STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件...

Kobe51920的博客 2243

【STL】——顺序容器

1、引言 2、vector(数组) 2.1基本操作 2.2扩容机制 2.3对容器进行连续插入或者删除操作时迭代器失效问题 2.4特点 3、list(双向循环链表) 3.1基本操作 3.2 特点 4、deque(双端队列容器) 4.1基本操作 4.2扩容机制 4.3特点 5、区别 5.1vector和list之间的区别 5.2vector和deque之间的区别

qq_43412060的博客 512

C++ STL中关于顺序容器的总结

顺序容器概述 C++的STL库中提供了很多的容器类型,一个容器类型就是一个类对象,方便开发者对数据进行操作,如增删查等操作,这些操作不依赖容器中的数据的类型,而是更具数据在容器中的位置以及容器的结构。 容器 功能 vertor 可改变数组的大小,支持快速查找,下标查找,但是对于插入和删除操作,需要系统会移动所有元素,效率会比较慢 deque 双端队列,支持快速访问,在容器的头和尾插入数据会很快,但是在除了头和尾插入或者删除数据和数组一样需要移动元素,效率较低。 list

马小超 420

STL 顺序容器,关联容器

向量 vector :   是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。 在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定,这个大小即capacity ()函数的返回值。当存储的数据超过分配的空间时v

gavin0123的专栏 2948

STL:顺序容器

vector、list、stack、queue的操作,以及一些延伸的说明。

上总介的博客 936

C++STL顺序容器通用操作

总结了常用的STL几种顺序容器的用法,包括vector, deque, list, array, string等。

weixin_50431732的博客 495

C++ STL容器总结(一)——顺序性容器

C++ STL总结(一)顺序性容器概述通用容器操作构造函数赋值与swap大小添加/删除元素(不适用于array)获取迭代器反向容器的额外成员(不支持forward_list)vector特性常用函数dequeliststring 通用容器的分类 STL 对定义的通用容器分三类:顺序性容器,关联式容器和容器适配器。 顺序性容器:是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器...

Leokb24的博客 1238

C++ 标准模板库 STL 顺序容器

C++ 标准模板库 STL 顺序容器 容器 数据结构 顺序性 重复性 支持迭代器 vector 动态数组 无序 可重复 随机访问迭代器 deque 双向队列 无序 可重复 随机访问迭代器 list 双向链表 无序 可重复 双向迭代器 动态数组 vector ​ vector #include <vector> 动态数组:其元素在内存中是连续存放的,随机存取任何元素都可以在常数时间内完成,在该容器的尾部增删元素也几乎能够在常数时间内完成具有较好的性能。 ​ 一个 vec

王清欢的博客 6896
上一篇: STL之迭代器(Iterator)
下一篇: STL之红黑树
Spring_24
博客等级 码龄12年 46粉丝 41原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Spring_24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值