--- GA: G-RZYLL0RZGV --- ###### tags: `大一程設-下` `東華大學` `東華大學資管系` `基本程式概念` `資管經驗分享` vector,另一種比 array 還方便的陣列 === [TOC] ## 前言 <span style="color:red">**參考文章請見最下面,這兩篇寫得非常好!!**</span> 上學期我們學過了最基礎的一般陣列,有特別強調說,他是在靜態時期編譯起來你就必須給他直接長度的,不能透過輸入賦予長度,雖然在某些 compiler 會過,但其實這件事情是不被大家所允許的。 接下來的 vector 就是動態的配置陣列大小給陣列,不用去操心記憶體,或是程式出錯,完全會自動配置大小,是非常好用的一個工具。 而 vector 是 array 的進階版,所以他還是一種 array,仍舊是連續配置記憶體,當容量不夠的時候會重新申請空間,並把原本的資料複製或搬到新的記憶體去。 但這樣有點不好,如果今天我們一直往 vector 裡面丟東西,一旦容量不夠,他可能會一直搬來搬去,其實有點沒有效率,因為他會一直釋放資源,添加資源,所以應該減少他配置新空間的次數。 上面這句話簡單的說就是,針對需求,可以配置比需求**多一倍**的空間,會是比較適當的。 ## vector library 引入 要使用 vector,請務必要 `#include <vector>` 這個 Library 進來! ## vector 常用功能摘要 <span style="color:red;font-size:25px">**只是摘要,功能還有更多哦!**</span> 既然是陣列,他就是一種容器,空間就很重要,所以針對常用功能做摘要說明,下面會再細部的用例子帶大家理解。 * **Capacity,容量相關** * `size()` 回傳陣列(vector)目前的長度 * `empty()` 看陣列是不是空的,如果 size 為 0 則回傳 true/1,反之則回傳 false/0 * `reserve()` 預先配置大小 * **Modifiers,修飾器,針對放資料進陣列或從陣列移除資料** * `push_back()` 放資料到 vector 的最後面 * `pop_back()` 從最後面刪除一個資料 * `insert()` 插入資料進 vector * `erase()` 從 vector 內移除資料 * `clear()` 清空 vector * **Iterators,疊代器,用來把指針指到我們要的位置** * `begin()` 把疊代器指向第一個元素 * `end()` 把疊代器指向最後一個元素的下一個位置 * [What is the past-the-end iterator in STL C++?](https://stackoverflow.com/questions/15252002/what-is-the-past-the-end-iterator-in-stl-c) * **Element Access,存取元素** * `at(i)` 回傳參數索引值所在位置的值 * `front()` 回傳 vector 頭的值 * `back()` 回傳 vector 尾的值 * `[i]` 與 at 的用法相似,取得索引值位置的值 ## vector 宣告與初始化! ### Declaration 宣告 `vector<data_type> variable_name;` ```cpp= vector<int> arr_name; ``` vector 代表你要宣告一個 vector,<>符號裡面放的是你這個陣列要儲存的資料的型態,即每一格要放的資料型態,arr_name 就是為你的 vector 自訂一個名字。 ### Initialize 初始化 你可以看到上面這樣的宣告,好像沒有告訴說這個陣列應該要存多少個資料,因為前面有講,vector 可以自動處理大小,所以可以不用宣告大小,但我會覺得養成宣告大小的習慣會好一些! ```cpp= vector<int> arr_name(10); //一維陣列,有十個儲存空間,每個空間都必須存整數,值預設每一個都會存 0 ``` ### 宣告即附值與取大小 上面是只有宣告大小,那如果想要在宣告陣列即給值可以嗎? 當然可以! ```cpp= 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 成員函式。 ```cpp= vector<int> a({1,2,3,4,5,6,7,8}); cout << a.size(); // 當然會印出 8 ``` 在做值的初始化的時候,還有另一種寫法。 ```cpp= vector<int> a(5,100); vector<string> b(10,"orange"); ``` 這個寫法是說,第一個參數代表陣列的 size,第二個參數是這個陣列一被宣告的時候,裡面的每一格初始化為什麼資料。所以 a 有五格,每一格都是 100。 但請注意,<span style="color:red">**不要自作主張的寫成以下這樣**</span>。 ```cpp= vector<int> a(5,100,60,7,8,13); ``` 不要覺得說第一個數字既然是 size,那我後面是不是可以直接寫每一格的內容是甚麼,<span style="color:red">**沒有這種寫法,請千萬注意**</span> **在下方的參考文章內還有複製 vector 的寫法,但不是我覺得重要的點,有興趣歡迎去參考文章看看!** ## 基本的插入/刪除內容進 vector ### push_back & pop_back * push_back() * 括號內的內容須與你 vector 的儲存資料的型態相同 * push_back() 是把資料放到 vector 的最後面 * pop_back() * 括號內的內容須與你 vector 的儲存資料的型態相同 * pop_back() 是把資料從 vector 的最後面刪掉 ```cpp= #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; } ``` 但如果你想要初始化陣列的時候就給予大小,你要注意下面這樣的寫法。 ```cpp= 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 不是這樣運作的。 **上面的程式碼的話圖會像這樣。** ![](https://i.imgur.com/nwu4CXl.gif) 所以如果想要動前四個的內容,就是使用我們學過的中括號摟! ``` a[0] = 5; a[1] = 7; a[2] = 11; a[3] = 1; ``` 到這邊你可能會覺得,只能夠從最後面增加跟刪除,好像有一點點沒有效率耶。 對!所以 vector 還有更進階的能夠直接插入或刪除中間元素的方式,我們後面來談。 但在談他們之前,我們要先來理解一下 vector iterator 中的 begin() 跟 end()。 不過 iterator (疊代器) 是比較進階的 vector,所以如果你想挑戰,歡迎去旁邊挑戰一下 Boss -> [我是旁邊的 Boss](https://hackmd.io/@ndhu-programming-2021/rJlf9BTTY) 比較不行的同學,你可以搞懂下面就好。 ## 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 的冪次方成長下去。 ```cpp= 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 的差異。 ![](https://i.imgur.com/C5HydOv.png) ## vector 中 reserve() 的用法 ### reserve 預先配置容器 reserve 函式能夠讓你告訴程式 vector 的 capacity 要多大。但裡面沒有放任何東西,所以 size 會是 0。 ```cpp= int main(){ vector<int> a; a.reserve(10); cout << "size:" << a.size() << ", capacity:" << a.capacity(); } ``` ![](https://i.imgur.com/dJTJQTA.png) 所以直到你 push_back 到他 size 要比 capacity 大的時候,他才會再擴充空間喔。照上面的說明來看,當我今天 size 要變成 11 的時候,capacity 就會變成 20 摟! ### 宣告初始化長度與 reserve 的不同 如果你今天是在宣告的時候就給予 vector 長度,像這樣 `vector<int> a(10)`。則你的 size 跟 capacity 都會是 10 哦,原因是因為上面這樣的宣告方式,會把全部的格子初始化成 0,所以現在裡面的 10 格都放了 0。 但上面的 reserve 只有設定容器大小,還沒有在裡面放東西。 ```cpp= int main(){ vector<int> a(10); cout << "size:" << a.size() << ", capacity:" << a.capacity(); } ``` ![](https://i.imgur.com/TecbpkN.png) 希望大家不要搞混摟! ## vector shrink_to_fit() 容器收縮 今天既然有配置容器大小,那有沒有把多餘用不到的刪除這件事呢? 當然也是有的,就是依靠 shrink_to_fit() 這個成員函式。 ```cpp= 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; } ``` ![](https://i.imgur.com/vsYZHbF.png) 有沒有發現他相較於陣列真的方便很多,雖然語法可能剛開始學會難一點點,但在一維陣列的處理上真的方便許多,雖然 vector 的二維寫起來比較困難一點就是了。 ## vector 的 resize() 那今天有方法把多餘的刪除,那有沒有辦法在放了很多資料後,再把長度根據我們的需求改動呢? 一樣有的,就是依靠 resize() 成員函式。但他與 reserve 不同的是,多被配置出來的新空間,預設會補 0,至於你說 string、double 型態的 vector 預設會補上甚麼,大家可以自己測試。 ```cpp= 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; } ``` ![](https://i.imgur.com/PEZKklw.png) vector 的基礎就先介紹到這邊啦,當然這些不是全部,但我相信也夠你們學了,有興趣的歡迎把 vector 比較進階的應用也學起來哦。 ## vector 的優缺點! ### Advantages * 宣告時不用確定大小,由後續的開發配置長度 * 節省空間,更提供 shrink_to_fit() 函式供開發者省去空間 * 支持 random access,也就是可以依靠 [i] 去取得資料 * 插入比較方便 * 若要針對一搬的陣列做插入,要整個搬移。 ### Disadvantages * 插入與刪除比較複雜,且效率比較低落 * 你可能要搞懂疊代器(iterator) * 只能再末端 push 跟 pop,但正常來說開頭端也可以更好,像 queue 一樣。 ## Reference : vector 超棒文章以及推薦!! 請照順序看!! * [vector 基本介紹](https://shengyu7697.github.io/std-vector/) * [vector 實作二維陣列](https://ramihaha.tw/c-program-container-vector-array-linklist/) * [vector 的 C++ 官方文件](https://www.cplusplus.com/reference/vector/vector/)