C++ STL容器總結之vector(超詳細版)

一、vector簡介

vector的中文翻譯爲向量,是一種C++ STL中的序列容器。它的是存儲方式和C++語言本身提供的數組一樣都是順序存儲,因此vector的操作和數組十分相似。但是和數組不一樣的是,數組的存儲空間是靜態的,一旦配置了就不能改變,而vector的存儲空間是動態的,隨着元素的加入,它的內部機制會自動擴充空間以容納新元素,因此也被稱爲可變長數組。

二、vector存儲機制

vector的動態空間實現如下圖所示,
1
爲了減少空間配置的時間代價,通常vector配置的空間會比我們標註的需求量更大一些,於是vector總的存儲空間就如上圖所示分成了兩部分,其中工作空間是按我們標註的需求配置的,而備用空間就是額外配置的那一部分空間。

當需要擴充的新的空間時,vector會優先使用備用空間。例如我們現在要在末尾插入6,它會將 finishfinish 迭代器指向的空間用於存儲6,最後再調整 finishfinish 迭代器的位置。
2當備用空間不足以存放要插入的元素時,vector會在另外較大的空閒空間中配置一塊大小爲原來兩倍的新空間(由於不能保證原空間之後有足有的空閒空間,所以並不是直接在原空間後面接續),然後將原空間中的數據按順序遷移到新空間,最後釋放原來的存儲空間。這一過程可以簡單記憶爲重新配置、移動數據、釋放原空間

下面是以“在備用空間用完的情況下插入新元素3”爲例,用圖片形式描述前後的存儲空間變化。
在這裏插入圖片描述
由於以上過程對使用者而言都是透明的,所以需要非常注意的一點是,一旦vector的空間重新配置,所有指向原vector的迭代器都會失效!

三、vector創建和初始化

初始化比較簡單,我們可以根據自己的使用需求來相應的初始化方法。

//vector頭文件
#include <vector>
//創建一個空的vector
vector<int> vec1;
//創建一個有n個元素的vector,初始值都爲0
vector<int> vec2(n);
//創建一個有n個元素的vector,並將它們值都初始化爲x
vector<int> vec3(n, x);
//用vec3來初始化vec4,它們大小和元素的值都相同
vector<int> vec4(vec3);

四、vector基本操作詳解

很多人不明白爲什麼要了解容器本身實現的原理,覺得學會怎麼用就夠了。其實,學了容器的實現原理會對容器操作會有更加深刻的理解,同時也會對算法思想有或多或少的領悟

接下來,我們結合這張底層存儲示意圖仔細分析vector的基本操作。
4

  • 迭代器
    這裏簡單介紹下面將出現的兩種比較陌生的迭代器:
    • reverse_iteratorreverse\_iterator,稱爲反向迭代器,它的遞增方向和我們常用的 iteratoriterator 相反,例如 rit++rit++表示指向的位置往左移。
    • const_iteratorconst\_iterator,它不能改變所指向的元素的值,但是可以再指向其他元素,和const關鍵詞標註的 iteratoriterator 剛好相反。
//獲取第一個元素的迭代器,也就是圖中的start
vec.begin();

//獲取最後一個下個位置的迭代器,即圖中的finish
vec.end();

//返回vector<T>::reverse_iterator類型迭代器,指向finish-1
vec.rbegin();

//返回vector<T>::reverse_iterator類型迭代器,指向start的前一個位置
vec.rend();

//c++11,返回vector<T>::const_iterator類型迭代器,位置同圖中start
vec.cbegin();

//c++11,返回vector<T>::const_iterator類型迭代器,位置同圖中finish
vec.cend();
  • 容量
//獲取當前容量大小
vec.size();

//獲取包括備用空間在內的總容量大小
vec.capacity();

//最大容量,即最多可以存儲多少個當前類型元素
vec.max_size();

//判斷vector是否爲空,即判斷start == finish
vec.empty();

//重新設置vector的容量,大小爲n
vec.resize(n);
  • 元素訪問
    因爲vector也是順序存儲,所以它進行元素的隨機訪問的時間複雜度也爲 O(1)O(1)
//像數組一樣用索引進行隨機訪問,例如vec[0]
operator []

//作用同上,增加異常處理,越界拋出out of range
vec.at(index);

//返回一個元素,即迭代器start指向的元素
vec.front();

//返回最後一個元素,即迭代器finish-1指向的元素
vec.back();
  • 修改操作操作
    由於是順序存儲結構,隨機插入或隨機刪除都會引起元素的移動(對末尾元素操作除外),因此它們的時間複雜度都是 O(n)O(n)。下圖是刪除元素的底層實現示意,可以用於幫助我們的理解,從圖中也可以看出刪除操作不會引起總空間的變化。
    5
//元素的賦值值操作(類似於初始化)
//將vec賦值爲n個x
vec.assign(n, x);
//將[first, last)範圍內的元素賦值給vec
vec.assign(first, last);
//傳入參數爲數組指針,將數組[0, n)中的元素賦值給vec
vec.assign(array, array + n);

//將新元素x插入到finish所在的位置,並將迭代器finish後移,時間複雜度爲O(1)
vec.push_back(x);

//清除位於finish-1的元素,時間複雜度爲O(1)
vec.pop_back();

//時間複雜度爲O(n),position表示插入位置的迭代器
//在position插入新元素,其值爲x
vec.insert(position, x);
//在position插入n個新元素,它們的值都爲x
vec.insert(position, n, x);

//時間複雜度爲O(n),下列參數均爲迭代器
//清除某個特定位置的元素
vec.erase(position);
//清除[first, last)中的所有元素
vec.erase(first, last);

//交換vec和vec2中的所有元素,
vec.swap(vec2);

//清除所有的元素,事實上調用了erase(begin(), end())
vec.clear();
  • 遍歷操作
//順序遍歷
//最常用的方式
for (int i = 0; i < vec.size(); i++) cout << vec[i] << endl;
//利用迭代器
vector<int>::iterator it;
for (it = vec.begin(); it != vec.end(); i++) cout << *it << endl;
//c++11
for(auto x : vec) cout << x << endl;

//逆序遍歷
//常用方式
for (int i = size() - 1; i >= 0; i--) cout << vec[i] << endl;
//利用反向迭代器
vector<int>::reverse_iterator rit;
for (rit = vec.rbegin(); rit != vec.rend(); rit++) cout << *rit << endl;
  • 查找操作
    這個操作主要是利用STL提供的 findfind 函數。
#include <algorithm>
//需要調用find函數,在[first, last)中查找x,返回的是迭代器
vector<int>::iterator it = find(vec.begin(), vec.end(), x);
//如果找到則輸出YES,否則輸出NO
if (it != vec.end()) cout << "YES" << endl;
else cout << "NO" << endl;
  • 排序和翻轉
    主要是調用STL提供的 sortsortreversereverse 函數。
#include <algorithm>
//升序排序(默認)
sort(vec.begin(), vec.end());
sort(vec.begin(), vec.end(), less<int>());
//降序排序
sort(vec.begin(), vec.end(), greater<int>());

//元素翻轉
reverse(vec.begin(), vec.end());

五、參考資料

  1. C++官方的vector文檔
  2. 《STL源碼剖析》
  3. 個人混亂的實驗代碼

既然都看到就這裏了(我也終於寫到這裏了),希望你可以做到下面三點哦:

  • 點贊,這是對作者辛苦寫作的最低成本的鼓勵。
  • 答應我,把它學會!(別扔進收藏夾喫灰)
  • 可以考慮關注一波,STL系列的文章會持續更新。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章