vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。
连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进形扩容。
size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity()-size()则是剩余的可用空间大小。当size()和capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长。
由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当n>capacity()时,调用reserve(n)才会改变vector容量。
resize()成员函数只改变元素的数目,不改变vector的容量。
1. 空的vector对象,size()和capacity()都为0
2. 当空间大小不足时,新分配的空间大小为原空间大小的2倍。
3. 使用reserve()预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。
4. 当reserve()分配的空间比原空间小时,是不会引起重新分配的。
5. resize()函数只改变容器的元素数目,未改变容器大小。
6. 用reserve(size_type)只是扩大capacity值,这些内存空间可能还是“野”的,如果此时使用“[ ]”来访问,则可能会越界。而resize(size_type new_size)会真正使容器具有new_size个对象。
注意:不同的编译器,vector有不同的扩容大小。在vs下是1.5倍,在GCC下是2倍。
下边使用gcc测试vec扩容的代码:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> vec;
cout<<"0个元素时容器的容量为"<<vec.capacity()<<endl;
for(int i=1;i!=100;i++)
{
vec.push_back(1);
cout<<i<<"个元素时容器的容量为"<<vec.capacity()<<endl;
}
return 0;
}
~
测试结果
可以看到容器的容量不足时,新分配的空间为原来的两倍(vs上是0 1 2 3 4 6 9 13. ....,按1.5倍重新分配的)。
下边从 sgi-stl vector源码证实一下vector内存的分配规则
void push_back(const _Tp& __x) {
if (_M_finish != _M_end_of_storage) { // 有备用空间
construct(_M_finish, __x); // 全局函数,将 __x 设定到 _M_finish 指针所指的空间上
++_M_finish; // 调整
}
else
_M_insert_aux(end(), __x); // 无备用空间,从新分配再插入
}
可以看到如果尾后插入元素时还有备用空间 则直接插入,否则调用M_insert_aux()重新分配。可以看到内存的分配规则。
即如果老得内存空间长度为0,则新内存空间长度为1,否则新的空间长度是老的空间长度的2倍。
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
if (_M_finish != _M_end_of_storage) {
construct(_M_finish, *(_M_finish - 1));
++_M_finish;
_Tp __x_copy = __x;
copy_backward(__position, _M_finish - 2, _M_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,否则新的空间长度是老的空间长度的2倍
iterator __new_start = _M_allocate(__len);
iterator __new_finish = __new_start;
__STL_TRY {
__new_finish = uninitialized_copy(_M_start, __position, __new_start);
construct(__new_finish, __x);
++__new_finish;
__new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
}
__STL_UNWIND((destroy(__new_start,__new_finish),
_M_deallocate(__new_start,__len)));
destroy(begin(), end());
_M_deallocate(_M_start, _M_end_of_storage - _M_start);
_M_start = __new_start;
_M_finish = __new_finish;
_M_end_of_storage = __new_start + __len;
}
}
频繁的压入元素,vector容器也会频繁的重新分配内存空间,可以通过用reserver(n)事先申请足够的容量,但reserver(n)申请的内存必须必当前的内存容量大。
// 预留存储空间,若 __n 大于当前的 capacity() ,则分配新存储,否则该方法不做任何事。
void reserve(size_type __n) {
if (capacity() < __n) {
const size_type __old_size = size();
iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish);
destroy(_M_start, _M_finish);
_M_deallocate(_M_start, _M_end_of_storage - _M_start);
_M_start = __tmp;
_M_finish = __tmp + __old_size;
_M_end_of_storage = _M_start + __n;
}
}
vector容器的空间释放
由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。