【STL深入理解】vector


這篇文章不打算講述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越界訪問”的錯誤。那該段程序錯在哪里?

  1. 首先這里的斷言可能會失敗。因為reserve操作將保證vector容量>=2。而且這里的斷言也是多余的。
  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迭代器的幾種失效的情況:

  1. 當插入一個元素后,插入位置之后的元素迭代器肯定失效。
  2. 當插入一個元素后,capacity返回值與沒有插入元素之前相比有改變(即存儲空間重新分配),則需要重新加載整個容器,此時指向容器的迭代器都會失效。
  3. 當進行刪除操作(erase,pop_back)后,指向刪除點的迭代器全部失效;指向刪除點后面的元素的迭代器也將全部失效


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM