目錄
1. 什么是vector?
2. vector的使用
1. 構造函數---初始化
1. 默認構造函數(無參構造)
2. 填充構造函數(指定數量和初始值)
3. 范圍構造函數(通過迭代器拷貝其他容器元素)
4. 拷貝構造函數(直接拷貝另一個vector)
注意:
2.?vector成員函數的使用
1. 三種遍歷方法
2. size
3. capacity
4.?max_size
5. reserve
6. resize
7. assign
8. insert
9. find
10. sort
11. erase
3. 二維vector(重點)
1. 核心本質與優勢
2. 常見初始化方式
3. 核心操作(訪問、添加、刪除)
4. 遍歷二維?vector?
5. 注意事項
4. 總結:
1. 什么是vector?
- vector是表示可變大小數組的序列容器。
- 就像數組一樣,vector也采用的連續存儲空間來存儲元素。也就是意味著可以采用下標對vector的元素進行訪問,和數組一樣高效。但是又不像數組,它的大小是可以動態改變的,而且它的大小會被容器自動處理。
- 本質講,vector使用動態分配數組來存儲它的元素。當新元素插入時候,這個數組需要被重新分配大小為了增加存儲空間。其做法是,分配一個新的數組,然后將全部元素移到這個數組。就時間而言,這是一個相對代價高的任務,因為每當一個新的元素加入到容器的時候,vector并不會每次都重新分配大小。
- vector分配空間策略:vector會分配一些額外的空間以適應可能的增長,因為存儲空間比實際需要的存儲空間更大。不同的庫采用不同的策略權衡空間的使用和重新分配。但是無論如何,重新分配都應該是對數增長的間隔大小,以至于在末尾插入一個元素的時候是在常數時間的復雜度完成的。
- 因此,vector占用了更多的存儲空間,為了獲得管理存儲空間的能力,并且以一種有效的方式動態增長。
- 與其它動態序列容器相比(deques, lists and forward_lists), vector在訪問元素的時候更加高效,在末尾添加和刪除元素相對高效。對于其它不在末尾的刪除和插入操作,效率更低。比起lists和forward_lists統一的迭代器和引用更好。
2. vector的使用
我們先來看一下C++官方文檔中對vector成員函數的一些介紹 :?vector文檔
1. 構造函數---初始化
圖中詳細列出了?std::vector?的4種構造方式:
- 默認構造函數:創建空容器,無元素。
- 填充構造函數:創建包含?n?個元素的容器,每個元素都是?val?的副本。
- 范圍構造函數:用迭代器范圍?[first, last)?內的元素初始化容器。
- 拷貝構造函數:復制另一個?vector???x?的所有元素來初始化容器。
1. 默認構造函數(無參構造)
- 作用:創建一個空的vector容器,不包含任何元素,內部數組(動態內存)未分配或僅分配最小默認空間。
- 示例:?
std::vector<int> vec;?(此時vec.empty()為?true , vec.size()?為0)。
- 核心特點:極簡初始化,適合后續通過?push_back()?、?assign()?等方法動態添加元素的場景。
2. 填充構造函數(指定數量和初始值)
- 作用:創建一個包含固定數量(n個)元素的vector,且所有元素都初始化為同一個值(?val?的副本,若不指定?val?,內置類型默認初始化,如int為0)。
- 示例:
std::vector<int> vec(5, 10);?(創建含5個元素的vector,每個元素都是10)。 std::vector<int> vec(5); ?(創建含5個元素的vector,每個元素默認初始化為0)。
- 核心特點:一次性初始化“批量相同值”的元素,避免后續逐個賦值,適合已知元素數量和統一初始值的場景(如創建固定長度的初始數組)。
3. 范圍構造函數(通過迭代器拷貝其他容器元素)
- 作用:從其他容器(如vector、array、list等)的指定迭代器范圍?[first, last)??中,拷貝元素來初始化當前vector(左閉右開,即包含?first?指向的元素,不包含?last?指向的元素)。
- 示例:??
std::vector<int> src = {1,2,3,4}; std::vector<int> vec(src.begin(), src.end()); // 拷貝src所有元素,vec變為{1,2,3,4} std::vector<int> vec2(src.begin()+1, src.end()-1); // 拷貝src的[2,3],vec2變為{2,3}
- 核心特點:靈活復用其他容器的“部分或全部元素”,無需手動逐個拷貝,適合需要基于現有容器子集/全集創建新vector的場景。
4. 拷貝構造函數(直接拷貝另一個vector)
- 作用:創建一個完全復制另一個vector(?x?) 的新vector,新vector的元素、大小、容量(通常)與原vector完全一致。
- 示例:
std::vector<int> src = {1,2,3}; std::vector<int> vec(src); // 拷貝src,vec與src完全相同 std::vector<int> vec2 = src; // 等價于上一行,拷貝構造的另一種寫法
- 核心特點:專門用于“復制已有vector”,是C++默認的“值拷貝”語義體現,適合需要創建原vector副本(獨立于原vector,修改新vector不影響原vector)的場景。
注意:
- 可以看到第三個迭代器構造函數是一個模板函數,所以不一定只用vector的迭代器,也可以用其他容器迭代器初始化,只要數據類型匹配(*iterator對象的類型跟vector中存的數據類型是一致的):
- 迭代器進行初始化模板函數實際是這樣實現的:
// template <class InputIterator> 表明這是模板函數,InputIterator是迭代器類型參數
template <class InputIterator>
vector(InputIterator first, InputIterator last) {// 函數體內通過迭代器的通用操作(++、* 解引用)遍歷序列,無需關心具體迭代器類型while (first != last) {push_back(*first); // *first 取迭代器指向的元素++first; // 迭代器向后移動}
}
- 我們定義下面兩個對象有沒有差別?
string s("111111");
vector<char> vc(6,'1'); //調用構造函數
- 答 : 不能,vector里面給char,雖然它們底層都是數組中存char類型數據,但是還是不一樣的,s對象中指向的空間結尾有\0,string的很多操作是獨有的,比如+=字符串等等
2.?vector成員函數的使用
1. 三種遍歷方法
上面知道了vector類對象如何初始化,那么我們想要遍歷該對象該怎么遍歷呢?
首先使用push_back尾插進去數據,遍歷方法:
- 下標+[]
- 迭代器遍歷
- 范圍for遍歷
#include<iostream>
#include<vector>
using namespace std;
void test_vector()
{vector<int> v;//使用push_back尾插數據v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//遍歷vector//1、下標+[]for(size_t i =0;i<v.size(),i++){v[i]-=1;cout<<v[i]<<" ";}cout<<endl;//2、迭代器vector<int>::iterator it = v.begin();while(it!=v.end()){*it += 1;cout<<*it<<" ";++it;}cout<<endl;//范圍forfor(auto e:v){cout<< e <<" ";}cout<<endl;
}
int main()
{test_vector();return 0;
}
我們還可以利用反向迭代器進行反向遍歷:
void test_vector()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//反向迭代器進行遍歷vector<int>::reverse_iterator rit = v.rbegin();while(rit!=v.rend()){cout<<*rit<<" ";++rit;}cout<<endl;
}
這里的rit不是原生指針,而是被封裝的類對象,重載operator++才能實現++rit時,倒著走 , 而非原生質真的直接操作。
2. size
- size ?函數是用于獲取容器中當前元素個數的核心接口
- 返回 vector ?中實際存儲的元素數量,類型為 size_t (無符號整數)。
vector<int> v = {1,2,3};
size_t count = v.size(); // count = 3
3. capacity
capacity ?是 std::vector ?的一個重要屬性,它表示當前分配的內存能夠容納的最大元素個數(即不重新分配內存時,最多能存多少元素)。而 size ?是當前實際存儲的元素個數。
vector<int> v;
v.push_back(1);
v.push_back(2);
// 此時 size 是 2,capacity 可能是 2、4 或其他(由編譯器和標準庫實現決定,通常會預分配多于當前元素數的內存以減少擴容次數)
- 當 size ?超過 capacity ?時, vector ?會自動擴容(重新分配更大的內存塊,復制原有元素,釋放舊內存),這也是 vector ?動態增長的底層機制。可以通過 vector ?的 capacity() ?函數來獲取這個值。
4.?max_size
返回vector可以容納的最大元素數。實際中并沒有什么意義
void test_vector()
{vector<int> v;cout<<v.max_size()<<endl;//沒什么意義v.reserve(10);//開空間,改變容量
}
5. reserve
功能 : 主動為?vector?預分配內存,確保其?capacity?(容量)至少達到指定大小,不改變?size?(實際元素個數)。避免多次自動擴容(擴容會復制元素、釋放舊內存,耗時),適合已知元素大致數量時提升效率。若?n?≤當前?capacity?,函數不做任何操作;只增容,不縮容(縮容需用?shrink_to_fit()?)。
vector<int> vec;
vec.reserve(100); // 預分配能存100個int的內存,此時size=0,capacity≥100
for (int i=0; i<100; ++i)
{vec.push_back(i); // 因已預分配,這100次插入不會觸發自動擴容
}
6. resize
改變這個vector對象的長度為n,如果n小于當前vector的長度,則將當前值縮短到第n個數據,刪除第n個以外的數據。如果n大于當前vector對象長度,延長該vector對象長度,并在最后插入指定內容直到達到的延長后的長度n。如果指定值, 用該值來初始化,否則,他們初始化為匿名對象。
vector<int> v = {1,2,3};// 場景1:n > size → 新增元素,值為0(int的默認值)
v.resize(5); // 現在v的size是5,元素為[1,2,3,0,0]// 場景2:n > size 且指定默認值
v.resize(7, 99); // 元素變為[1,2,3,0,0,99,99]// 場景3:n < size → 銷毀末尾元素
v.resize(2); // 元素變為[1,2],capacity仍保持擴容后的大小(如7)
7. assign
分配新的內容給vector,代替它當前的內容,并且修改它的大小。可以看到assign函數的參數可以是迭代器,也可以是val個數和val
vector<int> v = {1,2,3};// 示例1:范圍賦值(從其他容器/數組批量賦值)
list<int> lst = {4,5,6};
v.assign(lst.begin(), lst.end()); // v變為[4,5,6]// 示例2:重復賦值(填充n個相同元素)
v.assign(3, 99); // v變為[99,99,99]
8. insert
用于在指定位置插入元素的核心接口,支持單個元素、多個元素或范圍元素的插入,在容器的指定位置插入元素,插入后容器 ?size??增加,若空間不足會觸發擴容。
調用格式(三種重載):
插入單個元素:?iterator insert(iterator pos, const T& val);?(pos是插入位置的迭代器,val是要插入的元素)。
插入多個相同元素:
?iterator insert(iterator pos, size_t n, const T& val);?(n是插入個數)
插入范圍元素:
iterator insert(iterator pos, InputIterator first, InputIterator last);(?first/last是迭代器范圍)
插入位置的后續元素會向后移動(若空間不足,先擴容再移動,移動過程有性能開銷)。
返回值是指向第一個插入元素的迭代器,可用于后續操作。
vector<int> v = {1,3,4};// 示例1:插入單個元素
auto it = v.insert(v.begin() + 1, 2); // 在索引1的位置插入2,v變為[1,2,3,4]// 示例2:插入多個相同元素
v.insert(v.end(), 2, 5); // 在末尾插入2個5,v變為[1,2,3,4,5,5]// 示例3:插入范圍元素
list<int> lst = {6,7};
v.insert(v.begin(), lst.begin(), lst.end()); // 在開頭插入lst的元素,v變為[6,7,1,2,3,4,5,5]
那么我們假設想在3的前面插入呢?我們想一想我們肯定先需要找到3這個元素,才能在它前面插入元素,而我們發現vector當中沒有find函數,但是在算法里面有一個find函數模板以提供使用:
9. find
find函數參數是迭代器區間以及需要找到的val值,返回的是這段區間第一次發現的元素的迭代器,如果沒有發現則返回的是last,我們想要在3之前插入元素數據20:
void test_vector5()
{int a[] = { 1,3,4 };vector<int> v(a, a + 3);vector<int>::iterator pos = find(v.begin(),v.end(),3);if(pos!= v.end()){v.insert(pos,20);}for(auto e:v){cout<< e <<" ";}cout<<endl;
}
輸出:
1 20 3 4
10. sort
在算法模塊還有一個函數便于我們使用:sort函數
核心參數傳遞的就是迭代器,且明確要求是隨機訪問迭代器
void test_vector6()
{int a[] = { 1,2,3,4,5 };vector<int> v(a, a + 5);//默認排升序sort(v.begin(),v.end());
}
我們還可以用sort對數組進行排序:
void test_vector6()
{//指向數組的空間的指針是天然的迭代器int a1[]={30,1,13,23,42};sort(a1,a1+5);//也可以對數組排序for(auto e:a1){cout<< e <<" ";}cout<<endl;
}
指向數組的空間的指針是天然的迭代器,故也是可以對數組進行排序的
11. erase
erase??是用于從容器中刪除指定元素或元素范圍的核心接口,刪除后容器 ?size??減小,剩余元素會自動前移,但不會自動縮減內存容量(?capacity??不變)。
erase??有兩種主要重載形式,均通過迭代器指定刪除范圍:
1. 刪除單個元素?iterator erase(iterator pos);?pos?:指向要刪除元素的有效迭代器(不能是 ?end()?)。
返回值:指向被刪除元素下一個元素的迭代器(用于避免迭代器失效)。
2. 刪除元素范圍?iterator erase(iterator first, iterator last);?first?/?last?:指定刪除范圍[first, last)(左閉右開,包含first指向的元素,不包含last指向的元素)。
返回值:指向范圍 ?[first, last)??之后第一個元素的迭代器。
#include <vector>
#include <iostream>int main() {std::vector<int> v = {1, 2, 3, 4, 5};// 示例1:刪除單個元素(刪除值為3的元素)auto it = v.begin() + 2; // 指向元素3it = v.erase(it); // 刪除后,it指向元素4,v變為 [1,2,4,5]// 示例2:刪除元素范圍(刪除從it開始到末尾的元素)v.erase(it, v.end()); // 刪除4、5,v變為 [1,2]// 示例3:釋放多余內存(size=2,capacity可能大于2)v.shrink_to_fit(); // 可選,將capacity縮減至與size一致return 0;
}
3. 二維vector(重點)
C++ 中的二維vector :(vector<vector<T>>)
二維 vector?本質是“元素類型為 ?vector??的 ?vector?”,可理解為“動態二維數組”,其每行長度可獨立變化(非嚴格矩形,也叫“鋸齒數組”),核心特性和用法如下:
1. 核心本質與優勢
- 本質:外層 ?vector??存儲的每個元素,都是一個獨立的內層 ?vector?(即“一行”數據)。
- 優勢:
- 動態大小:無需預先指定行數和列數,可隨時添加/刪除行、擴展每行的列數。
- 靈活性:每行長度可不同(如第一行3個元素,第二行5個元素),適合不規則數據。
- 內存安全:自動管理內存,無需手動釋放,避免數組越界和內存泄漏。
2. 常見初始化方式
(1) 直接初始化(指定行數和每行列數)
適用于創建“矩形二維數組”(每行長度相同):
#include <vector>
using namespace std;// 方式1:初始化3行4列,所有元素默認值(int默認0,string默認空串)
vector<vector<int>> vec1(3, vector<int>(4));?// 方式2:初始化2行3列,所有元素指定為10
vector<vector<int>> vec2(2, vector<int>(3, 10));?
// 結果:vec2 = [[10,10,10], [10,10,10]]
(2) 列表初始化(自定義每行元素)
適用于不規則或已知具體數據的場景:
// 方式1:初始化3行,每行長度分別為2、3、1
vector<vector<int>> vec = {{1, 2}, ? ?// 第0行{3, 4, 5}, // 第1行{6} ? ? ? ?// 第2行
};// 方式2:先創建空外層vector,再逐個添加行
vector<vector<int>> vec2;
vec2.push_back({10, 20}); // 添加第0行
vec2.push_back({30}); ? ? // 添加第1行
// 結果:vec2 = [[10,20], [30]]
3. 核心操作(訪問、添加、刪除)
(1) 訪問元素
通過“外層索引[行號] + 內層索引[列號]”訪問,與普通二維數組語法一致:
vector<vector<int>> vec = {{1,2}, {3,4,5}};
int a = vec[0][1]; // 訪問第0行第1列,a = 2
int b = vec[1][2]; // 訪問第1行第2列,b = 5// 推薦:用 at() 訪問(會做越界檢查,更安全)
int c = vec.at(0).at(1); // 效果同上,越界時拋異常
(2) 添加元素
- 添加一行:用 ?push_back()??直接添加一個內層 ?vector??作為新行。
- 添加一列(擴展某行):通過外層 ?vector??的索引找到目標行,再用 ?push_back()??給該行添加元素。
vector<vector<int>> vec = {{1,2}, {3,4}};// 1. 添加新行
vec.push_back({5,6,7}); // 結果:[[1,2], [3,4], [5,6,7]]// 2. 給第0行添加一個元素(擴展列)
vec[0].push_back(8); // 結果:[[1,2,8], [3,4], [5,6,7]]
(3)刪除元素
- 刪除最后一行:外層 ?vector??調用 ?pop_back()?。
- 刪除某一行:外層 ?vector??調用 ?erase()?,通過迭代器指定行。
- 刪除某行的某個元素:找到目標行,調用該行(內層 ?vector?)的 ?erase()?。
vector<vector<int>> vec = {{1,2,8}, {3,4}, {5,6,7}};// 1. 刪除最后一行
vec.pop_back(); // 結果:[[1,2,8], [3,4]]// 2. 刪除第0行(通過迭代器)
vec.erase(vec.begin()); // 結果:[[3,4]]// 3. 給第0行刪除第1個元素(列)
vec[0].erase(vec[0].begin() + 1); // 結果:[[3]]
4. 遍歷二維?vector?
(1) 普通 ?for??循環(依賴索引)
vector<vector<int>> vec = {{1,2}, {3,4,5}};
// 外層循環遍歷行,內層循環遍歷每行的列
for (int i = 0; i < vec.size(); ++i)
{ // vec.size() 是行數for (int j = 0; j < vec[i].size(); ++j) { // vec[i].size() 是第i行的列數cout << vec[i][j] << " ";}cout << endl;
}
(2) 范圍 ?for??循環(更簡潔)
for (auto& row : vec)
{ // 遍歷每一行(用引用避免拷貝)for (auto& elem : row) { // 遍歷當前行的每個元素cout << elem << " ";}cout << endl;
}
5. 注意事項
- ?內存連續性:與普通二維數組(內存連續)不同,二維 ?vector??的每行(內層 ?vector?)是獨立的內存塊,整體內存不連續,訪問效率略低于普通二維數組。
- 行長度一致性:若需嚴格的“矩形二維數組”,需手動保證每行 ?size??相同(可封裝函數檢查或初始化時統一設置)。
- 性能優化:頻繁添加行時,可先給外層 ?vector??用 ?reserve(n)??預分配行數;頻繁擴展某行時,給對應內層 ?vector??預分配列數,減少擴容開銷。
典型使用場景:
- 存儲不規則數據(如每行元素個數不同的表格、稀疏矩陣)。
- 動態調整大小的場景(如刷題時不確定輸入數據的行數和列數)。
- 作為函數參數傳遞(需注意傳遞方式,推薦用引用 ?const vector<vector<T>>&??避免拷貝)。
4. 總結:
本文介紹了C++中vector容器的基本概念和使用方法。vector是可變大小的序列容器,采用連續存儲空間,支持高效隨機訪問和動態增長。文章詳細講解了vector的四種構造方式、常用成員函數(如size、capacity、resize、assign等)、遍歷方法(下標、迭代器、范圍for)以及二維vector的使用。重點內容包括:vector的動態擴容機制、元素訪問和修改操作、二維vector的初始化和遍歷(支持不規則數據結構)。vector相比數組和鏈表在隨機訪問和尾部操作上更高效,但在中間插入刪除時性能較低,適合需要頻繁訪問和動態調整大小的場景。
感謝大家的觀看!