C++ vector使用以及底層核心剖析
在 C++ 標準庫中,vector 是最常用的容器之一,它本質上是一個動態(tài)順序表,兼具數(shù)組的隨機訪問特性和動態(tài)擴容的靈活性。本文將從基礎使用、核心接口、迭代器失效、底層實現(xiàn)等維度,全面拆解 vector 的核心知識點,幫你徹底吃透這個容器。
一、vector 基礎使用
1. 頭文件與初始化
使用 vector 首先需要包含頭文件<vector>,它的初始化方式靈活多樣,適配不同場景:
#include <iostream>
#include <vector>
using namespace std;
void test_vector_init() {
// 1. 空vector
vector<int> v1;
// 2. 初始化10個元素,每個元素值為1
vector<int> v2(10, 1);
// 3. 用v2的迭代器區(qū)間初始化v3(拷貝v2所有元素)
vector<int> v3(v2.begin(), v2.end());
// 4. 列表初始化(C++11及以上)
vector<int> v4{1, 2, 0};
}2. 三種遍歷方式
vector 的遍歷和 string 高度相似,支持下標、迭代器、范圍 for 三種方式:
void test_vector_traverse() {
vector<int> v{1, 2, 3, 4, 5};
// 方式1:下標遍歷(隨機訪問特性)
for (size_t i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
// 方式2:迭代器遍歷
vector<int>::iterator it = v.begin();
while (it != v.end()) {
cout << *it << " ";
++it;
}
cout << endl;
// 方式3:范圍for(C++11及以上)
for (auto e : v) {
cout << e << " ";
}
cout << endl;
}3. 插入與刪除
vector 的插入(insert)和刪除(erase)是高頻操作,需注意迭代器的使用規(guī)則:
void test_vector_insert_erase() {
vector<int> v(10, 1);
// 尾插元素2
v.push_back(2);
// 頭插元素3(insert僅支持迭代器傳參)
v.insert(v.begin(), 3);
// 在第5個位置前插入4(迭代器偏移)
v.insert(v.begin() + 5, 4);
for (auto e : v) {
cout << e << " "; // 輸出:3 1 1 1 1 4 1 1 1 1 1 1 2
}
cout << endl;
// 尾刪元素
v.pop_back();
// 刪除第5個位置的元素
v.erase(v.begin() + 5);
for (auto e : v) {
cout << e << " "; // 輸出:3 1 1 1 1 1 1 1 1 1 1
}
cout << endl;
}4. 二維 vector(二維數(shù)組)
vector 支持嵌套定義實現(xiàn)二維數(shù)組,相比原生二維數(shù)組更靈活(每行長度可不同):
void test_vector_2d() {
// 初始化10行5列的二維數(shù)組,每個元素初始值為1
vector<int> v(5, 1);
vector<vector<int>> vv(10, v);
// 修改第3行第2列的元素(下標從0開始)
vv[2][1] = 2;
// 遍歷二維vector
for (size_t i = 0; i < vv.size(); i++) {
for (size_t j = 0; j < vv[i].size(); j++) {
cout << vv[i][j] << " ";
}
cout << endl;
}
}5. 實用案例:楊輝三角
結合 vector 的初始化、resize、元素訪問等特性,實現(xiàn) LeetCode 經典題 “楊輝三角”:
class Solution {
public:
vector<vector<int>> generate(int numRows) {
// 初始化numRows行的二維vector
vector<vector<int>> vv(numRows);
for(size_t i = 0; i < numRows; i++) {
// 第i行開辟i+1個空間,初始值為0
vv[i].resize(i+1, 0);
// 每行首尾元素設為1
vv[i].front() = vv[i].back() = 1;
}
// 填充中間元素
for(int i = 1; i < vv.size(); i++) {
for(int j = 1; j < vv[i].size()-1; j++) {
vv[i][j] = vv[i-1][j-1] + vv[i-1][j];
}
}
return vv;
}
};二、vector 核心接口對比與細節(jié)
1. reserve:僅擴容,不初始化
vector 的reserve和 string 的reserve存在關鍵區(qū)別:
- 當
n < size()時,vector 的 reserve無任何操作; - string 的 reserve 若縮容(多數(shù)編譯器忽略),至少保證容量不低于 size ()。
2. resize:改變元素個數(shù),按需初始化
resize會直接修改 vector 的元素個數(shù),行為分三種情況:
n < size():刪除末尾元素,僅調整有效元素個數(shù);size() < n < capacity():在末尾插入元素,用默認值初始化;n > capacity():先擴容,再插入元素初始化。
注意:vector 默認按元素類型的默認值初始化(如 int 為 0),而 string 固定初始化為'\0'。
// resize接口原型(可指定初始化值) void resize (size_type n, value_type val = value_type());
3. 不支持重載 <<和>>
vector 沒有默認的輸入輸出重載,需手動實現(xiàn)輸入輸出邏輯:
// 單個元素輸入
vector<int> v;
int x;
cin >> x;
v.push_back(x);
// 多個元素輸入(初始化5個元素)
vector<int> v1(5, 0);
for (size_t i = 0; i < 5; i++) {
cin >> v1[i];
}三、迭代器失效
迭代器失效是 vector 使用中最容易出錯的地方,本質是迭代器指向的空間失效或指向位置的語義改變,主要分為兩類場景:
1. 擴容導致的迭代器失效(野指針)
當 vector 插入元素觸發(fā)擴容時,原空間會被釋放,之前的迭代器變?yōu)橐爸羔槪?/p>
// 錯誤示例:擴容后迭代器失效
void test_vector_invalid1() {
vector<int> v{1,2,3,4};
// 插入位置迭代器
auto pos = v.begin() + 1;
// 插入元素觸發(fā)擴容,原pos指向的空間被釋放
v.insert(pos, 5);
// 訪問失效迭代器,結果未定義
cout << *pos << endl;
}解決方法:插入時記錄迭代器相對位置,擴容后重新修正迭代器:
iterator insert(iterator pos, const T& x) {
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage) {
// 記錄相對位置
size_t len = pos - _start;
// 擴容
reserve(capacity() == 0 ? 4 : 2 * capacity());
// 修正迭代器指向新空間
pos = _start + len;
}
// 元素后移 + 插入新元素
iterator end = _finish - 1;
while (end >= pos) {
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
// 返回新插入元素的迭代器
return pos;
}2. 插入 / 刪除導致的迭代器語義失效
(1)insert 后迭代器語義失效
insert 會挪動元素,原迭代器指向的位置語義改變(不再是原來的元素):
void test_vector_invalid2() {
vector<int> v{1,5,2,3,4};
auto p = find(v.begin(), v.end(), 2);
if (p != v.end()) {
// 插入后p不再指向2,而是指向新插入的40
v.insert(p, 40);
// 錯誤:修改的是40,而非原來的2
(*p) *= 10;
}
}解決方法:接收 insert 返回的新迭代器,重新定位:
auto p = find(v.begin(), v.end(), 2);
if (p != v.end()) {
// 接收新迭代器,指向插入的40
p = v.insert(p, 40);
// 修改原來的2(p+1位置)
(*(p + 1)) *= 10;
}(2)erase 后迭代器語義失效
erase 刪除元素后,原迭代器指向的位置變?yōu)橄乱粋€元素,若直接 ++ 會跳過元素或死循環(huán):
// 錯誤示例:刪除所有偶數(shù),迭代器失效導致漏刪/死循環(huán)
void test_vector_invalid3() {
vector<int> v{1,2,3,4};
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
v.erase(it); // 刪除后it失效
}
++it; // 失效迭代器++,行為未定義
}
}解決方法:接收 erase 返回的迭代器(指向刪除元素的下一個位置):
// 正確版本:刪除所有偶數(shù)
void test_vector_erase_fix() {
vector<int> v{1,2,3,4};
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
// 接收返回值,更新迭代器
it = v.erase(it);
} else {
++it; // 僅非刪除場景++
}
}
// 輸出:1 3
for (auto e : v) cout << e << " ";
}四、vector 底層實現(xiàn)關鍵細節(jié)
1. 迭代器區(qū)間構造(模板復用)
vector 的迭代器區(qū)間構造函數(shù)設計為模板函數(shù),支持任意容器的迭代器初始化(只要類型匹配):
// 類模板的成員函數(shù)可嵌套函數(shù)模板
template <class InputIterator>
vector(InputIterator first, InputIterator last) {
while (first != last) {
push_back(*first);
++first;
}
}
// 用法:僅拷貝v1的前3個元素
vector<int> v1{1,2,3,4,5};
vector<int> v2(v1.begin(), v1.begin()+3);2. 淺拷貝問題修復
vector 擴容時若用 memcpy 拷貝元素,會導致自定義類型(如 string)的淺拷貝問題,需改用賦值運算符:
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* tmp = new T[n];
// 錯誤:memcpy是淺拷貝,自定義類型會析構重復釋放
// memcpy(tmp, _start, size() * sizeof(T));
// 正確:調用T的賦值運算符,深拷貝自定義類型
for (size_t i = 0; i < old_size; i++) {
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = _start + old_size;
_end_of_storage = _start + n;
}
}3. 內置類型的 “偽構造函數(shù)”
vector 的 resize 接口默認參數(shù)為T(),為了適配內置類型(如 int),C++ 編譯器會為內置類型模擬構造函數(shù):
void resize(size_t n, T val = T()) {
if (n < size()) {
_finish = _start + n;
} else {
reserve(n);
while (_finish < _start + n) {
*_finish = val;
++_finish;
}
}
}
// 調用示例:int()等價于0,double()等價于0.0
v.resize(10); // 內置類型用默認值初始化4. const_iterator 的 typename 修飾
在模板中使用vector<T>::const_iterator時,需加typename說明這是類型(否則編譯器視為靜態(tài)成員):
// 錯誤:編譯器無法區(qū)分const_iterator是類型還是成員 // vector<T>::const_iterator it = v.begin(); // 正確1:加typename說明是類型 typename vector<T>::const_iterator it1 = v.begin(); // 正確2:用auto自動推導(推薦) auto it2 = v.begin();
到此這篇關于C++ vector:從使用到底層核心剖析的文章就介紹到這了,更多相關C++ vector用法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++?使用getline()從文件中讀取一行字符串方法示例
這篇文章主要介紹了C++?使用getline()從文件中讀取一行字符串方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09
在matlab中實現(xiàn)for循環(huán)的方法
for循環(huán)用來循環(huán)處理數(shù)據(jù),break用于終止離它最近的一層for循環(huán),continue用于跳過離它最近的一層for循環(huán),接著執(zhí)行下一次循環(huán),本文重點給大家介紹在matlab中實現(xiàn)for循環(huán)的方法,感興趣的朋友一起看看吧2021-11-11
關于c++編譯protobuf時提示LNK2001 無法解析的外部符號的問題
這篇文章主要介紹了關于c++編譯protobuf時提示LNK2001 無法解析的外部符號的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12

