STL 源码解析——vector

72 阅读1分钟

作为STL中的顺序类容器(vector,deque,list,forward_list,array)之一,通常被当作是c++的数组来使用,但其要比c的数组灵活的多,空间不够时能够自动扩展,并且占用的是堆内存。

c++20之后 vector中的 push_back 方法通过调用 emplace_back 方法实现。建议使用emplace_back来代替push_back,省去了构造一个临时变量再赋值的操作,而是直接在数组尾部进行构造新对象。

1、vector 迭代器

typedef _Tp value_type; typedef typename _Base::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 __gnu_cxx::__normal_iterator<pointer, vector> iterator; 
typedef __gnu_cxx::__normal_iterator<const_pointer, vector> const_iterator; 
typedef std::reverse_iterator<const_iterator> const_reverse_iterator; 
typedef std::reverse_iterator<iterator> reverse_iterator;

可见vector 的迭代器基本就是指针

2、vector 数据结构 vector是连续空间,有三个指针指向了不同位置

struct _Vector_impl_data {
    pointer _M_start; //指向使用空间的头 
    pointer _M_finish; //指向使用空间的尾 
    pointer _M_end_of_storage; //指向剩余空间的尾 
};

这三个指针分别可以对vector 的头、尾元素、容器体积进行试算

3、vector 元素操作 pop_back

//pop元素
void  pop_back()
{
  __glibcxx_requires_nonempty();
  --this->_M_impl._M_finish;
  _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
  _GLIBCXX_ASAN_ANNOTATE_SHRINK(1);
}

弹出尾部元素,并调整容器大小

emplace

iterator emplace(const_iterator __position, _Args&&... __args)
{ return _M_emplace_aux(__position, std::forward<_Args>(__args)...); }

新增加的插入元素的方法,第一个参数传入迭代器,第二个参数传入需要插入的新值,新值会传入迭代器对应元素前面。

insert 在指定位置插入已存在的对象,当对象已经存在或需要需要同时插入多个对象时会使用。适合简单的不追求性能的场景。

iterator insert(const_iterator __position, value_type&& __x)
{ return _M_insert_rval(__position, std::move(__x)); }

4、vector 的API实现

默认构造函数: vector arr; 这种情况下vector大小为零,为了节约内存空间,vector 不会主动申请内存、创建缓冲区

构造函数: 注意vector arr(100,0);和vector arr(100);两者在size和capicity没有区别,都会输出size:100,capacity:100

拷贝构造函数: 先申请目标对象同等大小的内存,再将目标容器内的所有值拷贝过去

vector(const vector<_Tp, _Alloc>& __x) 
    : _Base(__x.size(), __x.get_allocator())
    { _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); }

从迭代器构造 vector:

// Check whether it's an integral type.  If so, it's not an iterator.
template <class _InputIterator>
// 通用构造函数,接受两个迭代器
vector(_InputIterator __first, _InputIterator __last,
     const allocator_type& __a = allocator_type()) : _Base(__a) {
// 判断模版参数是否是整数类型
typedef typename _Is_integer<_InputIterator>::_Integral _Integral;
// 根据是否是整数调用不同的辅助函数
_M_initialize_aux(__first, __last, _Integral());
}
// 当参数是整数类型时  __true_type
template <class _Integer>
void _M_initialize_aux(_Integer __n, _Integer __value, __true_type) {
_M_start = _M_allocate(__n);  // 分配n个空间
_M_end_of_storage = _M_start + __n; 
_M_finish = uninitialized_fill_n(_M_start, __n, __value);  // 用 __value填充
}

比如

vector<int> a(100, 0);
vector<int> b(a.begin(), a.begin() + 10);

a是调用整型构造 而b是调用迭代器构造

析构函数: 当生命周期结束,对象即将被回收时,首先调用vector::~vector 然后调用_Vector_base::~_Vector_base释放已申请的内存

其他知识点: begin: 返回左边界迭代器

end:返回右虚边界迭代器

rbegin:返回右虚边界的反向迭代器

rend:返回数据左边界迭代器

size:返回end()-begin()

max_size:返回size_type类型的最大数

capacity:返回_M_end_of_storage-begin() 即缓冲区能够容纳的元素个数

swap:调换两个vector中的内容,只需要交换vector内部数据结构中的三个指针即可

insert:在任意位置插入任意数量个元素

pop_back:销毁尾部元素

erase:销毁指定区间内容

resize:两种情况,新size大于当前size,则插入new_size-old_size个0值;new_size小于old_size时,则对old_size-new_size个元素执行erase操作,但resize不会释放缓冲区上的可用内存

reserve:申请新内存

operator=:重构后的赋值运算符

三种情况:

b.size > a.capacity 时, a先执行_M_allocate_and_copy 申请并初始化新缓冲区,再执行_M_deallocate释放旧缓冲区

b.size < a.size 直接复制b到a, 并销毁a中多余对象

a.size < b.size < a.capacity 先复制b中前a.size个元素到a中,然后执行unitialiized_copy复制b中后b.size-a.size个元素到a中

assign(n, val) 实现也分三种情况 n > a.capacity,  构造新vector(假设为tmp), 并执行a.swap(tmp)

a.size < n < a.capacity, 同operator=

n < a.size, 同operator=