vector使用
vector定义
语句 | 作用 |
vector<int> a(n); | 指定容器大小为n |
vector<int> a(n, x); | 指定容器大小为n,并初始化所有元素为x |
vector<vector<int>> a(m, vector<int>(n)); | m行n列的二维数组,可以直接用a[i][j]访问 |
访问vector容器中元素的操作
语句 | 作用 |
vec.at(index) | 返回由index指定的位置上的元素 |
vec[index] | 返回由index指定的位置上的元素 |
vec.front() | 返回第一个元素(不检查容器是否为空) |
vec.back() | 返回最后一个元素(不检查容器是否为空) |
vector容器大小相关的操作
语句 | 作用 |
vec.capacity() | 返回不重新分配空间可以插入到容器vec中的元素的最大个数 |
vec.empty() | 容器vec为空,返回true;否则,返回false |
vec.size() | 返回容器vec中当前的元素个数 |
vec.max_size() | 返回可以插入到容器vec中的元素的最大个数 |
vec.resize(num) | 将元素个数改为num。如果size()增加,默认的构造函数负责创建这些新元素 |
vec.resize(num, elem) | 将元素个数改为num。如果size()增加,这些新元素初始化为elem |
vec.reserve(num) | 保留足够空间以容纳num个元素,避免在达到num之前的插入操作中重新分配空间 |
元素插入、元素删除、遍历向量容器中的元素
语句 | 作用 |
vec.clear() | 从容器中删除所有元素 |
vec.erase(position) | 删除由position指定的位置上的元素 |
vec.erase(beg, end) | 删除从beg到end-1之间的所有元素 |
vec.insert(position, elem) | 将elem的一个拷贝插入到由position指定的位置上,并返回新元素的位置 |
vec.insert(position, n, elem) | 将elem的n个拷贝插入到由 position指定的位置上 |
vec.insert(position, beg, end) | 将从beg到end-1之间的所有元素的拷贝插入到vec中由position指定的位置上 |
vec.push_back(elem) | 将elem的一个拷贝插入到vector的末尾 |
vec.pop_back() | 删除最后元素 |
vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新的元素。vector的实现技术,关键在于对大小的控制以及重新配置时的数据移动效率。
vector的数据结构:
vector采用线性连续空间,以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。
template<class T, class Alloc = alloc>class vector{
...protected:
iterator start; //表示目前使用的空间的头
iterator finish; //表示目前使用的空间的尾
iterator end_of_storage; //表示目前可用的空间的尾...
};
vector的构造函数和析构函数:
emplate <class T, class Alloc = alloc>
class vector {
...
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
....
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;
}
...
public:
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部分构造函数,其中默认构造函数只是把所有的迭代器都初始化为0,但它并没有申请内存空间。
另外4个大同小异,都向堆申请了大小为n的内存空间,只是初始化这些空间的时候进行的操作不一样而已。
这4个构造函数都用同一个函数fill_initialize()来进行堆空间的申请并且初始化。
void fill_initialize(size_type n, const T& value) {
start = allocate_and_fill(n, value);
finish = start + n;
end_of_storage = finish;
}
它接受两个参数n和value,n指明了要申请的堆空间大小,value指明了要初始化这些堆空间的内容,并把它们传给另外一个函数allocate_and_fill() ,该函数才是真正的申请堆空间和初始化。
allocate_and_fill() 函数。
// 分配空间并填满内容
iterator allocate_and_fill(size_type n, const T& x) {
iterator result = data_allocator::allocate(n);
uninitialized_fill_n(result, n, x);
return result;
}
函数接受来自fill_initialize()的两个参数,然后申请堆空间并初始化。data_allocator实质上就是simple_alloc<value_type, Alloc>,simple_alloc是SGI STL的空间配置器。若果是SGI STL第一级配置器那么data_allocator::allocate()实质上就是直接调用c语言中的malloc()来申请堆空间;若是第二级配置器,就先考察申请区块是否大于128bytes,若是大于则转调用第一级配置器,否则就以内存池来管理,目的是为了避免太多小额区块造成内存碎片化。
vector的析构函数:
~vector() {
destroy(start, finish);
dellocate();
}
析构函数很简单,就调用两个函数:destroy()和dellocate()。destroy()负责对象的析构,dellocate()负责释放申请的堆空间。这里释放的方式又与空间配置器相关。若果是第一级配置器,就直接调用c语言中free()函数,这正如申请时的简便。但若果是第二级配置器,则比较复杂一些。上面的内容就是关于vector申请和释放堆空间的大概过程,但仅仅是申请和释放堆空间而已。
// 第一个版本
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}
第一个版本接受一个指针,调用析构函数将该指针所指的对象析构。
// 第二个版本
template<class ForwardIterator, class T>
inline void destroy(ForwardIterator first, ForwardIterator last, T*) {
__destroy(first, last, value_type(first));
}
第二版本接受first和last两个迭代器,将[first, last)范围内的所有对象析构掉。
vector的push_back函数:
void push_back(const T& x) { if (finish != end_of_storage) {construct(finish, x); ++finish;}
else {insert_aux(end(), x);}
}
当我们把元素push_back到vector的尾端后,函数首先检查是否还有备用的空间,如果有的话就调用construct()函数,在finish迭代器指定的位置上构建x对象,同时改变finish迭代器,使其自增1。没有备用空间,就需要扩充空间,调用insert_aux()函数。
insert_aux()如下:
template <class T, class Alloc>
void 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
{
// uninitialized_copy()的第一个参数指向输入端的起始位置
// 第二个参数指向输入端的结束位置(前闭后开的区间)
// 第三个参数指向输出端(欲初始化空间)的起始处
// 将原vector的安插点以前的内容拷贝到新vector
new_finish = uninitialized_copy(start, position, new_start);
// 为新元素设定初值 x
construct(new_finish, x);
// 调整已使用迭代器的位置
++new_finish;
// 将安插点以后的原内容也拷贝过来
new_finish = uninitialized_copy(position, finish, new_finish);
}
catch(...)
{
// 回滚操作
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
// 析构并释放原vector
destroy(begin(), end());
deallocate();
// 调整迭代器,指向新vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
Vector动态扩容:
当备用空间不足时,vector做了以下的工作:
(1)重新分配空间:若原来的空间大小为0,则扩充空间为1,否则扩充为原来的两倍。
(2)移动数据,释放原空间,更新迭代器,当调用默认构造函数构造vector时,其空间大小为0;但当我们push_back一个元素到vector尾端时,vector就进行空间扩展,大小为1,以后每当备用空间用完了,就将空间大小扩展为原来的两倍。
动态增加大小,并不是在原空间之后接续新空间,(因为无法保证原空间之后上有可供分配的空间),而是以原大小的两倍来另外分配一块较大空间,因此,一旦空间重新分配,指向原vector的所有迭代器就会失。