這篇文章不打算講述vector的基本用法,而是總結一下近期我大量閱讀C++經典書籍時遇到的一些關於vector的容易忽略的知識點,特意將它們記錄下來,以便以后查閱。
1.v[0]和v.at(0)的區別
void f(vector<int>& v)
{
v[0]; //A
v.at(0); //B
}
觀察該函數,我們使用A和B的形式來訪問v的元素,他們有什么區別?他們唯一區別就是如果v空則B會拋出std::out_of_range的異常,至於A行為標准未加任何說明。所以B方式可以防止越界操作,但是B方式較A方式效率要低(因為加入了越界檢查)。
2.resize()和reserve()是不同的操作
int main()
{
vector<int> v;
v.reserve(2);
cout << "capacity: " << v.capacity() << endl;
assert(v.capacity() == 2);
v[0] = 1; //A
v[1] = 2; //B
return 0;
}
上面的代碼是有問題的,我在VS2015下進行編譯運行時彈出“vector越界訪問”的錯誤。那該段程序錯在哪里?
- 首先這里的斷言可能會失敗。因為reserve操作將保證vector容量>=2。而且這里的斷言也是多余的。
- 然后A和B的復制是有問題的,因為該程序忽視了resize/size和reserve/capacity的區別。size()告訴你容器中實際有多少個元素,resize()則會在容器末尾添加或者刪除元素,使得容器達到指定大小;capacity()告訴你最少添加多少個元素才會導致容器的重新分配內存,而reserve()在必要時候總是容器內部緩沖區擴至一個更大的內容,reserve()並不改變容器中元素的數量,它僅影響vector預先分配多大的內存空間。
所以上面的代碼修改可以這么做:
int main()
{
vector<int> v;
v.resize(2);
v[0] = 1;
v[1] = 2;
return 0;
}
或者使用pushback()來添加元素。
3.vector增長的方式
上面第二點說了vector中size()和capacity()是不同的意思,造成這一現象的原因就是vector的的增長方式有些不同。用一句話來總結vector的的增長方式就是“重新配置,移動數據,釋放原空間”。
標准庫實現者為了盡量減少容器空間重新分配次數,他們采取這樣一種策略:當不得不獲取新的內存空間時,vector通常會分配比新的空間需求更大的內存空間。容器預留這些空間作為備用,可以用來保存更多的元素。這樣,就不需要每次添加新元素都重新分配容器空間了。上面說到“容器預留這些空間作為備用”,這里的預留空間時多少呢?根據《STL源碼剖析》的說法,當增加新元素時,如果超過當時的容量,則容量會擴充至原理容量的2倍;如果兩倍仍不足,就擴張到足夠大的容量。
vector中有三個重要迭代器:
以下圖像形象表示出vector的增長方式以及size與capacity的區別。
為了驗證vector內存容量的增長策略,我特意做了以下實驗。
以下代碼在centos 7下編譯運行,其顯示結果與《STL源碼剖析》說法一致。
#include <iostream>
#include <vector>
using namespace std;
//在g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11)下編譯運行
int main()
{
vector<int> v(2,9); //9 9
cout << "size=" << v.size() << endl; //size=2
cout << "capacity=" << v.capacity() << endl; //capacity=2
v.push_back(1); //9 9 1
cout << "size=" << v.size() << endl; //size=3
cout << "capacity=" << v.capacity() << endl; //capacity=4
v.push_back(2); //9 9 1 2
cout << "size=" << v.size() << endl; //size=4
cout << "capacity=" << v.capacity() << endl; //capacity=4
v.push_back(3); //9 9 1 2 3
cout << "size=" << v.size() << endl; //size=5
cout << "capacity=" << v.capacity() << endl; //capacity=8
v.push_back(4); //9 9 1 2 3 4
cout << "size=" << v.size() << endl; //size=6
cout << "capacity=" << v.capacity() << endl; //capacity=8
v.push_back(5); // 9 9 1 2 3 4 5
cout << "size=" << v.size() << endl; //size=7
cout << "capacity=" << v.capacity() << endl; //capacity=8
v.pop_back();
v.pop_back();
cout << "size=" << v.size() << endl; //size=5
cout << "capacity=" << v.capacity() << endl; //capacity=8
v.pop_back();
v.pop_back();
cout << "size=" << v.size() << endl; //size=3
cout << "capacity=" << v.capacity() << endl; //capacity=8
v.clear();
cout << "size=" << v.size() << endl; //size=0
cout << "capacity=" << v.capacity() << endl; //capacity=8
return 0;
}
但是在VS2015下編譯運行,效果就不一樣了。
查閱資料知道,這里容量增長方式是:每次擴容50%
依次看來,在VS2015下采用的標准庫版本與Linux下的版本應該是不一樣的。
4.迭代器失效情況總結
對於vector而言,添加和刪除操作可能使容器的部分或者全部迭代器失效。為什么迭代器會失效呢?vector元素在內存中是順序存儲,試想:如果當前容器中已經存在了10個元素,現在又要添加一個元素到容器中,但是內存中緊跟在這10個元素后面沒有一個空閑空間,而vector的元素必須順序存儲一邊索引訪問,所以我們不能在內存中隨便找個地方存儲這個元素。於是vector必須重新分配存儲空間,用來存放原來的元素以及新添加的元素:存放在舊存儲空間的元素被復制到新的存儲空間里,接着插入新的元素,最后撤銷舊的存儲空間。這種情況發生,一定會導致vector容器的所有迭代器都失效。
先看一個錯誤的例子:
vector<mission>::iterator itr = vm.begin();
while (itr != vm.end())
{
if ((*itr).getStartTime() <= nowTime)
{
vm.erase(itr);
}
itr++;
}
這段代碼運行起來會crash,其原因當然是迭代器失效了,我們還用了它。
因為在erase操作后,原迭代器是相當於一個野指針的狀態,對其++必定出錯。
而erase的返回值就是指向被刪除的元素的下一個元素的迭代器,我們沒必要再次++了。
正確寫法:
vector<mission>::iterator itr = vm.begin();
while (itr != vm.end())
{
if ((*itr).getStartTime() <= nowTime)
{
itr = vm.erase(itr);
}
else
{
itr++;
}
}
vector迭代器的幾種失效的情況:
- 當插入一個元素后,插入位置之后的元素迭代器肯定失效。
- 當插入一個元素后,capacity返回值與沒有插入元素之前相比有改變(即存儲空間重新分配),則需要重新加載整個容器,此時指向容器的迭代器都會失效。
- 當進行刪除操作(erase,pop_back)后,指向刪除點的迭代器全部失效;指向刪除點后面的元素的迭代器也將全部失效。