vector是我们最经常用到的STL容器之一
其能够动态改变数组大小 比起C的数组方便了很多
三个指针
vector中有三个迭代器first,last,end, 分别指向了数组的开头, 数组的结尾+1以及数组的最大容量结尾
迭代器这里可以理解为是指针
当我们有了这三个指针之后, 一些vector的信息很快就能够得到
int first,last,end; //假设存的是指针地址 运算只涉及到相对大小 所以直接用int距离
unsigned int size(){ //通过首尾 很容易得出size
return last-first;
}
bool empty(){ //可以直接判断是否为空
return last==first;
}
unsigned int capacity(){ //得到当前数组的容量
return end-begin;
}
//...等等函数
其中上面这个capacity指的是当前vector的数组长度, 没想到吧, 其实vector底层还是用静态数组来实现的, 大概意思就像这样:
int* data=new int[capacity];
那么如何动态改变容量, 便是vector的核心
resize函数
当我们进行vector的push_back()操作的时候, 就有可能使得vector的静态数组超出其容量
在了解之前, 我以为vector的扩容是在后面申请一段新的空间, 然后通过链式合并到原来的
但是其实其原理非常简单, 在扩容的时候其实是新申请了一段更大的静态数组, 然后把原来的数组的东西copy过去, 最后删掉原来的数组, 大概是像这样的:
void resize(){
//此时静态数组已经满了 即last==end
int* new_data=new int[new_capacity]; //创建一个更大的内存的静态数组
for(int i=0;i<last-begin;i++) //把原来的元素复制过去
new_data[i]=data[i];
delete[] data; //把原来的数组空间释放
data=new_data; //然后把新的空间拿来用
end=new_capacity+begin; //扩容后了
}
由此可以看出, vector的扩容每次都是O(n)的操作, 这样就会使得我们以为的O(1) push_back()不那么香了, 因此为了降低操作复杂度, 显然我们是不能频繁的进行resize的
resize的频率显然取决于new_capacity是多少
- 假如该值总为capacity+1, 那么每次push_back()都要申请大一点的空间, 相当费力
- 假如该值为一个非常非常大的数, 那么就不用resize, 但是会浪费大量的空间
可以看出上面两种都是不大行的
因此C++的扩容采取的是倍数扩容, 扩容倍数在[1.5,2] 之间都是合理的
这是为了降低每次扩容的次数, 但是又不会过度浪费空间
在VS中, 该倍数为1.5 而在GCC中, 该倍数为2
这里VS的1.5倍考虑了内存的回收
想想看, 如果当倍数为2的时候, 那么每次扩容是这样的:
1 2 4 8 16 32 64...
显然, 每次新的值需要的内存大小, 比前面所有释放了的空间总和都要大
比如现在扩容到8, 以前释放过的内存大小总和也才1+2+4=7
但是当倍数为1.5的时候
1 2 3 5 8 12 18
可以看出, 在8以后的每次申请内存过程中, 前面已经释放过的内存总能比它大, 也就是能够回收内存
push_back()大概
void push_back(int x){
if(end==last)resize(); //容量不够了
data[last++]=x;
}