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:\n"; for (int i=0; i<1000; ++i) { foo.push_back(i); //std::cout << "i: " << i << '\n'; if (sz!=foo.capacity()) { sz = foo.capacity(); std::cout << "capacity changed: " << sz << '\n'; } } std::cout << "foo size: " << foo.size() << '\n'; std::cout << "cap: " << foo.capacity() <<'\n'; sz = foo.capacity(); for(int i = 0;i < 1000;i++) { //std::cout << foo.pop_back() << '\n'; std::cout << "i: " << i << " cap: " << foo.capacity() << " size: " << foo.size()<< '\n'; foo.pop_back(); if (sz!=foo.capacity()) { sz = foo.capacity(); std::cout << "capacity changed: " << sz << '\n'; } } 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。
參考鏈接: