vector vs array
在SGI STL中,vector和array都是数组容器,两种操作非常相似。区别在于:array是静态空间,一旦配置就不能改变;vector是动态空间,随着新元素加入,内部机制或自行扩充空间以容纳新元素。
vector的迭代器
vector维护的是一个连续线性空间,不论元素类型是什么,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector需要的操作:operator*,operator->,operatro++, operator--, operator+, operator-, operator+=, operator-=。而普通指针天生就具备这些操作。vector支持随机存取(operator[]),而普通指针也有这样的能力。因此,vector提供的迭代器是Random Access Iterators。
// vector迭代器关联类型
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
public:
// 定义vector的迭代器关联类型, 可用于iterator_traits萃取特性, 兼容STL架构
typedef _Tp value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* iterator; // vector的迭代器是普通指针
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_reference;
...
};
也就是说,如果客户端写出这样的代码:
vector<int>::iterator ivite;
vector<Shape>::iterator svite;
ivite的类型其实是int,svite的类型是Shape。
vector的数据结构
vector采用的数据结构就是一个线性连续空间。它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。
vector通过基类_Vector_base的三个迭代器start、finish、end_of_storage,提供线性空间的首尾表示、大小、容量、空容器判断、随机访问运算符operator[]、最前端元素值、最后端元素值等功能。
vector主要通过下面这部分,对外提供接口访问内部数据结构存储的元素:
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
public:
iterator begin() { return _M_start; } // 线性空间起始位置对应迭代器
const_iterator begin() const { return _M_start; } // 线性空间起始位置对应const迭代器
iterator end() { return _M_finish; } // 线性空间末尾位置对应迭代器
const_iterator end() const { return _M_finish; } // 线性空间末尾位置对应const迭代器
reverse_iterator rbegin()
{ return reverse_iterator(end()); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(end()); }
reverse_iterator rend()
{ return reverse_iterator(begin()); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(begin()); }
size_type size() const // 当前元素个数
{ return size_type(end() - begin()); }
size_type max_size() const // 理论上最大能支持的元素个数
{ return size_type(-1) / sizeof(_Tp); }
size_type capacity() const // 当前容量, 即线性空间最大能存放的元素个数
{ return size_type(_M_end_of_storage - begin()); }
bool empty() const // 判断是否包含元素
{ return begin() == end(); }
reference operator[](size_type __n) { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素reference
const_reference operator[](size_type __n) const { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素const reference
reference front() { return *begin(); } // 第一个元素reference
const_reference front() const { return *begin(); } // 第一个元素对应const reference
reference back() { return *(end() - 1); } // 最后一个元素reference
const_reference back() const { return *(end() - 1); } // 最后一个元素对应const reference
...
};
vector对应存储结构示意图:
size()是指当前实际存储元素的个数,capacity()是指底层用于存储的线性空间总大小,即容量。有size() <= capacity()成了。
以vector
vector<int> iv(2,9);
iv.push_back(1);
iv.push_back(2);
iv.push_back(3);
iv.push_back(4);
往vector 调用push_back()插入元素空间变化示意图:
当容量不足时,如果继续插入元素,会导致vector扩容,扩容机制是这样的:
1)当前容量为0,扩容后为1;
2)当然容量>0,扩容为原来的2倍。
擦除元素时,会导致finish指针移动,改变size()大小,但并不会导致end_of_storage指针变化,也就是不会导致容量变化。如果想要改变容量以节省空间,需要调用vector::shrink_to_fit(),减少容量以适应size()大小。不过shrink_to_fit()是C++11加入的内容,老版的SGI STL并没有该功能。
简而言之,老版SGI STL无法缩减vector容量。
vector的构造与内存管理
vector的空间配置与释放
vector的空间配置是通过基类_Vector_base完成的,所有细节都封装在基类内部。这样,vector就无需关心空间如何配置、释放的细节。
vector基类_Vector_base:
// vector基类, 负责维护vector底层数据结构的空间配置
template <class _Tp, class _Alloc>
class _Vector_base {
public:
typedef _Alloc allocator_type;
allocator_type get_allocator() const { return allocator_type(); } // 构造一个临时allocator_type(空间配置器)对象
_Vector_base(const _Alloc&)
: _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
_Vector_base(size_t __n, const _Alloc&)
: _M_start(0), _M_finish(0), _M_end_of_storage(0)
{
_M_start = _M_allocate(__n); // 初始配置n byte空间
_M_finish = _M_start; // 初始finish与start在同一位置
_M_end_of_storage = _M_start + __n; // 初始end_of_storage位置
}
~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); } // 释放整个线性空间
protected:
_Tp* _M_start; // 线性内存起始位置
_Tp* _M_finish; // 线性内存使用的结束位置
_Tp* _M_end_of_storage; // 线性内存的终点位置
typedef simple_alloc<_Tp, _Alloc> _M_data_allocator; // 一级空间配置/二级空间配置器的包装器
_Tp* _M_allocate(size_t __n) // 为子类提供配置器的allocate()接口, 配置n byte空间
{ return _M_data_allocator::allocate(__n); }
void _M_deallocate(_Tp* __p, size_t __n) // 为子类提供配置器的deallocate()接口, 释放起始地址为p的n byte空间
{ _M_data_allocator::deallocate(__p, __n); }
};
关于simple_alloc有一点需要注意:在vector中,所有内存操作都是以元素个数为单位,但对于一级/二级空间配置器,内存都是以byte为单位,这其中,就是simple_alloc做了转换处理,将元素个数对应内存,转换成了byte为单位的内存。
vector的构造
站在客户端的角度,构造vector的方法:
vector<int> ivec; // vector内容为空
vector<int> ivec2(10); // size()为10, 内容0
vector<int> ivec3(20, 5); // 20个元素值为5
vector<int> ivec4(ivec3); // 20个元素值为5
vector<int> ivec5({1,2,3}); // 3个元素, {1,2,3}. C++11内容, 初值列表初始化vector, 这里不列出
vector<int> ivec6(ivec3.begin(), ivec3.end()); // 用迭代区间构造vector, 内容为20个元素值5
vector的默认分配子是alloc(二级空间配置器),使用父类_Vector_base来屏蔽空间配置细节,方便以元素大小为配置单位。
// vector 构造函数
// 构造空vector, 对应客户端vector<int> ivec
explicit vector(const allocator_type& __a = allocator_type())
: _Base(__a) {}
// 构造大小为n byte, 所有值为value的vector, 对应客户端vector<int> ivec3(20, 5)
vector(size_type __n, const _Tp& __value,
const allocator_type& __a = allocator_type())
: _Base(__n, __a)
{ _M_finish = uninitialized_fill_n(_M_start, __n, __value); } // 用value填充未初始化内存区段(start, n)
// 构造大小为n byte的vector, 对应客户端vector<int> ivec2(10)
explicit vector(size_type __n)
: _Base(__n, allocator_type())
{ _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); } // 这里Tp是int, 用int()(默认值0)填充未初始化内存区段(start, n)
// 拷贝构造的vector, 对应客户端vector<int> ivec4(ivec3)
vector(const vector<_Tp, _Alloc>& __x)
: _Base(__x.size(), __x.get_allocator())
{ _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); } // 将x对应的源迭代区间所有元素拷贝到未初始化内存段
// 用迭代区间[first, last)构造vector
vector(const _Tp* __first, const _Tp* __last,
const allocator_type& __a = allocator_type())
: _Base(__last - __first, __a)
{ _M_finish = uninitialized_copy(__first, __last, _M_start); } // 将源迭代区间所有内容拷贝到start起始的目的区间
vector的析构
vector的析构函数很简单,调用全局destroy() 释放线性内存空间。
// 释放迭代器区间[first, last), 如果迭代器指向基本类型, 什么也不做; 如果迭代器指向class 类型, 就先逐个析构
~vector() { destroy(_M_start, _M_finish); }
// 注意vector析构之后, 会接着析构基类. 因此即使vector什么也没做, 基类会回收线性内存空间
// 析构基类会先回收内存段(start, end_of_storage - start)
// 空间配置那一章提到过, 如果是二级配置器, 内存>128byte, 会直接返还给OS; 如果内存<=128byte, 会加入到某个合适的free list, 留作备用
~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }
指定数组初始大小reserve(), resize()
有没有办法让vector一开始就保留指定大小的线性空间,而不是慢慢动态增长到?
答案是有的,可以用vector::reserve()。
// 让vector容量 >= n 个元素
void reserve(size_type __n) {
if (capacity() < __n) { // 只有当前容量 < n时, 才需要重新配置空间
const size_type __old_size = size(); // 已经装了元素个数
iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish); // 配置n个元素新空间, 并将[start, finish)元素拷贝到新空间
destroy(_M_start, _M_finish); // 调用全局destroy()销毁[start, finish)上的元素. 对于基本类型, 什么也不做; 对于class类型, 析构对象
_M_deallocate(_M_start, _M_end_of_storage - _M_start); // 调用基类的deallocate() 释放线性空间
// 重新配置start, finish, end_of_storage 管理线性空间
_M_start = __tmp;
_M_finish = __tmp + __old_size;
_M_end_of_storage = _M_start + __n;
}
}
// 配置n个元素新空间, 并将[start, finish)元素拷贝到新空间
// 遵循 "commit or rollback"规则
iterator _M_allocate_and_copy(size_type __n, const_iterator __first,
const_iterator __last)
{
iterator __result = _M_allocate(__n);
__STL_TRY {
uninitialized_copy(__first, __last, __result);
return __result;
}
__STL_UNWIND(_M_deallocate(__result, __n)); // commit or rollback精髓: 发生异常时, 释放线性空间
}
还有一个跟reserve()类似的接口resize(),它是负责什么的呢?
resize()用于指定vector的新size(),改变线性空间的finsih指针,但不改变start和end_of_storage指针。也就是说,resize()并不会影响capacity()大小。为满足最终size()为新指定值,如果当前元素超过指定的size,就擦除多余部分;如果当前元素占用size()不足,就会用指定值插入vector。
// 如果size()超过new_size, 就擦除多余的; 如果不超过, 就在末尾插入指定元素x. 最终目标是让size()等于new_size
void resize(size_type __new_size, const _Tp& __x) {
if (__new_size < size()) // 新size < 现有size()时, 说明原来的size较大, 需要擦除一部分
erase(begin() + __new_size, end()); // 擦除多余空间元素 [begin() + new_size, end())
else
insert(end(), __new_size - size(), __x);
}
void resize(size_type __new_size) { resize(__new_size, _Tp()); }
// 擦除指定位置position的元素, 后面的(position~末尾)元素整体向前移动
iterator erase(iterator __position) {
if (__position + 1 != end()) // 要删除的元素不是末尾元素
copy(__position + 1, _M_finish, __position); // 将擦除位置后的区间[position+1, finish)元素, 拷贝到position起始处
--_M_finish; // 因为只擦除一个元素, finish向前移动1
destroy(_M_finish); // finish指向的就是要删除的那个元素, 析构之, 但不释放空间(尚未归还给配置器)
return __position; // 返回销毁元素的位置
}
// 擦除迭代区间[first, last), 后面的元素整体向前移动
iterator erase(iterator __first, iterator __last) {
iterator __i = copy(__last, _M_finish, __first); // 将擦除区间后的区间[last, finish)元素, 拷贝到first起始处
destroy(__i, _M_finish); // 析构[i, finish)对象, 但并没有释放空间
_M_finish = _M_finish - (__last - __first); // 先前移动finish指针
return __first; // 返回销毁后的起始位置
}
vector的查询
尺寸size()与容量capacity()
size()用于查询vector当前元素个数,capacity()用于查询当前vector线性空间最多容纳元素个数。
size()和capacity()代码很简单,利用了线性空间的3个指针(start, finish, end_of_storage)
size_type size() const // 当前元素个数
{ return size_type(end() - begin()); }
size_type capacity() const // 当前容量, 即线性空间最大能存放的元素个数
{ return size_type(_M_end_of_storage - begin()); }
迭代器访问元素
vector支持迭代器访问元素,提供iterator进行正向顺序访问,reverse_iterator进行反向访问,以及它们的const版本。
所谓正向迭代器,是指从(地址空间)起始到末尾,从小下标到大下标的顺序访问;
所谓反向迭代器,是指从(地址空间)末尾到开始,从大下标到小下标的顺序访问。
// 正向迭代器
iterator begin() { return _M_start; } // 线性空间起始位置对应迭代器
const_iterator begin() const { return _M_start; } // 线性空间起始位置对应const迭代器
iterator end() { return _M_finish; } // 线性空间末尾位置对应迭代器
const_iterator end() const { return _M_finish; } // 线性空间末尾位置对应const迭代器
// 反向迭代器
reverse_iterator rbegin()
{ return reverse_iterator(end()); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(end()); }
reverse_iterator rend()
{ return reverse_iterator(begin()); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(begin()); }
引用访问元素
通过两类元素访问方式:
1)通过front(),back()直接访问第一个、最后一个元素,返回的是元素引用;
2)通过operator[] 随机访问指定下标的元素,返回的是元素引用。
reference operator[](size_type __n) { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素reference
const_reference operator[](size_type __n) const { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素const reference
reference front() { return *begin(); } // 第一个元素reference
const_reference front() const { return *begin(); } // 第一个元素对应const reference
reference back() { return *(end() - 1); } // 最后一个元素reference
const_reference back() const { return *(end() - 1); } // 最后一个元素对应const reference
vector元素操作
尾端插入元素push_back
push_back是在线性空间当前已经使用段的末尾,添加一个新对象,同时右移finish指针。
根据插入对象是否有参数,有两个版本push_back:1)以值x构造的Tp对象;2)无参构造Tp对象,即不需要初值x用于构造Tp对象。
// 在尾端插入以x构造的对象(调用Tp(x)), 会导致size加1. 容量不够时, 需要扩容
void push_back(const _Tp& __x) {
if (_M_finish != _M_end_of_storage) { // 备用空间足够, 不需要扩容
construct(_M_finish, __x); // 在尾端finish所指位置用x构造对象Tp()
++_M_finish; // 尾端标记右移一格
}
else
_M_insert_aux(end(), __x); // 在指定位置end() 插入以x构造的对象Tp()
}
// 在尾端插入空对象(调用Tp()), 会导致size加1. 容量不够时, 需要扩容
void push_back() {
if (_M_finish != _M_end_of_storage) { // 备用空间足够, 不需要扩容
construct(_M_finish); // 在尾端finish所指位置构造对象Tp()
++_M_finish; // 尾端标记右移一格
}
else
_M_insert_aux(end()); // 在指定位置end() 插入新构造对象Tp()
}
指定位置插入元素insert
insert也是用于插入元素,跟push_back的区别在于insert是在指定位置(迭代器)插入新元素。
也就是说,push_back(x)相当于insert(end(), x)。
//-----------------------------
// insert单个对象
// 在指定位置position插入单个对象Tp(x)
iterator insert(iterator __position, const _Tp& __x) {
size_type __n = __position - begin();
if (_M_finish != _M_end_of_storage && __position == end()) {
construct(_M_finish, __x);
++_M_finish;
}
else
_M_insert_aux(__position, __x);
return begin() + __n;
}
// 在指定位置position插入单个对象Tp()
iterator insert(iterator __position) {
size_type __n = __position - begin();
if (_M_finish != _M_end_of_storage && __position == end()) {
construct(_M_finish);
++_M_finish;
}
else
_M_insert_aux(__position);
return begin() + __n;
}
//-----------------------------
// insertd多个对象
// 在指定位置position插入源区间[first, last)所有对象
void insert(iterator __position,
const_iterator __first, const_iterator __last);
// 在指定区间{pos, n}插入构造的Tp(x)对象
void insert (iterator __pos, size_type __n, const _Tp& __x)
{ _M_fill_insert(__pos, __n, __x); }
insert不仅支持插入单个元素,还支持插入多个元素。通过指定 起始位置 + 源迭代器区间,或者指定 起始位置+元素个数+元素值,就能在指定位置(区间)上一次插入多个元素。
insert批量插入代码较为复杂,我们直接图解:
弹出最后一个元素pop_back
pop_back()弹出vector的最后一个元素,会让size减-1,但不会影响capacity。
// 将尾端元素拿掉, 并调整大小
void pop_back() {
--_M_finish; // 尾端标记前移一格, 表示将放弃尾端元素
destroy(_M_finish); // 全局函数, 如果finish所指元素是trivial type, 什么也不做; 如果是class type, 析构finish所指对象
}
擦除元素earse
擦除元素分为两类:1)擦除指定位置元素;2)擦除指定区间所有元素。
擦除元素后,后面的所有元素都会往前移动,以补充空位。另外,移动后的空位置,如果不是trivial type,需要调用其析构函数析构对象;如果是trivial type,可以什么也不做。
擦除元素会影响size,因此需要移动尾端标记finish,移动格数取决于擦除的元素个数。但不会影响capacity。
// 擦除指定位置元素
iterator erase(iterator __position) {
if (__position + 1 != end()) // position所指并非最后一个元素, 说明后续还有元素, 需要往前移动补充擦除的空位
copy(__position + 1, _M_finish, __position); // 将元素从[position+1, finish)拷贝到[position, ...), 也就是position+1之后元素往前移动1格
--_M_finish; // 尾端标记前移一格
destroy(_M_finish); // 析构尾端元素对象
return __position; // 返回擦除元素的位置对应迭代器
}
// 擦除指定区间[first, last)的所有元素
iterator erase(iterator __first, iterator __last) {
iterator __i = copy(__last, _M_finish, __first); // 将源区间[last, finish)所有元素前移, 拷贝到目标区间[first, ...), 返回目标区间结尾位置
destroy(__i, _M_finish); // 销毁拷贝后残余的对象[i, finish)
_M_finish = _M_finish - (__last - __first); // 尾端标记finish前移(last-first)格, 即已擦除元素个数
return __first;
}
注意:vector没有像list那样的remove移除元素接口。
清除所有元素clear
// 擦除所有元素, 如果是non-trivial type, 会对每个元素调用析构函数
void clear() { erase(begin(), end()); }
vector比较操作
SGI STL支持多种vector判断操作,不过并不是作为vector member function,而是作为global function。这样做,可以不用破坏容器的封装性。
根据《Effective C++》Item19,operator>可以转换为由operator<实现,operator!=可以转换为由operator<实现。因此,只需要实现operator==和operator<即可。
参考:https://www.cnblogs.com/fortunely/p/15715265.html
operator==
operator==用于比较两个vector是否相等。
// 比较两个vector的所有元素是否相等, 相等性通过equal判断
// 相等的两个vector, 要求长度相等, 并且所有元素都相等(通过元素的 "!=" 判断)
template <class _Tp, class _Alloc>
inline bool
operator==(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
{
return __x.size() == __y.size() &&
equal(__x.begin(), __x.end(), __y.begin());
}
operator<
operator<用于判断第一个vector是否小于第二个。
// 字典序比较两个vector的所有元素, 判断第一个vector是否小于第二个
template <class _Tp, class _Alloc>
inline bool
operator<(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
{
return lexicographical_compare(__x.begin(), __x.end(),
__y.begin(), __y.end());
}
vector特殊操作swap
swap()用于交换2个vector,代价很小,只需要交换底层线性空间的3个指针。
// 与x交换当前vector交换, 只需要交换维护底层线性空间3个指针即可, 无需将vector所有元素交换或拷贝
void swap(vector<_Tp, _Alloc>& __x) {
__STD::swap(_M_start, __x._M_start);
__STD::swap(_M_finish, __x._M_finish);
__STD::swap(_M_end_of_storage, __x._M_end_of_storage);
}