Try   HackMD
tags: 大一程設-下 東華大學 東華大學資管系 基本程式概念 資管經驗分享

vector,另一種比 array 還方便的陣列

前言

參考文章請見最下面,這兩篇寫得非常好!!

上學期我們學過了最基礎的一般陣列,有特別強調說,他是在靜態時期編譯起來你就必須給他直接長度的,不能透過輸入賦予長度,雖然在某些 compiler 會過,但其實這件事情是不被大家所允許的。

接下來的 vector 就是動態的配置陣列大小給陣列,不用去操心記憶體,或是程式出錯,完全會自動配置大小,是非常好用的一個工具。

而 vector 是 array 的進階版,所以他還是一種 array,仍舊是連續配置記憶體,當容量不夠的時候會重新申請空間,並把原本的資料複製或搬到新的記憶體去。

但這樣有點不好,如果今天我們一直往 vector 裡面丟東西,一旦容量不夠,他可能會一直搬來搬去,其實有點沒有效率,因為他會一直釋放資源,添加資源,所以應該減少他配置新空間的次數。

上面這句話簡單的說就是,針對需求,可以配置比需求多一倍的空間,會是比較適當的。

vector library 引入

要使用 vector,請務必要 #include <vector> 這個 Library 進來!

vector 常用功能摘要

只是摘要,功能還有更多哦!

既然是陣列,他就是一種容器,空間就很重要,所以針對常用功能做摘要說明,下面會再細部的用例子帶大家理解。

  • Capacity,容量相關

    • size() 回傳陣列(vector)目前的長度
    • empty() 看陣列是不是空的,如果 size 為 0 則回傳 true/1,反之則回傳 false/0
    • reserve() 預先配置大小
  • Modifiers,修飾器,針對放資料進陣列或從陣列移除資料

    • push_back() 放資料到 vector 的最後面
    • pop_back() 從最後面刪除一個資料
    • insert() 插入資料進 vector
    • erase() 從 vector 內移除資料
    • clear() 清空 vector
  • Iterators,疊代器,用來把指針指到我們要的位置

  • Element Access,存取元素

    • at(i) 回傳參數索引值所在位置的值
    • front() 回傳 vector 頭的值
    • back() 回傳 vector 尾的值
    • [i] 與 at 的用法相似,取得索引值位置的值

vector 宣告與初始化!

Declaration 宣告

vector<data_type> variable_name;

vector<int> arr_name;

vector 代表你要宣告一個 vector,<>符號裡面放的是你這個陣列要儲存的資料的型態,即每一格要放的資料型態,arr_name 就是為你的 vector 自訂一個名字。

Initialize 初始化

你可以看到上面這樣的宣告,好像沒有告訴說這個陣列應該要存多少個資料,因為前面有講,vector 可以自動處理大小,所以可以不用宣告大小,但我會覺得養成宣告大小的習慣會好一些!

vector<int> arr_name(10); //一維陣列,有十個儲存空間,每個空間都必須存整數,值預設每一個都會存 0

宣告即附值與取大小

上面是只有宣告大小,那如果想要在宣告陣列即給值可以嗎? 當然可以!

vector<int> a({1,2,3,4,5,6,7,8}); //宣告一個名為 a 的 vector,其容量為 8 // a[0] = 1, a[1] = 2, a[2] = 3,...依此類推,大家都會

請注意,這樣寫就不用再宣告長度了,因為他就依據設置的值就可以知道容量多少,請記得 vector 可以動態配置大小。

今天這樣的一個 vector,可以怎麼取得長度呢?就是使用前面說的 size 成員函式。

vector<int> a({1,2,3,4,5,6,7,8}); cout << a.size(); // 當然會印出 8

在做值的初始化的時候,還有另一種寫法。

vector<int> a(5,100); vector<string> b(10,"orange");

這個寫法是說,第一個參數代表陣列的 size,第二個參數是這個陣列一被宣告的時候,裡面的每一格初始化為什麼資料。所以 a 有五格,每一格都是 100。

但請注意,不要自作主張的寫成以下這樣

vector<int> a(5,100,60,7,8,13);

不要覺得說第一個數字既然是 size,那我後面是不是可以直接寫每一格的內容是甚麼,沒有這種寫法,請千萬注意

在下方的參考文章內還有複製 vector 的寫法,但不是我覺得重要的點,有興趣歡迎去參考文章看看!

基本的插入/刪除內容進 vector

push_back & pop_back

  • push_back()
    • 括號內的內容須與你 vector 的儲存資料的型態相同
    • push_back() 是把資料放到 vector 的最後面
  • pop_back()
    • 括號內的內容須與你 vector 的儲存資料的型態相同
    • pop_back() 是把資料從 vector 的最後面刪掉
#include <vector> using namespace std; int main(){ vector<int>a; // 宣告一個空的陣列,目前大小空間為 0 a.push_back(5); // a 的 size 為 1 a.push_back(7); // a 的 size 為 2 a.push_back(3); // a 的 size 為 3 vector<string>b; b.push_back("orange"); // b 的 size 為 1 b.push_back("apple"); // b 的 size 為 2 b.push_back("banana"); // b 的 size 為 3 a.pop_back(); // 放在 a 最後面的此時是 3,所以 3 被刪除 a.size(); // 此時 a 的 size 為 2 a.pop_back(); // 放在 a 最後面的此時是 7,所以 7 被刪除 a.size(); // 此時 a 的 size 為 1 return 0; }

