文章目錄
- 一:vector簡介
- 二:vector的創建和初始化
- 三:vector的遍歷
- 1.[]+下標
- 2.at()
- 3.迭代器遍歷
- 4.范圍for
- 四:vector的空間
- 1.size
- 2.max_size
- 3.capacity
- 4.reserve
- 5.resize
- 6.empty
- 五:vector的增刪查改
- 1.push_back
- 2.pop_back
- 3.find
- 4.insert
- 5.erase
- 6.swap
- 7.assign
Hello~同學們好,本文將深入探討 C++ 中的 vector 容器,作為標準模板庫(STL)中最常用的動態數組之一,vector 提供了靈活的元素存儲和高效的訪問方法。我們將從基礎知識入手,逐步學習其創建、初始化、遍歷、空間管理以及增刪查改等操作。通過詳細的示例和解析,希望能夠幫助讀者全面理解和掌握 vector 的使用技巧和注意事項。
一:vector簡介
vector文檔
- vector是表示可變大小數組的序列容器。
- 就像數組一樣,vector也采用的連續存儲空間來存儲元素。也就是意味著可以采用下標對vector的元素進行訪問,和數組一樣高效。但是又不像數組,它的大小是可以動態改變的,而且它的大小會被容器自動處理。
- 本質講,vector使用動態分配數組來存儲它的元素。當新元素插入時候,這個數組需要被重新分配大小為了增加存儲空間。其做法是,分配一個新的數組,然后將全部元素移到這個數組。就時間而言,這是一個相對代價高的任務,因為每當一個新的元素加入到容器的時候,vector并不會每次都重新分配大小。
- vector分配空間策略:vector會分配一些額外的空間以適應可能的增長,因為存儲空間比實際需要的存儲空間更大。不同的庫采用不同的策略權衡空間的使用和重新分配。但是無論如何,重新分配都應該是對數增長的間隔大小,以至于在末尾插入一個元素的時候是在常數時間的復雜度完成的。
- 因此,vector占用了更多的存儲空間,為了獲得管理存儲空間的能力,并且以一種有效的方式動態增長。
- 與其它動態序列容器相比(deque, list and forward_list), vector在訪問元素的時候更加高效,在末尾添加和刪除元素相對高效。對于其它不在末尾的刪除和插入操作,效率更低。比起list和forward_list統一的迭代器和引用更好。
使用STL的三個境界:能用,明理,能擴展 ,那么下面學習vector,我們也是按照這個方法去學習
二:vector的創建和初始化
要包頭文件#include<vector>
// 默認構造函數,創建一個空的 vectorvector<int> v1; // 創建一個包含 4 個默認初始化元素(值為 0)的 vectorvector<int> v2(4); // 創建一個包含 4 個元素,每個元素初始化為 10 的 vectorvector<int> v3(4, 10); // 使用迭代器范圍(v3 的起始和結束迭代器)初始化 vector,v4 將包含 v3 的所有元素vector<int> v4(v3.begin(), v3.end());// 拷貝構造函數,創建一個 v2 的副本vector<int> v5(v2); // 使用初始化列表創建 vector,v6 將包含 1, 2, 3, 4, 5, 6, 7 這些元素vector<int> v6 = {1, 2, 3, 4, 5, 6, 7}; // 使用 std::string 初始化string s1("12345"); // 創建一個包含 "12345" 的字符串 s1// 使用 std::string 的迭代器初始化 std::vector<int>// 這里每個 char 會隱式轉換為其對應的 int(ASCII 值)vector<int> v3(s1.begin(), s1.end()); // 使用字符串的迭代器初始化 vector<int>
vector和string的區別:
std::vector:
- 不自動添加 \0:
- std::vector 只是一個通用的動態數組容器,它不會在末尾自動添加 \0。你需要手動管理字符串的結束標志。
- 當你需要將 std::vector 轉換為 C 風格字符串時,你必須手動添加一個 \0。
std::string:
- 自動添加 \0:
- std::string 在內部管理一個以 \0 結尾的字符數組。這個空字符保證了字符串可以直接使用 C 風格字符串的函數。
- 當你創建或操作 std::string 對象時,\0 是自動添加和管理的,因此不需要手動處理。
三:vector的遍歷
1.[]+下標
void test_vector1()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}
看一下結果(push_back后面會將,顧名思義也就是尾插)
調試一下看看
2.at()
at() 函數用于訪問vector 中的元素,并進行邊界檢查。與 operator[] 不同,at() 會在訪問越界時拋出 std::out_of_range 異常,因此它比 operator[] 更安全,但稍微有點性能開銷。
#include <iostream> // 引入輸入輸出流頭文件
#include <vector> // 引入 vector 容器頭文件
#include <stdexcept> // 引入標準異常頭文件
using namespace std; // 使用標準命名空間
int main() {vector<int> vec = { 10, 20, 30, 40, 50 }; // 初始化一個包含五個整數的 vector// 正常訪問 vector 的元素try {for (size_t i = 0; i < vec.size(); ++i) {cout << "Element at index " << i << " is " << vec.at(i) << endl; // 使用 at() 函數訪問元素}}catch (const out_of_range& e) {cerr << "Out of range error: " << e.what() << endl; // 捕捉并處理越界訪問異常}// 嘗試訪問越界元素try {cout << "Element at index 10 is " << vec.at(10) << endl; // 訪問索引為 10 的元素,這將拋出異常}catch (const out_of_range& e) {cerr << "Out of range error————" << e.what() << endl; // 捕捉并處理越界訪問異常}return 0; // 返回 0 表示程序正常結束
}
3.迭代器遍歷
iterator的使用 | 接口說明 |
---|---|
begin +end(重點) | 獲取第一個數據位置的iterator/const_iterator 獲取最后一個數據的下一個位置 的iterator/const_iterator |
rbegin + rend | 獲取最后一個數據位置的reverse_iterator 獲取第一個數據前一個位置的 reverse_iterator |
迭代器訪問+修改
vector<int> v1; // 定義一個空的 vector 容器v1.push_back(1); // 向容器中添加元素 1v1.push_back(2); // 向容器中添加元素 2v1.push_back(3); // 向容器中添加元素 3v1.push_back(4); // 向容器中添加元素 4vector<int>::iterator it = v1.begin(); // 初始化迭代器,指向 vector 的起始位置// 使用 while 循環遍歷 vector 的元素while (it != v1.end()) { // 當迭代器未到達 vector 的末尾時繼續循環*it -= 10; // 將迭代器指向的元素值減去 10cout << *it << " "; // 打印當前元素值it++; // 迭代器指向下一個元素}cout << endl; // 輸出換行符
為什么 while (it != v1.end())不能用 it<v1.end()?
因為在 C++ 中,標準庫容器(如 std::vector)的迭代器支持比較操作,但通常是使用 != 而不是 < 來判斷迭代器是否已經到達容器的末尾。!= 比較更加直觀和符合語義。
使用 < 進行比較可能在某些情況下無效,因為并不是所有迭代器都支持 < 運算符。特別是,對于雙向迭代器或更復雜的迭代器(如關聯容器中的迭代器),它們不支持這種操作。
注意:vector<int>::iterator it = v1.begin();
要用vector::指明是什么類型的迭代器。
4.范圍for
void vector_Traversal_test() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);// 范圍forfor (auto e : v) {cout << e << " ";}cout << endl;for (auto& e : v) {e += 10;cout << e << " ";}cout << endl;//范圍for實際上是使用迭代器來遍歷容器的
//把上面兩個展開其實是下面兩個。for (auto it = v.begin(); it != v.end(); ++it){auto e = *it; // 通過迭代器獲取當前元素cout << e << " ";}cout << endl;for (auto it = v.begin(); it != v.end(); ++it) {auto& e = *it; // 通過迭代器獲取當前元素的引用e += 10; // 修改元素的值cout << e << " ";}cout << endl;
}
四:vector的空間
size | 獲取數據個數 | |
max_size | 容器所能容納的最大元素數量 | |
capacity | 獲取容量大小 | |
resize | 改變vector的size | |
reserve | 改變vector的capacity | |
empty | 判斷是否為空 |
1.size
size() 函數返回當前 vector 中的元素個數。
2.max_size
max_size() 函數返回 vector 可以容納的最大元素數量,這個數量通常是一個非常大的值,取決于系統限制和內存可用性。
3.capacity
capacity() 函數返回當前 vector 內部存儲空間的容量,即在重新分配之前可以存儲的元素數量。
4.reserve
reserve(n) 函數用于請求 vector 預留足夠的存儲空間,以容納至少 n 個元素。這樣做可以減少因為容器擴展而導致的重新分配操作,提高插入元素的效率。
如果已經確定vector中要存儲元素大概個數,可以提前將空間設置足夠,就可以避免邊插入邊擴容導致效率低下的問題了
void TestVectorExpandOP()
{vector<int> v;size_t sz = v.capacity();v.reserve(100); // 提前將容量設置好,可以避免一遍插入一遍擴容cout << "making bar grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}
5.resize
resize() 函數用于改變 vector 的大小,即容器中元素的數量。它可以根據傳入的參數 n,進行不同的操作:
- 當 n < size 時:
- 容器尾部多余的元素會被銷毀。
- capacity 不會改變。
- 當 size <= n <= capacity 時:
- 容器尾部新增加的元素被初始化為默認值(對于 int 類型,默認值是 0)。
- capacity 不會改變。
- 當 n > capacity 時:
- 容器尾部新增加的元素被初始化為默認值。
- size 和 capacity 都會變為 n,并且需要重新分配內存來擴展容器的存儲空間。
6.empty
empty() 函數檢查 vector 是否為空,如果 vector 中沒有元素,則返回 true,否則返回 false。
五:vector的增刪查改
push_back | 尾插 |
---|---|
pop_back | 尾刪 |
find | 查找(注意這個是算法模塊實現,不是vector的成員接口) |
insert | 在pos位置之前插入數據 |
erase | 刪除pos位置的數據 |
swap | 交換兩個vector的數據空間 |
assign | 用于將新值分配給向量的元素,替換當前內容,并修改向量的大小 |
1.push_back
vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);for (auto e : v){cout << e << " ";}cout << endl;vector<string> v1;v1.push_back(" we");v1.push_back(" all");v1.push_back(" love");v1.push_back(" C++");for (auto e : v1){cout <<e;}cout << endl;
2.pop_back
void test_vector5() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);for (auto e : v) cout << e << " "; cout << endl;v.pop_back(); // 尾刪:3for (auto e : v) cout << e << " "; cout << endl;v.pop_back(); // 尾刪:2for (auto e : v) cout << e << " "; cout << endl;
}
3.find
思考:為什么string、map、set、都有自己的find而vector和list沒有?
為什么 string、map、set 提供 find 操作?
- std::string:
- std::string 是一個字符序列,提供 find 操作用于子串查找,這是字符串操作中非常常見的需求。
- 例如,查找某個子串在字符串中的位置。
- std::map 和 std::set:
- std::map 和 std::set 是關聯容器,基于平衡二叉樹(如紅黑樹)實現。
- find 操作在這些容器中是核心功能,因為它們的主要用途就是快速查找鍵。
- std::map 提供鍵值對的查找,而 std::set 提供唯一鍵的查找。
- 這些容器的查找操作效率是 O(log n)。
為什么 vector 和 list 不提供 find 操作?- std::vector:
- std::vector 是一個動態數組,主要用于順序存儲和訪問。
- 查找操作的效率是 O(n),因為需要線性掃描整個數組。
- 提供 find 操作在效率上不占優勢,因此沒有直接提供。
- std::list:
- std::list 是一個雙向鏈表,適用于頻繁插入和刪除操作。
- 查找操作的效率同樣是 O(n),因為需要線性掃描鏈表。
- 和 vector 類似,提供 find 操作在效率上不占優勢,因此沒有直接提供。
#include <algorithm>
void test_vector9()
{vector<int> v;v.push_back(9);v.push_back(9);v.push_back(6);vector<int>::iterator it = find(v.begin(), v.end(),6);if (it != v.end()){cout << "找到啦" << endl;cout << *it << endl;}
}
4.insert
void test_vector9()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);auto it1 = v.begin() + 2;//在第三個位置插入v.insert(it1, 100);for (auto e : v){cout << e << " ";}//1 2 100 3 4 5 6cout << endl;auto it2 = v.begin() + 4;v.insert(it2, 3, 90);//在第五個位置插入3個90for (auto e : v){cout << e << " ";}//1 2 100 3 90 90 90 4 5 6cout << endl;vector<int> vec1 = { 1, 2, 6 };vector<int> vec2 = { 3, 4, 5 };auto it3 = vec1.begin() + 2; // 在第三個位置插入元素vec1.insert(it3, vec2.begin(), vec2.end()); // 在位置 it 處插入 vec2 的所有元素// 現在 vec1 = {1, 2, 3, 4, 5, 6}
}
注意:pos的類型都是iterator;
5.erase
vector<int> v {1, 2, 3, 4, 5};// 刪除第三個元素auto it = v.erase(v.begin() + 2);// v = {1, 2, 4, 5}for (auto elem : v) {cout << elem << " ";}cout << endl;// 刪除第二個到第四個元素auto first = v.begin() + 1;auto last = v.begin() + 4;v.erase(first, last);// v = {1, 5}for (auto elem : v) {cout << elem << " ";}cout << endl;
//注意:[first,last) 是左閉右開的區間所以last可以取v.begin() + 4
- 在調用 erase 后,被移除的元素會被析構,相關的內存也會被釋放。
- 對于 erase(iterator first, iterator last),注意 first 和 last 的有效性和順序,確保不會越界訪問或者非法操作。
- 刪除元素后,后續的元素會向前移動填補空缺,保持 vector 的連續性。
- erase 操作可能會導致迭代器失效,因此在使用返回的迭代器之前,要確保其仍然有效。
迭代器失效問題我會放在vector模擬實現(下一篇文章)詳細講解。
6.swap
std::vector 提供了一個成員函數 swap,用于交換兩個 vector 對象的內容。這個操作可以快速地交換兩個容器的元素,而不需要復制它們的內容。
vector<int> v1 {1, 2, 3};vector<int> v2 {4, 5, 6};// 使用 swap 交換兩個 vector 的內容v1.swap(v2);cout << "After swapping:\n";cout << "v1: ";for (auto elem : v1) {cout << elem << " "; //4 5 6}cout << "\nv2: ";for (auto elem : v2) {cout << elem << " "; //1 2 3}cout << endl;
效果和注意事項
- 內容交換:swap 函數會交換兩個 vector 對象的所有元素,包括它們的大小(size)和容量(capacity)。
- 高效性:swap 操作非常高效,因為它只涉及指針的交換,不需要復制元素。這對于大型的 vector 特別有用,可以在不重新分配內存的情況下快速交換數據。
- 迭代器和引用的影響:swap 操作不會使現有的迭代器、引用和指針失效,因此可以安全地在 swap 后繼續使用交換后的 vector 對象。
- 使用場景:swap 可以用于重新排序或重新組織數據,也可以用于優化內存使用,比如在算法中交換兩個 vector 來實現更高效的數據處理流程。
- 示例: 上面的示例展示了如何使用 swap 將兩個 vector 對象的內容進行交換,從而在輸出中顯示了交換后的結果。
總之,std::vector 的 swap 函數是一個非常有用的工具,能夠快速、高效地交換兩個 vector 對象的內容,適合在需要優化內存使用或者重新組織數據時使用。
7.assign
void demonstrate_assign() {// 創建一個空的 vector<int>vector<int> v;// 使用 assign(n, value) 方法賦值v.assign(5, 10); // 用5個10替換當前內容cout << "After v.assign(5, 10): ";for (int i : v) {cout << i << " "; // 輸出: 10 10 10 10 10}cout << endl;// 使用 assign(first, last) 方法賦值int arr[] = {1, 2, 3, 4, 5};v.assign(arr, arr + 3); // 用數組的前3個元素替換當前內容cout << "After v.assign(arr, arr + 3): ";for (int i : v) {cout << i << " "; // 輸出: 1 2 3}cout << endl;// 使用 vector 的迭代器范圍賦值vector<int> v2 = {7, 8, 9};v.assign(v2.begin(), v2.end()); // 用v2的所有元素替換當前內容cout << "After v.assign(v2.begin(), v2.end()): ";for (int i : v) {cout << i << " "; // 輸出: 7 8 9}cout << endl;
}
📜 [ 聲明 ] 由于作者水平有限,本文有錯誤和不準確之處在所難免,
本人也很想知道這些錯誤,懇望讀者批評指正!