vector是动态数组,其基本定义如下:
namespace std{
template <class T ,class Allocator = allocator<T> >
class vector;
}
vector中的元素可以是任意类型,但是必须是可复制、可拷贝的,事实上,对于STL中的绝大部分容器都有此要求。Allocator则是内存模型。
容量
vector采用内存动态分配机制,当vector中元素达到临界值时,vector会自动重新配置;vector中元素个数可以使用size(), 容量是capacity(),超过容量,vector就会重新配置,一般来说,会重新分配原来两倍的空间;
需要注意的是,重新配置会导致以下结果:
- 和vector相关的引用,指针,迭代器都会失效;
- 内存重新分配很耗时间;
基于上述原因,所以我们应该尽量避免内存重新分配,可以采用以下两种措施:
- 使用reserve()预分配足够的空间;
- 使用构造函数初始化时分配足够的空间;
需要注意的是,reserve() 函数只能只能为我们预留足够的空间,更近一步,它只能让vector的容量增大,但是无法让容量缩小。如果我们要使容器容量缩小,可以使用shrink_to_fit(),它会把vector从曾经最大的容量减少到它现在需要的容量。
事实上,即使删除元素,也不会导致容量缩减,也就不会内存重新分配,但是由于元素移动,可能导致删除元素之后的引用、指针、迭代器失效;而安插则可能导致重新配置空间,所有引用、指针、迭代器失效;
除了reserve() 和 shrink_to_fit() 函数之外,能够操作容量的还有 swap() 函数,两个数据类型相同的vector在交换元素时,会将容量一起交换,如下:
template<class T>
void shrinkCapacity(std::vector<T>& v)
{
std::vector<T> temp(v);
v.swap(tmp);
}
基本操作
using namespace std;
int main()
{
vector<int> c;
//增
c.insert(c.begin(),10);
c.push_back(10);
//删
c.pop_back();
c.erase(c.begin()); //删除迭代器位置的元素,同时返回下一位置的迭代器
c.clear();
//改
c.at(0) = 10;
c[0] = 11;
c.front() = 10;
c.back() = 10;
return 0;
}
以上列举出了vector包括增、删、改的基本操作。基本可以满足日常的需要。如果还要更详细的了解,可以去查看相关文档:链接。这里就不再赘述。
这里需要注意的是:vector由于本质是数组,所以我们在进行插入及删除时都可能涉及元素移动,所以涉及到效率问题,我们可以从以下几个方面优化:
- 在容器尾部安插或者移除元素
- 容量一开始就够大
- 安插多个元素时,调用一次比调用多次要快(整体插入移除)
copy函数
copy函数可以用于将vector的某个范围的元素复制到另一个vector,其原型如下:
copy(_II __first, _II __last, _OI __result);
其中,
- __first:起始位置。
- __last:结束位置。
- __result:目标数组。
需要注意的是,__first是第一个被复制的元素,而结束位置的元素并不会复制。例如,我们可以像下面这样将一个数组中元素复制到另一个数组:
vector<int> vec1 = {1,2,3,4,5};
vector<int> vec2;
copy(vec1.begin(),vec1.end(),vec2);
上述代码似乎并没有问题,但是考虑一下,vec2实际是空的,我们或许会想vector的内存会动态分配,但是实际并不如我们所愿,如果执行上述操作,我们最终会发现,vec2仍然为空。
为什么呢?因为copy函数并非是vector的独有算法,它是STL中的通用算法,这就意味着它不会考虑到vector的内存扩展,我们必须确保目标数组不为空才成。
我们或许会使用reserve()函数。如下:
vec2.reverse(6);
如果我们这样使用那就意味着我们对容量及大小的区别并没有完全理解,大小是从begin()到end()中所有元素个数,而容量则是为该数组分配的内存,容量中有部分内存是数组还未使用到的。所有reverse之后数组大小仍为0,begin()及end()都指向NULL。正确做法如下:
vector<int> vec2(3);
我们为数组分配了3个默认元素(0),大小为3,容量为3。
vector与array
vector可以像数组一样使用,唯一不同的在于array空间可能不足,但vector则不会;
STL标准程序库一书中说,需要注意不要把迭代器当做第一元素的地址来传递,因为其也许并不是一个一般指针;不过侯捷先生的STL源码刨析中提供了源码,vector的迭代器就是一个普通指针,因此,在确认vector不为空的情况下,其迭代器的第一元素可以视作其地址;
vector与list
vector本质上是数组,而list本质上是链表,因此二者存在一定的区别。
- vector支持随机存取,可以根据索引获取元素;在末端附加活删除元素时,vector的性能相当好,但是如果要在前端、中部安插或者删除元素,那么操作点之后的没一个元素都要移到另一个位置,将会导致效率低下;
- list不支持随机存取,也就是说不支持索引获取,如果要获取某个元素,必须遍历到该元素,因此list没有[]重载,也没有at()函数;
- list任何位置安插删除元素都很快,始终在常数时间内完成,因为不需要移动任何元素,只需要修改其前向及后向指针指向即可;
vector动态内存的实现
当vector出现内存重新分配时,会进行以下操作:
这和我们想的可能不一样,重新内存分配时,并非是在原有内存去分配,而是重新配置空间,然后将原空间元素移动到新空间,最后再释放原空间;
下面提供一段伪代码来表示这个过程:
int *data;
int capacity;
int size;
int reallocate(int aoc)
{
int *newData = new int[aoc];
for(int i = 0 ; i < size ; ++i)
{
newData[i] = data[i];
}
delete data[];
data = newData;
capacity = aoc;
}
vector异常
注意,这里所说的异常也基本适用于STL其他容器;
- 获取元素时,只有at会抛出异常,其他方式不会;
- push_back等函数如果失败,那么该操作不会带来任何效应;
- pop_back等函数不会抛出任何异常,这也意味着,即使已经不存在元素,pop_back也不会出现异常,但编译器可能出现未定义的操作;