在 C++ 标准库中,vector 是最常用的容器之一,它本质上是一个动态顺序表,兼具数组的随机访问特性和动态扩容的灵活性。本文将从基础使用、核心接口、迭代器失效、底层实现等维度,全面拆解 vector 的核心知识点,帮你彻底吃透这个容器。
一、vector 基础使用
1. 头文件与初始化
使用 vector 首先需要包含头文件<vector>,它的初始化方式灵活多样,适配不同场景:
cpp
#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的迭代器区间初始化v3(拷贝v2所有元素)
vector<int> v3(v2.begin(), v2.end());
// 4. 列表初始化(C++11及以上)
vector<int> v4{1, 2, 0};
}
2. 三种遍历方式
vector 的遍历和 string 高度相似,支持下标、迭代器、范围 for 三种方式:
cpp
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)是高频操作,需注意迭代器的使用规则:
cpp
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(二维数组)
vector 支持嵌套定义实现二维数组,相比原生二维数组更灵活(每行长度可不同):
cpp
void test_vector_2d() {
// 初始化10行5列的二维数组,每个元素初始值为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、元素访问等特性,实现 LeetCode 经典题 "杨辉三角":
cpp
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 核心接口对比与细节
1. reserve:仅扩容,不初始化
vector 的reserve和 string 的reserve存在关键区别:
- 当
n < size()时,vector 的 reserve无任何操作; - string 的 reserve 若缩容(多数编译器忽略),至少保证容量不低于 size ()。
2. resize:改变元素个数,按需初始化
resize会直接修改 vector 的元素个数,行为分三种情况:
n < size():删除末尾元素,仅调整有效元素个数;size() < n < capacity():在末尾插入元素,用默认值初始化;n > capacity():先扩容,再插入元素初始化。
注意 :vector 默认按元素类型的默认值初始化(如 int 为 0),而 string 固定初始化为'\0'。
cpp
// resize接口原型(可指定初始化值)
void resize (size_type n, value_type val = value_type());
3. 不支持重载 <<和>>
vector 没有默认的输入输出重载,需手动实现输入输出逻辑:
cpp
// 单个元素输入
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 插入元素触发扩容时,原空间会被释放,之前的迭代器变为野指针:
cpp
// 错误示例:扩容后迭代器失效
void test_vector_invalid1() {
vector<int> v{1,2,3,4};
// 插入位置迭代器
auto pos = v.begin() + 1;
// 插入元素触发扩容,原pos指向的空间被释放
v.insert(pos, 5);
// 访问失效迭代器,结果未定义
cout << *pos << endl;
}
解决方法:插入时记录迭代器相对位置,扩容后重新修正迭代器:
cpp
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 会挪动元素,原迭代器指向的位置语义改变(不再是原来的元素):
cpp
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 返回的新迭代器,重新定位:
cpp
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 删除元素后,原迭代器指向的位置变为下一个元素,若直接 ++ 会跳过元素或死循环:
cpp
// 错误示例:删除所有偶数,迭代器失效导致漏删/死循环
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 返回的迭代器(指向删除元素的下一个位置):
cpp
// 正确版本:删除所有偶数
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 底层实现关键细节
1. 迭代器区间构造(模板复用)
vector 的迭代器区间构造函数设计为模板函数,支持任意容器的迭代器初始化(只要类型匹配):
cpp
// 类模板的成员函数可嵌套函数模板
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)的浅拷贝问题,需改用赋值运算符:
cpp
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. 内置类型的 "伪构造函数"
vector 的 resize 接口默认参数为T(),为了适配内置类型(如 int),C++ 编译器会为内置类型模拟构造函数:
cpp
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说明这是类型(否则编译器视为静态成员):
cpp
// 错误:编译器无法区分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();