STL容器——vector
vector
与array
非常相似,两者的唯一差别在于空间运用的灵活性。array是静态空间,一旦配置了就不能更改;要换个大一点的房子,可以,一切琐碎得由客户端自己来;首先配置一块新空间,然后将元素从旧地址一一搬往新地址,再把原来的空间释放给系统。vector是动态空间,随着新元素的加入,它的内部机制会自行扩充空间以容纳新元素。
vector定义摘要
template<class T, class Alloc = alloc>
class vector{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* itertor;
typedef value_type& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
iterator start; //表示目前使用空间的头
iterator finish; //表示目前使用空间的尾
iterator end_of_storage;//表示目前可用空间的尾
void insert_aux(iterator position,const T& x);
void deallocate(){
if(start)
data_allocator::deallocate(start, end_of_storage - start);
}
void fill_initialize(size_type n, const T& value){
start = allocate_and_fill(n, value);
finish = start + n;
end_of_storage = finish;
}
public:
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 operator[](size_type n) { return *(begin() + n); }
vector() : start(0), finish(0), end_of_storage(0) {}
vector(size_type n, const T& value) { fill_initialize(n, value); }
vector(int n, const T& value) { fill_initialize(n, value); }
vector(long n, const T& value) { fill_initialize(n, value); }
explicit vector(size_type n) { fill_initialize(n, T()); }
~vector(){
destory(start, finish);
deallocate(); //这是vector的一个成员函数
}
reference front() { return *begin(); } //第一个元素
reference back() { return *(end() - 1); } //最后一个元素
void push_back(const T& x){
if(finish != end_of_storage){
construct(finish, x);
++finish;
}
else
insert_aux(end(), x);
}
void pop_back(){
--finish;
destory(finish);
}
iterator erase(iterator position){ //清除某位置上的元素
if(position + 1 != end())
copy(position+1, finish, position); //后续元素往前移
--finish;
destory(finish);
return position;
}
void resize(size_type new_size, const T& x){
if(new_size < size())
erase(begin() + new_size, end());
else
insert(end(), new_size - size(), x);
}
void resize(size_type new_size) { resize(new_size, T& x); }
void clear() { erase(begin(), end()); }
protected:
//配置空间并填满内容
iterator allocate_and_fill(size_type n, const T& x){
iterator result = data_allocator::allocate(n);
uninitialized_fill_n(result, n, x);
return result;
}
};
vector的迭代器
vector
维护的是一个连续线性空间,所以不论其元素类型是什么,普通指针都可以作为vector
的迭代器而满足所有必要条件。因为vector
迭代器所需要的操作行为,如operator*,operator->,operator++,operator--,operator+,operator-,operator+=,operator-=
,普通指针天生就具备。vector支持随机存取,而普通指针正有这样的能力。所以,vector
提供的是random access iterator
。
template<class T. class Alloc = alloc>
class vector{
typedef T value_type;
typedef value_type* iterator; //vector的迭代器是普通指针
...
};
根据上述定义,如果客户端写出这样的代码:
vector<int>::iterator ivite;
vector<Shape>::iterator svite;
ivite
的类型其实就是int*
,svite
的类型其实就是Shape*
。
vector的数据结构
vector
所采用的数据结构非常简单:线性连续空间。它以两个迭代器start
和finish
分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage
指向整块连续空间(含备用空间)的尾端。
template<class T, class Alloc = alloc>
class vector{
...
protected:
iterator start; //表示目前使用空间的头
iterator finish; //表示目前使用空间的尾
iterator end_of_storage; //表示目前可用空间的尾
...
};
运用start,finish,end_of_storage三个迭代器,便可轻易地提供首尾标识,大小,容量,容器判断,注标([ ])运算子,最前端元素,最后端元素等功能。
template <class T, class Alloc = alloc>
class vector{
...
public:
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 operator[](size_type n) { return *(begin() + n); }
reference front() { return *begin(); }
reference back() { return *(end() - 1); }
};
vector的构造和内存管理:constructor,push_back
vector
提供许多constructors
,其中一个允许我们指定空间大小及初值。
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);
uninitialized_fill_n(result, n, x); //全局函数
return result;
}
当我们以push_back()
将新元素插入于vector
尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish
,使vector
变大。如果没有备用空间了,就扩充空间(重新配置,移动数据,释放原空间)。
void push_back(const T& x){
if(finish != end_of_storage){ //还有备用空间
construct(finish, x);
++finish;
}
else
insert_aux(end(), x); //没有备用空间
}
template<class T, class Alloc>
void vector<T, alloc>::insert_aux(iterator position, const T& x){
if(finish != end_of_storage) //还有备用空间
{
//在备用空间起始处构造一个元素,并以vector最后一个元素值为其初始值
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个元素的大小
//如果原大小不为0,则配置原大小的两倍
//前半段用来放置原数据,后半段准备用来放置新数据
iterator new_start = data_allocator::allocate(len); //实际配置
iterator new_finish = new_start;
try{
//将原vector的内容拷贝到新vector
new_finish = uninitialized_copy(start, position, new_start);
//为新元素设定初值x
construct(new_finish, x);
++new_finish;
//将安插点的原内容也拷贝过来(提示:本函数也可能被insert(p, x)调用)
new_finish = uninitialized_copy(position ,finish, new_finish);
}
catch(...){
destory(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
//析构并释放原vector
destory(begin(), end());
deallocate();
//调整迭代器,指向新vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
注意,所谓动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间,因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
vector删除其中的元素,迭代器如何变化?为什么是两倍扩容?释放空间?
size()
函数返回的是已用空间大小,capacity()
返回的是总空间大小,capacity() - size()
则是剩余的可用空间大小。当size()
和capacity()
相等,说明vector
目前的空间已被用完,如果再添加新元素,则会引起vector
空间的动态增长。
由于动态增长会引起重新分配内存空间,拷贝原空间,释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)
预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当 n > capacity()
时,调用reserve(n)
才会改变vector
容量。
resize()
成员函数只改变元素的数目,不改变vector
的容量。
- 空的
vector
对象,size()
和capacity()
都为0; - 当空间大小不足时,新分配的空间大小为原空间大小的2倍;
- 使用
reserve()
预先分配了一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率; - 当
reserve()
分配的空间比原空间小时,不会引起重新分配的; resize()
只改变容器的元素数目,不改变容器的大小;- 用
reserve(size_type)
只是扩大capacity
值,这些内存空间可能还是未初始化的,如果此时使用下标运算符[]
来访问,则可能会越界;而resize(size_type new_size)
会真正使容器具有new_size
个对象;
不同的编译器,vector
有不同的扩容大小,在vs下是1.5倍,在GCC下是2倍。
空间和时间的权衡,简单来说,空间分配的多,平摊时间复杂度低,但浪费空间也多。
采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度。
如何释放空间:
由于vector
的内存占用空间只增不减,比如你首先分配了10000个字节,然后erase
掉后面的9999个,留下一个有效元素,然是内存占用仍为10000个,所有内存空间是在vector
析构时才能被系统回收。empty()
检测容器是否为空,clear()
可以清空所有元素。
clear()
无法保证内存的回收。
vector的增加删除是怎么做的?为什么是1.5倍或者2倍?
- 新增元素:
vector
通过一个连续的数组存放元素,如果数组已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素; - 对
vector
的任何操作, 一旦引起空间重新配置,指向原vector
的所有迭代器就都失效了; - 初始时刻
vector
的capacity
为0,插入第一个元素后capacity
增加为1; - 考虑可能产生的堆空间浪费,成倍增长倍数不能太大,使用较为广泛的扩容方式有两种,以2倍方式扩容,或者以1.5倍方式扩容。
- 新增元素:
vector
通过一个连续的数组存放元素,如果数组已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素。 pop_back()
可以删除最后一个元素,erase()
可以删除由一个iterator
指出的元素,也可以删除一个指定范围的元素。remove()
也可以删除vector
容器的元素,不同的是,采用remove
不会改变容器的大小,而pop_back()
与erase()
等成员函数会改变容器的大小。