但如果你想要初始化陣列的時候就給予大小,你要注意下面這樣的寫法。

int main(){ vector<int> a(4); a.push_back(3); // 此時 a[4] 也就是第五格,資料為 3 a.push_back(5); // 此時 a[5] 也就是第六格,資料為 5 a.push_back(9); // 此時 a[6] 也就是第七格,資料為 9 }

你可能會納悶為甚麼,因為 vector 在宣告即給大小的時候,他就配置的大小為 4 格的陣列出來,每一格都是 0,所以我們此時若 push_back,他會再加一個元素進來,成為第五位成員。

所以不要傻傻地覺得宣告沒有設值他會乖乖地從 0 開始哦,vector 不是這樣運作的。

上面的程式碼的話圖會像這樣。

所以如果想要動前四個的內容,就是使用我們學過的中括號摟!

a[0] = 5;
a[1] = 7;
a[2] = 11;
a[3] = 1;

到這邊你可能會覺得,只能夠從最後面增加跟刪除,好像有一點點沒有效率耶。
對!所以 vector 還有更進階的能夠直接插入或刪除中間元素的方式,我們後面來談。

但在談他們之前,我們要先來理解一下 vector iterator 中的 begin() 跟 end()。

不過 iterator (疊代器) 是比較進階的 vector,所以如果你想挑戰,歡迎去旁邊挑戰一下 Boss -> 我是旁邊的 Boss

比較不行的同學,你可以搞懂下面就好。

vector 奇妙混淆學習者的特性 - capacity

vector 中 size 與 capacity 的差異

vector 的 capacity 是有必要好好談論的,他和 size 是完全不一樣的東西哦。

capacity 是取得目前 vector 配置的空間大小,而 size 是取得目前 vector 中有的元素個數。你可以想像成一個能夠裝 10 個東西的箱子(capacity),他目前裡面有 3 樣東西(size)。

而如果我們一直朝 vector 內放東西,放到滿了之後,因為 vector 會自動配置大小,所以他會擴充他的容量。而每次的擴充都會是當前 capacity 的 2 倍或 1.5 倍,取決於你的 compiler。
而容量會是 1、2、4、8、16、32,以 2 的冪次方成長下去。

int main(){ vector<double> a; cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); return 0; }

所以上面的程式輸出如下,希望你能了解 capacity 跟 size 的差異。

vector 中 reserve() 的用法

reserve 預先配置容器

reserve 函式能夠讓你告訴程式 vector 的 capacity 要多大。但裡面沒有放任何東西,所以 size 會是 0。

int main(){ vector<int> a; a.reserve(10); cout << "size:" << a.size() << ", capacity:" << a.capacity(); }

所以直到你 push_back 到他 size 要比 capacity 大的時候,他才會再擴充空間喔。照上面的說明來看,當我今天 size 要變成 11 的時候,capacity 就會變成 20 摟!

宣告初始化長度與 reserve 的不同

如果你今天是在宣告的時候就給予 vector 長度,像這樣 vector<int> a(10)。則你的 size 跟 capacity 都會是 10 哦,原因是因為上面這樣的宣告方式,會把全部的格子初始化成 0,所以現在裡面的 10 格都放了 0。

但上面的 reserve 只有設定容器大小,還沒有在裡面放東西。

int main(){ vector<int> a(10); cout << "size:" << a.size() << ", capacity:" << a.capacity(); }

希望大家不要搞混摟!

vector shrink_to_fit() 容器收縮

今天既然有配置容器大小,那有沒有把多餘用不到的刪除這件事呢? 當然也是有的,就是依靠 shrink_to_fit() 這個成員函式。

int main(){ vector<int> a; a.reserve(10); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; a.push_back(1); a.shrink_to_fit(); cout << "size:" << a.size() << ", capacity:" << a.capacity() << endl; }

有沒有發現他相較於陣列真的方便很多,雖然語法可能剛開始學會難一點點,但在一維陣列的處理上真的方便許多,雖然 vector 的二維寫起來比較困難一點就是了。

vector 的 resize()

那今天有方法把多餘的刪除,那有沒有辦法在放了很多資料後,再把長度根據我們的需求改動呢? 一樣有的,就是依靠 resize() 成員函式。但他與 reserve 不同的是,多被配置出來的新空間,預設會補 0,至於你說 string、double 型態的 vector 預設會補上甚麼,大家可以自己測試。

int main(){ vector<int> a({1,2,3,4,5,6}); cout << "size:" << a.size() << "capacity:" << a.capacity() << endl; //假設此時我們收到更進階的需求,知道需要 15 格。 a.resize(15); cout << "size:" << a.size() << "capacity:" << a.capacity() << endl; return 0; }

vector 的基礎就先介紹到這邊啦,當然這些不是全部,但我相信也夠你們學了,有興趣的歡迎把 vector 比較進階的應用也學起來哦。

vector 的優缺點!

Advantages

  • 宣告時不用確定大小,由後續的開發配置長度
  • 節省空間,更提供 shrink_to_fit() 函式供開發者省去空間
  • 支持 random access,也就是可以依靠 [i] 去取得資料
  • 插入比較方便
    • 若要針對一搬的陣列做插入,要整個搬移。

Disadvantages

  • 插入與刪除比較複雜,且效率比較低落
    • 你可能要搞懂疊代器(iterator)
  • 只能再末端 push 跟 pop,但正常來說開頭端也可以更好,像 queue 一樣。

Reference : vector 超棒文章以及推薦!!

請照順序看!!