vector与array相比,有更好的灵活性。array是静态空间,配置之后就无法改变,若需要更大的空间,只能进行配置新空间 →数据移动→释放旧空间的操作;而vector不需要担心空间不足的问题,其内部机制会自动扩充空间从而接纳新的元素。
vector的数据结构是连续的线性空间。它的三个迭代器,start、finish、end_of_storage,分别表示目前使用空间的头、使用空间的尾和可用空间的尾:
...
class vector{
...
protected:
iterator start;
iterator finish;
iterator end_of_storage;
...}
有了start、finish、end_of_storage这三个迭代器,我们可以完成一些简单操作:
-
返回首尾指针:
iterator begin() { return start; }
iterator end() { return finish; }
-
返回大小、容量、空容器判断:
size_type size() const { return size_type(end() - begin()); }
size_type capacity() const { return size_type(end_of_storage - begin()); }
bool empty() const { return begin() == end(); }
-
返回注标运算子、首元素、尾元素:
reference front() { return *begin(); }
reference back() { return *(end() - 1); }//end指针指向尾元素之后
reference operator[](size_type n) { return *(begin() + n); }//退化成数组,存在越界访问的问题
vector的大小和容量
当不断向vector中加入和弹出数据后,通过程序观察大小和容量的变化。
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> iv;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
//resize()、reserve()、push_back、pop_back
iv.resize(1);//resize为size对应的方法;reverse为capacity对应的方法
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.reserve(2);//resize为size对应的方法;reserve为capacity对应的方法
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.push_back(2) ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.push_back(4) ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.push_back(5) ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.push_back(5) ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.pop_back() ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.pop_back() ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
return 0;
}
得到这样的结果:
因此可以得出结论:
1、capacity的值会大于等于size的值;
2、当使用resize改变size的大小时,capacity不会成倍地增大,而是与size大小相同或略大于size;
3、当通过push_back向vector中添加元素时,若vector的size和capacity相同,此时capacity会扩充至两倍;
4、当通过pop_back弹出数据时,capacity的大小不会变小。
当对元素进行插入、删除、清楚等操作时,观察capacity和size的大下变换:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
//erase、clear、insert
vector<int> iv(4,6);
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.push_back(6) ;
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.erase(iv.begin());
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.insert(iv.begin(),1,1);
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.insert(iv.begin(),6,1);
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
iv.clear();
cout<<"capacity= "<<iv.capacity()<<" "<<"size= "<<iv.size()<<endl;
return 0;
}
得到的结果如下:
当capacity足够大的时候,这些元素操作不会改变capacity大小,而插入元素后若超出vector的容量,则将capacity扩大至元素的个数。
vector的构造函数:
vector有很多的构造函数,其中一个允许我么指定空间大小和初始值:
vector(size_type n, const T& value) { fill_initialize(n, value); }
//填充和初始化
void fill_initialize(size_type n, const T& value) {
start = allocate_and_fill(n, value);
finish = start + n;
end_of_storage = finish;
}
//配置和填充
iterator allocate_and_fill(size_type n, const T& x) {
iterator result = data_allocator::allocate(n);
__STL_TRY {//异常控制?
uninitialized_fill_n(result, n, x);
return result;
}
__STL_UNWIND(data_allocator::deallocate(result, n));
}
uninitialized_fill_n()是一个全局函数,为指定范围内的所有元素设置相同的初始值,接受三个参,分别是欲初始化空间的起始处、欲初始化空间的大小、初始值;data_allocator::allocate()是根据alloc定义的,方便以元素大小作为配置单位。
push_back()
vector的push_back()源码如下:
void push_back(const T& x) {
if (finish != end_of_storage) {
construct(finish, x);
++finish;
}
else
insert_aux(end(), x);
}
construct()是对象内容构造的基本函数,接受两个参数,分别是一个指针和一个初始值,作用是将初始值设定到指针所指的空间上。当vector的大小小于容量时,简单地在开辟的空间中加入一个值;否则,调用insert_aux()函数:
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
if (finish != end_of_storage) {//存在备用空间
construct(finish, *(finish - 1));//以最后一个值作为初始值进行初始化
++finish;
T x_copy = x;
copy_backward(position, finish - 2, finish - 1);
*position = x_copy;
}
else {
const size_type old_size = size();
const size_type len = old_size != 0 ? 2 * old_size : 1;//若原大小为0,则配置1
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
new_finish = uninitialized_copy(start, position, new_start);//拷贝position前的旧数据
construct(new_finish, x);//加入新数据
++new_finish;//调整new_finish
new_finish = uninitialized_copy(position, finish, new_finish);//拷贝position后的旧数据
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {//出错就销毁后重新开辟空间
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
destroy(begin(), end());//析构并释放
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
其中,copy_backward( first, last ,result)表示的是从最后一个元素开始复制,接受三个参数,分别为被复制的元素的区间范围[first,last),复制到目标区间的具体位置[result-(last-first),result)。其实push_back()并不会执行insert_aux()的if判断,if判断后执行的操作是首先在vector的末尾插入一个元素,初始值为vector插入前的最后一个元素,然后将position至finish之间的元素向后移一位,最后将需要插入的值得副本插入至position位置。
当所需的vector的大小大于容量时,开辟一段新的空间,并将容量扩大为原来的两倍(若初始容量是0,则配置为1),前半段用来放置旧数据,后半段用来放置新数据。由于insert_aux()函数会被insert调用,因此函数的功能是更通用地将数据插入至position位置。position的值为end()时,也就是在末尾插入数据,就是push_back()的功能。
在完成数据的插入之后,需要析构并释放原vector,然后再调整迭代器的名称,将start、finish、end_of_storage指向新的vector。
vector其他主要元素操作:
除了push_back()之外,还有pop_back()、erase()、clear()、insert()、swap()等等,对其中部分操作的源码进行解读。
-
pop_back()
void pop_back() {
--finish;
destroy(finish);
}
pop_back()将尾后指针finish向前移动,最后将finish指向的对象析构掉(destroy()为全局函数,用于析构)。
-
erase()
iterator erase(iterator position) {
if (position + 1 != end())
copy(position + 1, finish, position);
--finish;
destroy(finish);
return position;
}
iterator erase(iterator first, iterator last) {
iterator i = copy(last, finish, first);
destroy(i, finish);
finish = finish - (last - first);
return first;
}
erase()函数有两个版本的,用于清除某个位置的元素或某两个位置之间的所有元素。
对于第一个版本,若删除元素不是末尾元素,则将position之后的元素向前移动,并将末尾元素析构;否则直接对末尾元素析构。
对于第二个版本,思想与第一个版本相似,将(last,finish)之间的元素拷贝至first处,然后将多余的元素析构掉。
-
insert()
template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x) {
if (n != 0) {
if (size_type(end_of_storage - finish) >= n) {
T x_copy = x;
const size_type elems_after = finish - position;
iterator old_finish = finish;
if (elems_after > n) {
uninitialized_copy(finish - n, finish, finish);
finish += n;
copy_backward(position, old_finish - n, old_finish);
fill(position, position + n, x_copy);
}
else {
uninitialized_fill_n(finish, n - elems_after, x_copy);
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish);
finish += elems_after;
fill(position, old_finish, x_copy);
}
}
else {
const size_type old_size = size();
const size_type len = old_size + max(old_size, n);
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
new_finish = uninitialized_copy(start, position, new_start);
new_finish = uninitialized_fill_n(new_finish, n, x);
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
destroy(start, finish);
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}
insert(position,n,x)接受三个参数,分别是插入位置,插入元素数量,插入元素值。插入操作的情况比之前的元素操作都要复杂,因为它会遇到更多的情况:插入元素后是否会超过vector的容量(有就是备用空间容量是否大于插入元素个数)?插入元素的数量是否大于插入点后的元素数量?
①备用空间容量大于等于插入元素个数n
若插入点后的元素数量m大于插入元素的数量n,先将vector的后n个元素uninitialized_copy至vector末尾,再将m-n个元素拷贝至old_finish(插入数据前的finish)之前,最后再对position位置填充需要插入的数据。
假设m=3,n=2,插入过程如下图所示:
若插入点后的元素数量m小于插入元素的数量n,假设m=2,n=3,插入过程如下图所示:
②备用空间容量小于插入元素个数n
此时需要开辟新的空间,首先将插入点之前的元素uninitialized_copy()至新的vector内,然后将需要拷贝的元素uninitialized_fill_n()至新vector的插入点,最后再将原vector剩下的元素uninitialized_copy()至新vector的最后。
开辟新的空间的大小由插入元素的数量决定:若插入元素数量大于vector的大小old_size,则新vector的大小为old_size+n;否则新vector的大小为2*old_size:
const size_type len = old_size + max(old_size, n);