C++ vector使用以及底層核心剖析
在 C++ 標(biāo)準(zhǔn)庫(kù)中,vector 是最常用的容器之一,它本質(zhì)上是一個(gè)動(dòng)態(tài)順序表,兼具數(shù)組的隨機(jī)訪問(wèn)特性和動(dòng)態(tài)擴(kuò)容的靈活性。本文將從基礎(chǔ)使用、核心接口、迭代器失效、底層實(shí)現(xiàn)等維度,全面拆解 vector 的核心知識(shí)點(diǎn),幫你徹底吃透這個(gè)容器。
一、vector 基礎(chǔ)使用
1. 頭文件與初始化
使用 vector 首先需要包含頭文件<vector>,它的初始化方式靈活多樣,適配不同場(chǎng)景:
#include <iostream>
#include <vector>
using namespace std;
void test_vector_init() {
// 1. 空vector
vector<int> v1;
// 2. 初始化10個(gè)元素,每個(gè)元素值為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 高度相似,支持下標(biāo)、迭代器、范圍 for 三種方式:
void test_vector_traverse() {
vector<int> v{1, 2, 3, 4, 5};
// 方式1:下標(biāo)遍歷(隨機(jī)訪問(wèn)特性)
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個(gè)位置前插入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個(gè)位置的元素
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 支持嵌套定義實(shí)現(xiàn)二維數(shù)組,相比原生二維數(shù)組更靈活(每行長(zhǎng)度可不同):
void test_vector_2d() {
// 初始化10行5列的二維數(shù)組,每個(gè)元素初始值為1
vector<int> v(5, 1);
vector<vector<int>> vv(10, v);
// 修改第3行第2列的元素(下標(biāo)從0開(kāi)始)
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. 實(shí)用案例:楊輝三角
結(jié)合 vector 的初始化、resize、元素訪問(wèn)等特性,實(shí)現(xiàn) LeetCode 經(jīng)典題 “楊輝三角”:
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行開(kāi)辟i+1個(gè)空間,初始值為0
vv[i].resize(i+1, 0);
// 每行首尾元素設(shè)為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 核心接口對(duì)比與細(xì)節(jié)
1. reserve:僅擴(kuò)容,不初始化
vector 的reserve和 string 的reserve存在關(guān)鍵區(qū)別:
- 當(dāng)
n < size()時(shí),vector 的 reserve無(wú)任何操作; - string 的 reserve 若縮容(多數(shù)編譯器忽略),至少保證容量不低于 size ()。
2. resize:改變?cè)貍€(gè)數(shù),按需初始化
resize會(huì)直接修改 vector 的元素個(gè)數(shù),行為分三種情況:
n < size():刪除末尾元素,僅調(diào)整有效元素個(gè)數(shù);size() < n < capacity():在末尾插入元素,用默認(rèn)值初始化;n > capacity():先擴(kuò)容,再插入元素初始化。
注意:vector 默認(rèn)按元素類型的默認(rèn)值初始化(如 int 為 0),而 string 固定初始化為'\0'。
// resize接口原型(可指定初始化值) void resize (size_type n, value_type val = value_type());
3. 不支持重載 <<和>>
vector 沒(méi)有默認(rèn)的輸入輸出重載,需手動(dòng)實(shí)現(xiàn)輸入輸出邏輯:
// 單個(gè)元素輸入
vector<int> v;
int x;
cin >> x;
v.push_back(x);
// 多個(gè)元素輸入(初始化5個(gè)元素)
vector<int> v1(5, 0);
for (size_t i = 0; i < 5; i++) {
cin >> v1[i];
}三、迭代器失效
迭代器失效是 vector 使用中最容易出錯(cuò)的地方,本質(zhì)是迭代器指向的空間失效或指向位置的語(yǔ)義改變,主要分為兩類場(chǎng)景:
1. 擴(kuò)容導(dǎo)致的迭代器失效(野指針)
當(dāng) vector 插入元素觸發(fā)擴(kuò)容時(shí),原空間會(huì)被釋放,之前的迭代器變?yōu)橐爸羔槪?/p>
// 錯(cuò)誤示例:擴(kuò)容后迭代器失效
void test_vector_invalid1() {
vector<int> v{1,2,3,4};
// 插入位置迭代器
auto pos = v.begin() + 1;
// 插入元素觸發(fā)擴(kuò)容,原pos指向的空間被釋放
v.insert(pos, 5);
// 訪問(wèn)失效迭代器,結(jié)果未定義
cout << *pos << endl;
}解決方法:插入時(shí)記錄迭代器相對(duì)位置,擴(kuò)容后重新修正迭代器:
iterator insert(iterator pos, const T& x) {
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage) {
// 記錄相對(duì)位置
size_t len = pos - _start;
// 擴(kuò)容
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. 插入 / 刪除導(dǎo)致的迭代器語(yǔ)義失效
(1)insert 后迭代器語(yǔ)義失效
insert 會(huì)挪動(dòng)元素,原迭代器指向的位置語(yǔ)義改變(不再是原來(lái)的元素):
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);
// 錯(cuò)誤:修改的是40,而非原來(lái)的2
(*p) *= 10;
}
}解決方法:接收 insert 返回的新迭代器,重新定位:
auto p = find(v.begin(), v.end(), 2);
if (p != v.end()) {
// 接收新迭代器,指向插入的40
p = v.insert(p, 40);
// 修改原來(lái)的2(p+1位置)
(*(p + 1)) *= 10;
}(2)erase 后迭代器語(yǔ)義失效
erase 刪除元素后,原迭代器指向的位置變?yōu)橄乱粋€(gè)元素,若直接 ++ 會(huì)跳過(guò)元素或死循環(huán):
// 錯(cuò)誤示例:刪除所有偶數(shù),迭代器失效導(dǎo)致漏刪/死循環(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 返回的迭代器(指向刪除元素的下一個(gè)位置):
// 正確版本:刪除所有偶數(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; // 僅非刪除場(chǎng)景++
}
}
// 輸出:1 3
for (auto e : v) cout << e << " ";
}四、vector 底層實(shí)現(xiàn)關(guān)鍵細(xì)節(jié)
1. 迭代器區(qū)間構(gòu)造(模板復(fù)用)
vector 的迭代器區(qū)間構(gòu)造函數(shù)設(shè)計(jì)為模板函數(shù),支持任意容器的迭代器初始化(只要類型匹配):
// 類模板的成員函數(shù)可嵌套函數(shù)模板
template <class InputIterator>
vector(InputIterator first, InputIterator last) {
while (first != last) {
push_back(*first);
++first;
}
}
// 用法:僅拷貝v1的前3個(gè)元素
vector<int> v1{1,2,3,4,5};
vector<int> v2(v1.begin(), v1.begin()+3);2. 淺拷貝問(wèn)題修復(fù)
vector 擴(kuò)容時(shí)若用 memcpy 拷貝元素,會(huì)導(dǎo)致自定義類型(如 string)的淺拷貝問(wèn)題,需改用賦值運(yùn)算符:
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* tmp = new T[n];
// 錯(cuò)誤:memcpy是淺拷貝,自定義類型會(huì)析構(gòu)重復(fù)釋放
// memcpy(tmp, _start, size() * sizeof(T));
// 正確:調(diào)用T的賦值運(yùn)算符,深拷貝自定義類型
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. 內(nèi)置類型的 “偽構(gòu)造函數(shù)”
vector 的 resize 接口默認(rèn)參數(shù)為T(),為了適配內(nèi)置類型(如 int),C++ 編譯器會(huì)為內(nèi)置類型模擬構(gòu)造函數(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;
}
}
}
// 調(diào)用示例:int()等價(jià)于0,double()等價(jià)于0.0
v.resize(10); // 內(nèi)置類型用默認(rèn)值初始化4. const_iterator 的 typename 修飾
在模板中使用vector<T>::const_iterator時(shí),需加typename說(shuō)明這是類型(否則編譯器視為靜態(tài)成員):
// 錯(cuò)誤:編譯器無(wú)法區(qū)分const_iterator是類型還是成員 // vector<T>::const_iterator it = v.begin(); // 正確1:加typename說(shuō)明是類型 typename vector<T>::const_iterator it1 = v.begin(); // 正確2:用auto自動(dòng)推導(dǎo)(推薦) auto it2 = v.begin();
到此這篇關(guān)于C++ vector:從使用到底層核心剖析的文章就介紹到這了,更多相關(guān)C++ vector用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ 數(shù)據(jù)結(jié)構(gòu)之對(duì)稱矩陣及稀疏矩陣的壓縮存儲(chǔ)
這篇文章主要介紹了C++ 數(shù)據(jù)結(jié)構(gòu)之對(duì)稱矩陣及稀疏矩陣的壓縮存儲(chǔ)的相關(guān)資料,這里實(shí)現(xiàn)稀疏矩陣和對(duì)稱矩陣的壓縮存儲(chǔ)的實(shí)例,需要的朋友可以參考下2017-08-08
C++實(shí)現(xiàn)LeetCode(68.文本左右對(duì)齊)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(68.文本左右對(duì)齊),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
Qt開(kāi)發(fā)之使用socket實(shí)現(xiàn)遠(yuǎn)程控制
本篇文章將會(huì)介紹下位機(jī)通過(guò)心跳包和上位機(jī)之間進(jìn)行數(shù)據(jù)交互和遠(yuǎn)程功能控制的實(shí)現(xiàn)方法。文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-11-11
C語(yǔ)言中操作utmp文件的相關(guān)函數(shù)用法
這篇文章主要介紹了C語(yǔ)言中操作utmp文件的相關(guān)函數(shù)用法,包括getutent()函數(shù)和setutent()函數(shù)以及endutent()函數(shù),需要的朋友可以參考下2015-08-08

