vector 是 C++ 標準庫中的一個動態數組容器(Sequence Container),它可以自動管理內存大小,可以在運行時根據需要動態增長或縮小。它是一個非常常用且強大的容器,用於存儲一系列元素。可以簡單的認爲,vector是一個能夠放任意類型的動態數組。
下面詳細介紹 vector
的使用方法,並提供相應的代碼案例。
1,基本函數實現
1.1 構造函數
構造函數是初始化一個數組。vector本質是類模板,可以存儲任何類型的數據。vector在聲明前需要加上數據類型,而vector則通過模板參量設定類型。
vector():創建一個空vector vector(int nSize):創建一個vector,元素個數爲nSize vector(int nSize,const t& t):創建一個vector,元素個數爲nSize,且值均爲t vector(const vector&):複製構造函數 vector(begin,end):複製[begin,end)區間內另一個數組的元素到vector中
示例:
vector<int> vec; // 創建一個空數組vec vector<int> vec(1, 2, 3, 4, 5, 6);//vec中的內容爲1, 2, 3, 4, 5, 6 vector<int> vec(4); // 開闢四個空間,值默認爲0 vector<int> vec(5, 4); //創建5個值爲4的數組 vector<int> vec(a);//聲明並用a向量初始化vec向量 vector<int> vec(a.begin(), a.end()); // 將a的值從頭開始到尾複製 vector<int> vec(a.rbegin(), a.rend()); // 將a的值從尾到頭複製 int a[5]={1,2,3,4,5}; vector<int> vec(a,a+5);//將a數組的元素用來初始化vector向量 vector<int> vec(&a[1],&a[4]);//將a[1]-a[4]範圍內的元素作爲vec的初始值
1.2 增加函數
即向vector中插入元素。其中emplace_back的效果和push_back一樣,都是尾部插入元素。二者的差別在於底部實現的機制不同;push_back是將這個元素拷貝或移動到容器中(如果是拷貝的話,事後會自行銷燬先前創建的這個元素);而emplace_back在實現時,則是直接在容器尾部創建這個元素,省去了拷貝或移動元素的過程,所以emplace_back的速度更快。
void push_back(const T& x):向量尾部增加一個元素X iterator insert(iterator it,const T& x):向量中迭代器指向元素前增加一個元素x iterator insert(iterator it,int n,const T& x):向量中迭代器指向元素前增加n個相同的元素x iterator insert(iterator it,const_iterator first,const_iterator last):向量中迭代器指向元素前插入另一個相同類型向量的[first,last)間的數據
示例:
//在vector的末尾插入新元素 vec.push_back(1); //在迭代器的前面插入新元素 vector<int>::iterator it; it=vec.begin(); vec.insert(it,5);//在第一個元素前面插入5 //在vector中加入3個1元素,同時清除掉以前的元素 vec.assign(3,1);//現在vector中只有3個1
assgin修改vector,和insert操作類似,不過insert是從尾部插入,而assign則將整個vector改變。
1.3 刪除函數
erase是通過迭代器刪除某個或某個範圍的元素,並返回下一個元素的迭代器。
iterator erase(iterator it):刪除向量中迭代器指向元素 iterator erase(iterator first,iterator last):刪除向量中[first,last)中元素 void pop_back():刪除向量中最後一個元素 void clear():清空向量中所有元素
示例:
//刪除最後一個元素 vec.pop_back(); //刪除指定位置的元素 vec.erase(vec.begin());//刪除第一個位置的元素值 //清除所有元素 vec.clear(); //判斷該數組是否爲空 vec.empty();
1.4 遍歷函數
reference at(int pos):返回pos位置元素的引用 reference front():返回首元素的引用 reference back():返回尾元素的引用 iterator begin():返回向量頭指針,指向第一個元素 iterator end():返回向量尾指針,指向向量最後一個元素的下一個位置 reverse_iterator rbegin():反向迭代器,指向最後一個元素 reverse_iterator rend():反向迭代器,指向第一個元素之前的位置
遍歷數組示例:
//向數組一樣利用下標進行訪問 vector<int> a; for(int i=0;i<a.size();i++){ cout<<a[i]; } //利用迭代器進行訪問 vector<int>::iterator it; for(it=a.begin();it!=a.end();it++){ cout<<*it; }
1.5 vector容量和大小
顧名思義,size表示當前有多少個元素,capacity是可容納的大小,因爲vector是順序存儲的,那麼和數組一樣,有一個初始容量,在vector裏就是capacity。capacity必然大於等於size,每次擴容時會改變,具體大小和vector底層實現機制有關。
max_size 是可存儲的最大容量,和實際的編譯器,系統有關,使用的比較少。
empty就是判斷vector是否爲空,其實就是判斷size是否等於0。定義vector時設定了大小,resize修改大小等操作,vector都不爲空,clear後,size=0,那麼empty判斷就爲空。
bool empty() const:判斷向量是否爲空,若爲空,則向量中無元素 int size() const:返回向量中元素的個數 int capacity() const:返回當前向量所能容納的最大元素值 int max_size() const:返回最大可允許的vector元素數量值
2,容器特性和屬性
2.1 容器的特性
2.1.1,順序序列(Sequential Container)
順序容器中的元素按照嚴格的線性順序排序。可以通過元素在序列中的位置訪問對應的元素。
std::vector
是C++標準庫中的一種順序序列容器,這意味着它按照元素的順序進行存儲和訪問。- 每個元素都有一個唯一的位置(索引),可以通過索引直接訪問,同時支持迭代器用於循環訪問。
2.1.2,動態數組(Dynamic Array)
支持對序列中的任意元素進行快速直接訪問,甚至可以通過指針算述進行該操作。提供了在序列末尾相對快速地添加/刪除元素的操作。
std::vector
是一個動態數組容器,它在內部使用動態分配的數組來存儲元素。- 它能夠動態調整數組的大小,可以在運行時根據需要增加或減少元素的數量,而無需預先指定數組的大小。
2.1.3,能夠感知內存分配器的(Allocator-aware)
容器使用一個內存分配器對象來動態地處理它的存儲需求。
std::vector
是 allocator-aware 的,這意味着它能夠感知並與特定的內存分配器協同工作。- 使用者可以通過傳遞自定義的內存分配器(通常是一個模板參數),以實現對內存分配和釋放過程的控制。
以下是一個簡單的示例,演示了 std::vector
的這三個特性:
#include <iostream> #include <vector> int main() { // 創建一個空的vector std::vector<int> myVector; // 添加元素 myVector.push_back(10); myVector.push_back(20); myVector.push_back(30); // 遍歷並輸出元素 for (const auto& num : myVector) { std::cout << num << " "; } return 0; }
在這個例子中,std::vector
被用作順序序列,存儲了動態數組,並且它的內存管理是由C++標準庫的分配器機制處理的。
2.2 容器的屬性
下面介紹一下 std::vector
的基本屬性,以及一些示例。
2.2.1 動態調整大小:
std::vector
是一個動態大小的容器,可以在運行時根據需要自動調整大小。這意味着你可以隨時向vector
中添加或刪除元素,而無需擔心數組大小的固定性。它會自動處理內存管理。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector; // 創建一個空的vector myVector.push_back(1); // 添加元素 myVector.push_back(2); myVector.push_back(3); std::cout << "Vector的大小: " << myVector.size() << std::endl; // 輸出vector的大小 return 0; }
2.2.2 快速隨機訪問
std::vector
支持隨機訪問,即通過索引直接訪問元素。這使得在訪問和修改元素時效率非常高。 由於元素的連續存儲(底層實現是數組),std::vector 支持快速的隨機訪問。你可以通過索引直接訪問向量中的元素。
示例:
#include <iostream> #include <vector> int main() { std::vector<std::string> names = {"Alice", "Bob", "Charlie"}; // 隨機訪問元素 std::cout << "第二個元素是: " << names[1] << std::endl; return 0; }
2.2.3 動態內存管理
std::vector
負責管理其內部的動態內存,因此你無需手動分配或釋放內存。當元素被添加或刪除時,vector
會自動處理內存的分配和釋放。當向量的大小超過其當前容量時,會動態分配更多的內存以容納更多的元素。這可以確保在添加元素時不會頻繁重新分配內存。
#include <iostream> #include <vector> int main() { std::vector<double> prices; // 添加元素 prices.push_back(10.5); prices.push_back(20.0); prices.push_back(15.75); // 遍歷元素並輸出 for (const auto& price : prices) { std::cout << "價格: " << price << std::endl; } return 0; }
總結: std::vector
是C++中一個強大且靈活的容器,它提供了動態大小、隨機訪問、動態內存管理和豐富的功能。通過合理利用std::vector
,你可以更輕鬆地處理動態數據集合,而無需手動管理內存或擔心固定數組大小的限制。除此之外,還有下面屬性:
- 1,連續內存存儲:std::vector中的元素在內存中是連續存儲的,這意味着可以通過指針算術直接訪問元素,並且對於許多算法來說,這種存儲方式是高效的,有助於提高訪問效率。
- 2,尾部插入和刪除: 向量支持在尾部進行快速的元素插入和刪除操作。
- 3,快速尾部操作: 由於元素的尾部插入和刪除是快速的,std::vector 適用於需要在序列的末尾執行大量操作的場景。
- 4,STL 算法和迭代器支持: std::vector 與 C++ 標準模板庫(STL)的其他部分無縫集成,可以使用算法和迭代器對向量進行操作。你可以使用迭代器來遍歷
std::vector
中的元素。
2.3 如何獲取vector的內存
在C++中,std::vector
是一個封裝了動態數組的容器,它自動處理內存的分配和釋放。要獲取 std::vector
的底層內存地址,你可以使用 data()
成員函數,它返回指向容器第一個元素的指針。
以下是一個示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 獲取底層內存地址 int* ptr = myVector.data(); // 輸出每個元素及其內存地址 for (size_t i = 0; i < myVector.size(); ++i) { std::cout << "元素 " << myVector[i] << " 的內存地址: " << &myVector[i] << std::endl; } // 輸出整個vector的底層內存地址 std::cout << "Vector的底層內存地址: " << ptr << std::endl; return 0; }
請注意,通過 data()
獲取的指針指向的是 vector 中第一個元素的地址。如果 vector 是空的,data()
返回的是一個合法但未定義的指針,因此在使用之前應確保 vector 不爲空。
注意:一般情況下,直接使用 data()
獲取 vector 的底層內存並進行操作可能不是一個良好的實踐,因爲 std::vector
提供了許多高級的函數和方法,可以更安全和方便地訪問元素。直接訪問內存可能會引發未定義的行爲,特別是在修改元素時。
2.3.1 疑問:獲取內存使用 myVector.data() 和 myVector[0]的區別是什麼?
myVector.data()
和 myVector[0]
返回的是不同類型的指針,並且有不同的用途。
myVector.data()
:
myVector.data()
返回指向 vector 第一個元素的指針,是一個原始指針(T*
類型,其中T
是 vector 中元素的類型)。- 這個指針允許你直接訪問 vector 的底層內存,但是需要小心使用,因爲它沒有邊界檢查和安全保障。
- 在對
data()
返回的指針進行操作時,務必確保 vector 不爲空。
int* ptr = myVector.data(); // 對 ptr 進行直接內存操作
myVector[0]
:
myVector[0]
是 vector 的運算符重載,它返回 vector 中第一個元素的引用。- 這個引用允許你直接訪問 vector 的第一個元素,並且通過引用操作進行修改。它提供了邊界檢查,確保你訪問的是有效的元素。
int& ref = myVector[0]; // 對 ref 進行直接操作,修改會影響 vector 中的元素
選擇使用的場景:
- 如果你需要直接訪問 vector 中第一個元素的底層內存,並且要進行原始指針的操作,可以使用
myVector.data()
。 - 如果你只是想獲取第一個元素的值,並且可能會進行修改,使用
myVector[0]
更直觀且更安全,因爲它提供了引用而不是裸指針,並且有邊界檢查。
3,C++ vector 底層實現機制
這裏參考的是:https://c.biancheng.net/view/6901.html
STL 衆多容器中,vector是最常用的容器之一,其底層所採用的數據結構非常簡單,就只是一段連續的線性內存空間。
3.1 vector源碼分析
通過分析vector容器的源碼就可以看到,他就是使用三個迭代器來表示:
//_Alloc 表示內存分配器,此參數幾乎不需要我們關心 template <class _Ty, class _Alloc = allocator<_Ty>> class vector{ ... protected: pointer _Myfirst; pointer _Mylast; pointer _Myend; };
其中,_Myfirst 指向的是 vector 容器對象的起始字節位置;_Mylast 指向當前最後一個元素的末尾字節;_myend 指向整個 vector 容器所佔用內存空間的末尾字節。
下圖演示了以上這 3 個迭代器分別指向的位置。
如上圖所示,通過這 3 個迭代器,就可以表示出一個已容納 2 個元素,容量爲 5 的 vector 容器。
在此基礎上,將 3 個迭代器兩兩結合,還可以表達不同的含義,例如:
- _Myfirst 和 _Mylast 可以用來表示 vector 容器中目前已被使用的內存空間;
- _Mylast 和 _Myend 可以用來表示 vector 容器目前空閒的內存空間;
- _Myfirst 和 _Myend 可以用表示 vector 容器的容量。
- 對於空的 vector 容器,由於沒有任何元素的空間分配,因此 _Myfirst、_Mylast 和 _Myend 均爲 null。
通過靈活運用這 3 個迭代器,vector 容器可以輕鬆的實現諸如首尾標識、大小、容器、空容器判斷等幾乎所有的功能,比如:
template <class _Ty, class _Alloc = allocator<_Ty>> class vector{ public: iterator begin() {return _Myfirst;} iterator end() {return _Mylast;} size_type size() const {return size_type(end() - begin());} size_type capacity() const {return size_type(_Myend - begin());} bool empty() const {return begin() == end();} reference operator[] (size_type n) {return *(begin() + n);} reference front() { return *begin();} reference back() {return *(end()-1);} ... };
3.2 vector 擴大容量的本質
上面有說到果,vector作爲容器有着動態數組的功能,當加入的數據大於vector容量(capacity)時會自動擴容,系統會自動申請一片更大的空間,把原來的數據拷貝過去,釋放原來的內存空間。
另外需要指明的是,當 vector 的大小和容量相等(size==capacity)也就是滿載時,如果再向其添加元素,那麼 vector 就需要擴容。vector 容器擴容的過程需要經歷以下 3 步:
- 完全棄用現有的內存空間,重新申請更大的內存空間;
- 將舊內存空間中的數據,按原有順序移動到新的內存空間中;
- 最後將舊的內存空間釋放。
這也就解釋了,爲什麼 vector 容器在進行擴容後,與其相關的指針、引用以及迭代器可能會失效的原因。
由此可見,vector 擴容是非常耗時的。爲了降低再次分配內存空間時的成本,每次擴容時 vector 都會申請比用戶需求量更多的內存空間(這也就是 vector 容量的由來,即 capacity>=size),以便後期使用。vector 容器擴容時,不同的編譯器申請更多內存空間的量是不同的。以 VS 爲例,它會擴容現有容器容量的 50%。
4,使用示例
std::vector
是一個非常有用的標準庫容器,它提供了動態數組的功能,可以根據需要自動調整大小。以下是一些關於 std::vector
的基本示例:
4.1:引入頭文件
首先,使用的話需要引入頭文件 <vector>
#include <vector>
4.2:創建 vector 對象
直接使用vector
模板類來創建一個 vector 對象。可以創建存儲特定類型元素的 vector,格式爲: vector<數據類型> 名字
。例如:
vector<int> myVector; // 創建一個存儲整數的 vector,名字爲myVector vector<char> myVector; // 創建一個存儲字符的 vector,名字爲myVector vector<string> myVector; // 創建一個存儲字符串的 vector,名字爲myVector ……
4.3:創建 vector並添加元素
使用push_back()函數將元素添加到vector的末尾,默認且只能添加到末尾。
代碼如下:
#include <iostream> #include <vector> int main() { // 創建一個空的 vector std::vector<int> numbers; // 向 vector 中添加元素 numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); // 訪問 vector 中的元素 std::cout << "Vector elements: "; for (int i = 0; i < numbers.size(); ++i) { std::cout << numbers[i] << " "; } std::cout << std::endl; return 0; }
打印結果如下:
Vector elements: 1 2 3
4.4:使用範圍循環遍歷 vector
代碼如下:
#include <iostream> #include <vector> int main() { // 創建一個 vector,並初始化 std::vector<std::string> fruits = {"Apple", "Banana", "Orange"}; // 也可以向 vector 中添加元素 fruits.push_back("pear"); // 使用範圍循環遍歷 vector std::cout << "Fruits: "; for (const auto& fruit : fruits) { std::cout << fruit << " "; } std::cout << std::endl; return 0; }
結果如下:
Fruits: Apple Banana Orange pear
4.5:插入和刪除元素
#include <iostream> #include <vector> int main() { // 創建一個 vector,並初始化 std::vector<double> prices = {10.5, 20.3, 15.0}; // 直接在尾部插入元素 prices.push_back(88); // 在指定位置插入元素 prices.insert(prices.begin() + 1, 25.8); // 刪除指定位置的元素 prices.erase(prices.begin() + 2); // 打印 vector 元素 std::cout << "Prices: "; for (const auto& price : prices) { std::cout << price << " "; } std::cout << std::endl; return 0; }
結果:
Prices: 10.5 25.8 15 88
4.6:使用迭代器訪問數組
除了直接使用for循環遍歷訪問數組之外,我們也可以利用迭代器將容器中數組輸出。
首先聲明一個迭代器,vector<int>::iterator it; 來訪問vector容器,目的是遍歷或者指向vector容器的元素。
#include <iostream> #include <vector> int main() { // 創建一個 vector,並初始化 std::vector<int> numbers = {1, 2, 3, 4, 5}; // 使用迭代器訪問 vector 中的元素 std::cout << "Vector elements: "; for (auto it = numbers.begin(); it != numbers.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; return 0; }
結果如下:
Vector elements: 1 2 3 4 5