1. 基本操作
#include<iostream> #include<vector> using namespace std; int main() { vector<int>v1; vector<int>v2(4); vector<int>v3(1,4); v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); // 这里清除区间,左闭右开 v1.erase(v1.begin()+2, v1.end()-1); v2 = v1; if(!v2.empty()) { for(const auto i : v2) { cout << i << " "; } cout << endl; cout << "v1 size: " << v2.size() << endl; v2.~vector(); } return 0; } 1. push_back和pop_back操作 都只是对尾部进行操作, push_back:从尾部插入数据时,当数组还有备用空间时就直接插入尾部就行,如果没有就重新寻找更大的空间并将数据赋值过去 void push_back(const T& x) { // 如果还没有到填满整个数组, 就在数据尾部插入 if (finish != end_of_storage) { construct(finish, x); ++finish; } // 数组被填充满, 调用insert_aux必须重新寻找新的更大的连续空间, 再进行插入 else insert_aux(end(), x); } pop_back: 从尾部删除,使用空间的尾自减并调用析构函数,但是并没有释放内存 void pop_back() { --finish; destroy(finish); } finish始终都指向最后一个元素的后一个位置的地址. 2. 容器大小的调整 reverse: 修改容器的大小 空间大小 void reverse(size_type n) { // 修改容器的大小要大于之前的容器大小 } resize: size() 使用大小, 变小释放,变大用尾部填充 new_size大于cap,小于2*cap,则用cap = 2*new_size 大于2*cap,则cap = new_size void resize(size_type new_size, const T& x) { // 元素大小大于了要修改的大小, 则释放掉超过的元素 if (new_size < size()) erase(begin() + new_size, end()); // 元素不够, 就从end开始到要求的大小为止都初始化x else insert(end(), new_size - size(), x); }
2. reverse 和 resize
// vector::reserve #include <iostream> #include <vector> int main () { std::vector<int>::size_type sz; std::vector<int> foo; sz = foo.capacity(); std::cout << "making foo grow: "; for (int i=0; i<1000; ++i) { foo.push_back(i); //std::cout << "i: " << i << ' '; if (sz!=foo.capacity()) { sz = foo.capacity(); std::cout << "capacity changed: " << sz << ' '; } } std::cout << "foo size: " << foo.size() << ' '; std::cout << "cap: " << foo.capacity() <<' '; sz = foo.capacity(); for(int i = 0;i < 1000;i++) { //std::cout << foo.pop_back() << ' '; std::cout << "i: " << i << " cap: " << foo.capacity() << " size: " << foo.size()<< ' '; foo.pop_back(); if (sz!=foo.capacity()) { sz = foo.capacity(); std::cout << "capacity changed: " << sz << ' '; } } return 0; }
3. 测试
#include <iostream> #include <vector> using namespace std; int main () { std::vector<int>::size_type sz; std::vector<int> foo; vector<int>v1; for(int i = 0;i < 12;i++) { v1.push_back(i); cout << v1.capacity() << " " << v1.size() << endl; } cout << "max_size: " << v1.max_size() << endl; }
可见,VS中按1.5倍扩容,GCC以2倍扩容。
一种不调用析构函数将vector清空的方法:
vector<int>().swap(v1);
4. 扩容因子
实际上,C++标准并没有push_back要用哪个增长因子,这是由标准库的实现者决定的。
如何选取扩容因子呢?
从空间角度:扩容因子越大,预留的空间就越大,浪费的空间也越多
从时间角度:扩展到相同长度下,K越小,扩容的次数越多,时间开销越大
假设扩容因子为k,扩容后的最终长度为n,这意味着需要扩容 $log_kn$ 次,
这元素复制和开辟内存的时间正比于: $t = 1 + k + k^2 + ... + k^{log_kn -1} = frac{n-1}{k-1}$,可见k越小越好。
如何达到时间和空间的平衡呢?
我们来看一下K = 2时的情况。
每次扩容后capacity的情况如下:1,2,4,8,16,32 ……..
当我们释放了4的空间,我们寻找8的新空间,再次扩容,释放8,寻找16。。
仔细分析,第5次扩容时,需要寻找16的新空间,第4次释放了8,第3次释放了4,第2次释放了2,第1次释放了1,所以 1 + 2 + 4 + 8 = 15 < 16,也就意味着,之前释放的空间,永远无法被下一次的扩容利用,这对内存与cache是非常不友好的。
我们再来看一下K = 1.5的情况。
每次扩容之后capacity的情况为:1,2,3,4,6,9,13,19,28 ……
再按刚才的思路分析一遍,1 + 2 >= 3; 2 + 3 + 4 >= 6; 6 + 9 >= 13 …….
所以,当K为1.5时,显然对内存和cache要友好很多,至少从容量上来说,是存在重复利用的可能性的。
因此,我们可以得出结论,当K = 2时,时间上要比 K = 1.5 占优,而空间上比 1.5 稍有劣势。
理论最优扩容因子是多少呢?
继续刚才的分析,我们希望的是,上几次的空间,存在被下一次扩容时利用的可能性。
也就是 X(n-2) + X(n-1) >= X(n),显然我们也希望时间上也要更好,即X(n-2) + X(n-1) = X(n)
即:1,2,3,5,8,13,21,34,55 。。。。
是不是很熟悉。。。是的,这就是我们的斐波那契数列。。。
那么当N趋于无限大时,取极限,最佳的扩容因子也就是那个最美的数,黄金分割率,1.618。
参考链接: