C++ vector 详解:从入门到模拟实现,一文搞懂动态数组
C++ vector 详解:从入门到模拟实现,一文搞懂动态数组
本文系统讲解 C++ STL 中
std::vector的全部核心知识:从基础API到底层扩容机制,再到手写模拟实现,适合所有C++学习者阅读。
一、前言
在 C++ 的 STL(Standard Template Library)中,vector 无疑是用得最频繁的容器之一。它提供了动态数组的能力,在保持连续内存布局的同时,自动管理内存的分配和释放。
无论你是刚学完 C 语言数组、准备过渡到 C++ 的初学者,还是已经在刷 LeetCode、写项目的进阶开发者——理解 vector 的方方面面,都是 C++ 学习中无法绕过的必修课。
本文特点:
- 分层讲解:从基础用法到底层原理,你可以按需跳读
- 大量可运行代码:每一个接口都配有完整的示例代码
- 模拟实现:手写一个
MyVector,帮助你真正理解底层发生了什么 - 常见面试题覆盖:扩容机制、迭代器失效、
emplace_backvspush_back等高频考点
在正式开始之前,建议你先收藏本文,方便随时查阅。
二、vector 是什么?
2.1 官方定义
std::vector 是 C++ 标准库中的一个顺序容器(Sequence Container),它在内部维护了一个动态分配的连续数组。
template<
class T,
class Allocator = std::allocator<T>
> class vector;
2.2 核心特性
| 特性 | 说明 |
|---|---|
| 连续存储 | 元素在内存中连续存放,支持 O(1) 随机访问 |
| 动态扩容 | 容量不足时自动重新分配更大的内存空间 |
| 尾部高效 | 尾部插入/删除平均 O(1)(均摊复杂度) |
| 中间低效 | 中间插入/删除 O(n),需要移动后续所有元素 |
2.3 vector vs 普通数组
// 普通数组 —— 大小在编译期确定,不可改变
int arr[10]; // 固定10个元素,无法扩容
// vector —— 大小可在运行时动态变化
vector<int> vec; // 初始为空
vec.push_back(1); // 往里面加,自动扩容
vec.push_back(2);
// 现在 vec 有2个元素,容量可能大于2
一句话总结:vector 就是一个会自己管理内存、"会长大"的数组。
2.4 物理结构图解
vector 底层通常由三个指针来管理:
_start _finish _end_of_storage
| | |
v v v
+---+---+---+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+
<--- size=4 ---> <- capacity-size ->
<------------- capacity() = 10 ------------------>
_start:指向数组起始位置_finish:指向最后一个元素的下一个位置_end_of_storage:指向分配内存的末尾
size() = _finish - _start,capacity() = _end_of_storage - _start
理解这个三指针模型,对你之后看源码和模拟实现都会有很大帮助。
三、vector 的构造方式
vector 提供了多种构造函数,覆盖了各种使用场景。
3.1 默认构造
vector<int> v1; // 空 vector,size=0, capacity=0
vector<string> v2; // 存储 string 的空 vector
3.2 指定大小的构造
vector<int> v3(5); // 5个元素,值均为 int() = 0
vector<int> v4(5, 42); // 5个元素,值均为 42
vector<string> v5(3, "hello"); // 3个元素,值均为 "hello"
注意:vector<int> v(5) 调用了 5 次 int()(值为 0),而不是只分配空间不初始化。
3.3 拷贝构造
vector<int> v6(5, 10); // {10, 10, 10, 10, 10}
vector<int> v7(v6); // 深拷贝,v7 = {10, 10, 10, 10, 10}
vector<int> v8 = v6; // 等价于拷贝构造
拷贝构造会进行深拷贝——v7 和 v6 各自拥有独立的内存空间,修改其中一个不会影响另一个。
3.4 迭代器范围构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v9(arr, arr + 5); // {1, 2, 3, 4, 5}
vector<int> v10(v9.begin(), v9.end()); // 也可用另一个vector的迭代器
vector<int> v11(v9.begin() + 1, v9.begin() + 4); // {2, 3, 4},左闭右开
string s = "hello";
vector<char> v12(s.begin(), s.end()); // {'h', 'e', 'l', 'l', 'o'}
这个构造方式非常灵活——任何满足 InputIterator 的类型都可以传进去。
3.5 初始化列表构造(C++11)
vector<int> v13 = {1, 2, 3, 4, 5};
vector<int> v14{10, 20, 30};
vector<string> v15{"apple", "banana", "cherry"};
这是 C++11 引入的语法,也是日常开发中最直观的写法。
3.6 移动构造(C++11)
vector<int> createVector() {
vector<int> temp = {1, 2, 3, 4, 5};
return temp; // 现代编译器使用移动语义,不会额外拷贝
}
vector<int> v16 = createVector(); // 高效,直接"窃取"temp的资源
3.7 容易踩的坑:() vs {} 初始化
vector<int> a(3, 5); // 调用填充构造:{5, 5, 5}
vector<int> b{3, 5}; // 调用初始化列表构造:{3, 5}
// 这就是为什么以下写法会让人困惑:
vector<int> c(10); // 10个元素,每个都是0
vector<int> d{10}; // 1个元素,值为10
vector<int> e(10, 1); // 10个元素,每个都是1
vector<int> f{10, 1}; // 2个元素:10和1
四、vector 的迭代器
4.1 迭代器类型一览
| 迭代器 | 获取方法 | 说明 |
|---|---|---|
| 正向迭代器 | begin() / end() |
从前往后遍历 |
| 反向迭代器 | rbegin() / rend() |
从后往前遍历 |
| const 正向 | cbegin() / cend() |
只读正向遍历 |
| const 反向 | crbegin() / crend() |
只读反向遍历 |
在 vector 中,迭代器的底层实现就是原生指针(T*),这也是 vector 迭代器是随机访问迭代器(Random Access Iterator)的原因——指针天然支持 +n、-n、[] 等操作。
4.2 遍历 vector 的四种方式
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {10, 20, 30, 40, 50};
// 方式1:下标访问
cout << "下标遍历:" << endl;
for (size_t i = 0; i < v.size(); ++i) {
cout << v[i] << " ";
}
cout << endl;
// 方式2:迭代器
cout << "迭代器遍历:" << endl;
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 方式3:范围 for(C++11,最推荐日常使用)
cout << "范围for遍历:" << endl;
for (int x : v) {
cout << x << " ";
}
cout << endl;
// 方式4:for_each + lambda(C++11)
cout << "for_each遍历:" << endl;
for_each(v.begin(), v.end(), [](int x) {
cout << x << " ";
});
cout << endl;
return 0;
}
输出:
下标遍历:
10 20 30 40 50
迭代器遍历:
10 20 30 40 50
范围for遍历:
10 20 30 40 50
for_each遍历:
10 20 30 40 50
4.3 反向遍历
vector<int> v = {1, 2, 3, 4, 5};
// 反向迭代器
for (auto it = v.rbegin(); it != v.rend(); ++it) {
cout << *it << " "; // 5 4 3 2 1
}
cout << endl;
4.4 迭代器支持的操作(随机访问)
vector<int> v = {10, 20, 30, 40, 50};
auto it = v.begin();
it + 2; // 指向 30
*(it + 2); // 30
it[3]; // 40(等价于 *(it + 3))
it += 1; // 指向 20
auto it2 = v.end();
it2 - it; // 得到两个迭代器间的距离(ptrdiff_t)
it < it2; // true,随机访问迭代器支持比较
五、vector 的容量管理
容量管理是理解 vector 底层行为的关键。这一部分认真看,面试常考。
5.1 核心接口
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
cout << "初始 size: " << v.size() // 0
<< ", capacity: " << v.capacity() << endl; // 0
v.push_back(1);
cout << "插入1个后 size: " << v.size() // 1
<< ", capacity: " << v.capacity() << endl; // 可能是1(取决于编译器)
v.reserve(100);
cout << "reserve(100)后 size: " << v.size() // 仍然是1!
<< ", capacity: " << v.capacity() << endl; // 至少100
v.shrink_to_fit();
cout << "shrink_to_fit后 size: " << v.size() // 1
<< ", capacity: " << v.capacity() << endl; // 接近1
return 0;
}
5.2 重点辨析:resize() vs reserve()
这是初学者最容易混淆的两个函数:
resize(n) |
reserve(n) |
|
|---|---|---|
| 改变 size | 是,size 变为 n | 否,size 不变 |
| 改变 capacity | 可能(n > capacity 时会扩容) | 可能(n > capacity 时会扩容) |
| 新增元素的值 | 有默认值(或指定值) | 不添加元素 |
| 主要用途 | 需要确保有 n 个元素 | 预分配空间,避免频繁扩容 |
代码演示:
vector<int> v;
v.reserve(10);
cout << "reserve(10)后:"; // size=0, capacity=10
// v[0] = 5; // 错误!size还是0,不能访问v[0]
v.resize(5);
cout << "resize(5)后:"; // size=5, capacity=10(没缩容)
// v[0] = 5; // 正确!现在size=5,可以访问v[0]~v[4]
v.resize(10, 99);
cout << "resize(10, 99)后:"; // size=10, capacity=10
// 前5个元素不变,后5个填99
5.3 扩容机制:VS(1.5倍)vs g++(2倍)
这是 vector 最经典的面试题之一。
不同编译器对 vector 的扩容策略不同:
| 编译器 | 扩容因子 | 示例(连续push_back) |
|---|---|---|
| Visual Studio (MSVC) | 约 1.5 倍 | 1 → 2 → 3 → 4 → 6 → 9 → 13… |
| GCC (libstdc++) | 2 倍 | 1 → 2 → 4 → 8 → 16 → 32… |
| Clang (libc++) | 2 倍 | 同 GCC |
为什么选择 1.5 倍或 2 倍?
-
2 倍扩容:实现简单(左移一位),扩容次数少,但可能浪费较多内存。比如容量 1024,再 push 一次就变成 2048,之前的总容量(1+2+4+…)永远凑不出恰好回收完所有旧空间。
-
1.5 倍扩容:理论上可以在多次扩容后复用之前释放的内存块(因为旧容量之和可能大于新容量)。内存利用率更优,但实现复杂一点。
验证代码:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
size_t oldCap = v.capacity();
for (int i = 0; i < 100; ++i) {
v.push_back(i);
if (v.capacity() != oldCap) {
cout << "size=" << v.size()
<< ", capacity=" << v.capacity()
<< "(扩容了!)" << endl;
oldCap = v.capacity();
}
}
return 0;
}
GCC 下输出示例:
size=1, capacity=1(扩容了!)
size=2, capacity=2(扩容了!)
size=3, capacity=4(扩容了!)
size=5, capacity=8(扩容了!)
size=9, capacity=16(扩容了!)
size=17, capacity=32(扩容了!)
size=33, capacity=64(扩容了!)
size=65, capacity=128(扩容了!)
可以看到明显的 2 倍扩容规律。
5.4 扩容的代价
扩容并不是免费的。当容量不足时,vector 需要做三件事:
- 分配一块新的、更大的内存
- 将旧空间的所有元素拷贝(或移动)到新空间
- 释放旧空间
如果 vector 中存储的是复杂的自定义类型,且拷贝构造函数代价较高,频繁扩容会严重影响性能。这就是为什么在实际开发中,如果能预估元素数量,应当提前 reserve()。
六、vector 的元素访问
6.1 访问方式一览
vector<int> v = {10, 20, 30, 40, 50};
v[0]; // 10 —— 不检查越界
v.at(0); // 10 —— 检查越界,越界抛 out_of_range 异常
v.front(); // 10 —— 首元素引用
v.back(); // 50 —— 尾元素引用
v.data(); // 返回 int*,指向底层数组首地址
6.2 operator[] vs at() 深入对比
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
// v[5] = 10; // 越界!不报错,未定义行为(可能崩也可能不崩)
try {
v.at(5) = 10; // 越界!抛出 std::out_of_range 异常
} catch (const out_of_range& e) {
cout << "捕获异常:" << e.what() << endl;
}
return 0;
}
operator[] |
at() |
|
|---|---|---|
| 越界检查 | 无 | 有(抛异常) |
| 性能 | 更快 | 略慢(多一次边界判断) |
| 推荐场景 | 确定不越界时(如循环 i < v.size()) |
不确定是否越界时(如外部传参) |
6.3 data() —— 与 C 接口交互
当你需要调用一个接受 C 风格数组的接口时,data() 非常有用:
vector<int> v = {1, 2, 3, 4, 5};
// 传递给只接受 int* 的 C 函数
void c_style_func(const int* arr, size_t n) {
for (size_t i = 0; i < n; ++i) {
printf("%d ", arr[i]);
}
}
c_style_func(v.data(), v.size()); // 输出: 1 2 3 4 5
注意:当 v.empty() 时,v.data() 返回的指针可能为 nullptr,使用前要检查。
七、vector 的增删改操作
7.1 尾部操作:push_back / emplace_back / pop_back
vector<int> v;
// push_back —— 先构造临时对象,再拷贝/移动到vector
v.push_back(10);
v.push_back(20);
// v = {10, 20}
// emplace_back —— 直接在vector内部构造对象(C++11)
v.emplace_back(30);
// v = {10, 20, 30}
// pop_back —— 删除尾部元素
v.pop_back();
// v = {10, 20}
7.2 push_back vs emplace_back —— 效率对比
这是另一个面试高频考点,用自定义类型来演示最清楚:
#include <iostream>
#include <vector>
using namespace std;
class Person {
public:
string name;
int age;
Person(const string& n, int a) : name(n), age(a) {
cout << "Person 构造:" << name << endl;
}
Person(const Person& other) : name(other.name), age(other.age) {
cout << "Person 拷贝构造:" << name << endl;
}
Person(Person&& other) noexcept : name(move(other.name)), age(other.age) {
cout << "Person 移动构造:" << name << endl;
}
};
int main() {
vector<Person> v;
v.reserve(10); // 提前预留空间,避免扩容干扰观察
cout << "=== push_back ===" << endl;
v.push_back(Person("Alice", 25));
// 输出:构造 → 移动构造
cout << "=== emplace_back ===" << endl;
v.emplace_back("Bob", 30);
// 输出:构造(只有一次!)
return 0;
}
输出:
=== push_back ===
Person 构造:Alice
Person 移动构造:Alice
=== emplace_back ===
Person 构造:Bob
可以看到,push_back 先构造了临时对象 Person("Alice", 25),再将其移动到 vector 中(2 次调用),而 emplace_back 直接在 vector 内部构造对象(1 次调用)。
结论:在往 vector 中添加复杂对象时,优先使用 emplace_back。
7.3 任意位置插入:insert
vector<int> v = {1, 2, 3, 4, 5};
// 在指定位置插入单个元素
auto it = v.begin() + 2; // 指向 3
v.insert(it, 99);
// v = {1, 2, 99, 3, 4, 5}
// 在指定位置插入 n 个相同元素
v.insert(v.begin(), 3, 0);
// v = {0, 0, 0, 1, 2, 99, 3, 4, 5}
// 在指定位置插入一段迭代器范围
vector<int> extra = {100, 200};
v.insert(v.end(), extra.begin(), extra.end());
// v = {0, 0, 0, 1, 2, 99, 3, 4, 5, 100, 200}
注意:insert 在非尾部位置插入元素时,需要将插入位置之后的所有元素向后移动,时间复杂度为 O(n)。频繁在中间插入应该考虑 list 或其他容器。
7.4 任意位置删除:erase
vector<int> v = {10, 20, 30, 40, 50};
// 删除单个元素
v.erase(v.begin() + 2); // 删除 30
// v = {10, 20, 40, 50}
// 删除一段范围 [first, last)
v.erase(v.begin() + 1, v.begin() + 3); // 删除 20 和 40
// v = {10, 50}
insert 和 erase 都返回一个新的有效迭代器——这点非常重要,与迭代器失效直接相关(见第八章)。
7.5 assign / clear / swap
vector<int> v;
v.assign(5, 42); // v = {42, 42, 42, 42, 42}——整体赋值
v.assign({1, 2, 3}); // v = {1, 2, 3}——用初始化列表赋值
v.clear(); // 清空所有元素,size=0(但capacity可能不变)
vector<int> v2 = {100, 200};
v.swap(v2); // 交换v和v2的内容(只交换指针,O(1))
// v = {100, 200}, v2 = {1, 2, 3}
swap 的性能技巧:如果你想让 vector 真正释放内存(capacity 变为 0),可以用一个空 vector 与它交换:
vector<int> v(1000);
v.clear(); // size=0,但 capacity 可能还是 1000
vector<int>().swap(v); // 真正释放内存!v 与临时空 vector 交换
// 现在 v.capacity() == 0
八、迭代器失效问题(重点+难点)
8.1 什么是迭代器失效?
迭代器本质是指向容器内元素的"指针"。当容器内部发生了内存重新分配或元素移动时,之前获取的迭代器可能指向已经释放的内存或错误的元素——这就是迭代器失效。
8.2 导致迭代器失效的操作
| 操作 | 失效范围 | 原因 |
|---|---|---|
push_back(触发扩容) |
所有迭代器 | 底层内存整体搬迁 |
insert(触发扩容) |
所有迭代器 | 同上 |
insert(未触发扩容) |
插入点及之后的迭代器 | 后面元素整体后移 |
erase |
删除点及之后的迭代器 | 后面元素整体前移 |
resize(触发扩容) |
所有迭代器 | 底层内存整体搬迁 |
8.3 失效场景演示
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin() + 2; // it 指向 3
cout << "删除前 *it = " << *it << endl; // 3
v.erase(v.begin() + 1); // 删除 2 —— 触发元素移动
// cout << *it << endl; // 危险!it 已失效,未定义行为
return 0;
}
8.4 解决方案:利用返回值更新迭代器
insert 和 erase 都返回一个有效的迭代器:
insert(pos, val)返回指向新插入元素的迭代器erase(pos)返回指向被删除元素下一个位置的迭代器
vector<int> v = {1, 2, 3, 4, 5};
// 安全删除偶数元素的正确写法
for (auto it = v.begin(); it != v.end(); /* 不要在for头里++ */) {
if (*it % 2 == 0) {
it = v.erase(it); // erase 返回下一个有效位置
} else {
++it;
}
}
// v = {1, 3, 5}
常见错误写法,会导致迭代器跳跃或失效:
// 错误!删除后it失效,++it 是未定义行为
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it % 2 == 0) {
v.erase(it);
}
}
面试要点:记住 “erase 返回下一个有效迭代器,用 it = v.erase(it) 来接住它。”
九、vector 的扩容机制深入
9.1 三指针模型回顾
回忆第二节的图,vector 底层用三个指针管理内存。扩容的核心逻辑就是:
// 伪代码
void push_back(const T& val) {
if (_finish == _end_of_storage) {
// 空间不足,需要扩容
reserve(capacity() == 0 ? 1 : capacity() * 2); // 2倍扩容为例
}
*_finish = val;
++_finish;
}
9.2 扩容流程详解
当 reserve(new_cap) 被调用且 new_cap > capacity() 时:
- 分配新空间:
new new_cap * sizeof(T)字节 - 迁移元素:将旧空间的元素拷贝/移动到新空间
- 释放旧空间:
delete[]旧空间 - 更新指针:
_start、_finish、_end_of_storage指向新空间
void reserve(size_t n) {
if (n > capacity()) {
T* tmp = new T[n]; // 1. 开辟新空间
size_t sz = size();
// 2. 拷贝/移动旧元素到新空间
for (size_t i = 0; i < sz; ++i) {
tmp[i] = _start[i]; // 调用拷贝赋值
}
delete[] _start; // 3. 释放旧空间
_start = tmp; // 4. 更新指针
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
9.3 memcpy 陷阱
扩容时拷贝数据,能用 memcpy 吗?
// 错误的做法
memcpy(tmp, _start, sz * sizeof(T)); // 浅拷贝!只拷贝字节
对于存储自定义类型(如 string、含指针成员的类型)的 vector,memcpy 是灾难性的。 原因:
memcpy是浅拷贝,只复制字节,不会调用拷贝构造函数- 如果类型包含指向堆内存的指针(如
string内部的char*),浅拷贝会导致两个对象共享同一块内存 - 释放旧空间时析构函数会释放那块共享内存,新空间中的指针就成了野指针
正确做法是逐个调用拷贝/移动操作,让每个元素正确完成深拷贝。
十、vector 的模拟实现
理解 vector 的最好方式就是自己写一个。下面我们逐步实现一个简化版的 MyVector。
10.1 整体架构
template<typename T>
class MyVector {
private:
T* _start; // 指向数据起始位置
T* _finish; // 指向最后一个元素的下一个位置
T* _end_of_storage; // 指向分配空间的末尾
public:
// 迭代器(就是原生指针)
typedef T* iterator;
typedef const T* const_iterator;
// ... 成员函数见下文
};
10.2 构造函数
// 默认构造
MyVector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
// 填充构造:n个val
MyVector(size_t n, const T& val = T())
: _start(new T[n]), _finish(_start + n), _end_of_storage(_start + n) {
for (size_t i = 0; i < n; ++i) {
_start[i] = val;
}
}
// 迭代器范围构造
template<typename InputIterator>
MyVector(InputIterator first, InputIterator last) {
size_t n = last - first;//包含的元素个数
_start = new T[n];//创建一个包含n个T类元素的空间
//默认当前的size和capacity是同样的大小
_finish = _start + n;
_end_of_storage = _start + n;
//一一进行赋值拷贝(不能用memcpy)
for (size_t i = 0; i < n; ++i) {
_start[i] = first[i];
}
}
10.3 拷贝构造 & 赋值重载(深拷贝)
// 拷贝构造 —— 深拷贝
MyVector(const MyVector<T>& v) {
size_t n = v.size();
_start = new T[n];
_finish = _start + n;
_end_of_storage = _start + n;
for (size_t i = 0; i < n; ++i) {
_start[i] = v._start[i];
}
}
// 现代写法:拷贝并交换(Copy and Swap)
void swap(MyVector<T>& v) {
//用库里面的swap进行交换
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
MyVector<T>& operator=(MyVector<T> v) { // 传值,自动调用拷贝构造
swap(v); // 交换,v 的析构函数帮我们清理旧资源
return *this;
}
10.4 容量相关
size_t size() const { return _finish - _start; }
size_t capacity() const { return _end_of_storage - _start; }
bool empty() const { return _start == _finish; }
void reserve(size_t n) {
if (n > capacity()) //需要扩容
{
T* tmp = new T[n];
size_t sz = size();
// 逐个拷贝(不能 memcpy!)
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;//释放旧空间
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& val = T()) {
if (n < size())
{
// 缩小:只需移动 _finish
_finish = _start + n;
}
else
{
// 扩大:可能需要扩容
if (n > capacity()) {
reserve(n);
}
// 填充新增的位置
for (size_t i = size(); i < n; ++i) {
_start[i] = val;
}
_finish = _start + n;
}
}
10.5 元素访问
T& operator[](size_t i) { return _start[i]; }
const T& operator[](size_t i) const { return _start[i]; }
T& front() { return *_start; }
const T& front() const { return *_start; }
T& back() { return *(_finish - 1); }
const T& back() const { return *(_finish - 1); }
iterator begin() { return _start; }
const_iterator begin() const { return _start; }
iterator end() { return _finish; }
const_iterator end() const { return _finish; }
10.6 增删操作
void push_back(const T& val) {
if (_finish == _end_of_storage)
{
// 空间不足,扩容
reserve(capacity() == 0 ? 1 : capacity() * 2);
}
*_finish = val;
++_finish;
}
void pop_back()
{
if (!empty())
{
--_finish;
}
}
// insert —— 在pos位置前插入val,返回新插入元素的迭代器
iterator insert(iterator pos, const T& val)
{
size_t offset = pos - _start; // 记录偏移量(防止扩容后pos失效)
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 1 : capacity() * 2);
}
pos = _start + offset; // 用偏移量恢复 pos
// 将pos及之后的元素整体后移一位
for (iterator it = _finish; it > pos; --it) {
*it = *(it - 1);
}
*pos = val;
++_finish;
return pos;
}
// erase —— 删除pos位置的元素,返回下一个有效迭代器
iterator erase(iterator pos)
{
// 将pos之后的元素整体前移一位
for (iterator it = pos; it < _finish - 1; ++it)
{
*it = *(it + 1);
}
--_finish;
return pos; // pos现在指向原来pos+1位置的元素
}
10.7 析构函数
~MyVector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
10.8 完整源码
将以上各部分整合起来,再加上一些辅助函数:
#include <iostream>
#include <algorithm> // for std::swap
#include <cassert>
using namespace std;
template<typename T>
class MyVector {
private:
T* _start;
T* _finish;
T* _end_of_storage;
public:
typedef T* iterator;
typedef const T* const_iterator;
// ===== 构造 / 析构 =====
MyVector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
MyVector(size_t n, const T& val = T())
: _start(new T[n]), _finish(_start + n), _end_of_storage(_start + n) {
for (size_t i = 0; i < n; ++i) _start[i] = val;
}
template<typename InputIterator>
MyVector(InputIterator first, InputIterator last) {
size_t n = last - first;
_start = new T[n];
_finish = _start + n;
_end_of_storage = _start + n;
for (size_t i = 0; i < n; ++i) _start[i] = first[i];
}
MyVector(const MyVector<T>& v) {
size_t n = v.size();
_start = new T[n];
_finish = _start + n;
_end_of_storage = _start + n;
for (size_t i = 0; i < n; ++i) _start[i] = v._start[i];
}
// 拷贝并交换
void swap(MyVector<T>& v) {
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
MyVector<T>& operator=(MyVector<T> v) {
swap(v);
return *this;
}
~MyVector() {
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
// ===== 容量 =====
size_t size() const { return _finish - _start; }
size_t capacity() const { return _end_of_storage - _start; }
bool empty() const { return _start == _finish; }
void reserve(size_t n) {
if (n > capacity()) {
T* tmp = new T[n];
size_t sz = size();
for (size_t i = 0; i < sz; ++i) tmp[i] = _start[i];
delete[] _start;
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& val = T()) {
if (n < size()) {
_finish = _start + n;
} else {
if (n > capacity()) reserve(n);
for (size_t i = size(); i < n; ++i) _start[i] = val;
_finish = _start + n;
}
}
// ===== 元素访问 =====
T& operator[](size_t i) { return _start[i]; }
const T& operator[](size_t i) const { return _start[i]; }
T& front() { return *_start; }
const T& front() const { return *_start; }
T& back() { return *(_finish - 1); }
const T& back() const { return *(_finish - 1); }
// ===== 迭代器 =====
iterator begin() { return _start; }
const_iterator begin() const { return _start; }
iterator end() { return _finish; }
const_iterator end() const { return _finish; }
// ===== 增删改 =====
void push_back(const T& val) {
if (_finish == _end_of_storage) {
reserve(capacity() == 0 ? 1 : capacity() * 2);
}
*_finish = val;
++_finish;
}
void pop_back() {
if (!empty()) --_finish;
}
iterator insert(iterator pos, const T& val) {
size_t offset = pos - _start;
if (_finish == _end_of_storage) {
reserve(capacity() == 0 ? 1 : capacity() * 2);
}
pos = _start + offset;
for (iterator it = _finish; it > pos; --it) *it = *(it - 1);
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos) {
for (iterator it = pos; it < _finish - 1; ++it) *it = *(it + 1);
--_finish;
return pos;
}
void clear() { _finish = _start; }
};
// ===== 测试代码 =====
int main() {
// 测试默认构造 & push_back
MyVector<int> v;
for (int i = 1; i <= 10; ++i) {
v.push_back(i);
}
cout << "遍历 v:";
for (size_t i = 0; i < v.size(); ++i) {
cout << v[i] << " ";
}
cout << endl;
// 测试 insert
v.insert(v.begin() + 3, 99);
cout << "insert 后:";
for (auto x = v.begin(); x != v.end(); ++x) cout << *x << " ";
cout << endl;
// 测试 erase
v.erase(v.begin() + 3);
cout << "erase 后:";
for (auto x = v.begin(); x != v.end(); ++x) cout << *x << " ";
cout << endl;
// 测试拷贝构造
MyVector<int> v2(v);
cout << "拷贝构造 v2:";
for (size_t i = 0; i < v2.size(); ++i) cout << v2[i] << " ";
cout << endl;
// 拷贝独立验证
v2[0] = 999;
cout << "修改v2后,v[0]=" << v[0] << " (不受影响)" << endl;
cout << "\n所有测试通过!" << endl;
return 0;
}
测试输出:
遍历 v:1 2 3 4 5 6 7 8 9 10
insert 后:1 2 3 99 4 5 6 7 8 9 10
erase 后:1 2 3 4 5 6 7 8 9 10
拷贝构造 v2:1 2 3 4 5 6 7 8 9 10
修改v2后,v[0]=1 (不受影响)
所有测试通过!
十一、最佳实践 & 常见陷阱
11.1 提前预留空间
// 差 —— 频繁扩容
vector<int> v;
for (int i = 0; i < 100000; ++i) {
v.push_back(i); // 可能触发十几次扩容
}
// 好 —— 一次到位
vector<int> v;
v.reserve(100000);
for (int i = 0; i < 100000; ++i) {
v.push_back(i); // 不会触发扩容
}
11.2 尽量用 emplace_back
对于存储非基本类型的 vector,emplace_back 比 push_back 少一次拷贝/移动。如果类型构造代价大(如 string 的拷贝),收益更加明显。
11.3 遍历时删除:用返回值接 erase
前面第八章已经详细演示了,核心口诀:it = v.erase(it),不要在其他地方 ++it。
11.4 删除元素时的"迭代器跳跃"
// 错误:删除元素后还 ++it,会跳过一个元素
vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it)
{
if (*it % 2 == 0) {
v.erase(it); // 危险!
}
}
// 结果不可预测
// 正确:只在未删除时才 ++it
for (auto it = v.begin(); it != v.end(); )
{
if (*it % 2 == 0) {
it = v.erase(it); // 不 ++it,因为 erase 已经"前进"了一步
} else {
++it;
}
}
11.5 vector<bool> 是特殊的
vector<bool> 不是真正的 vector!标准对它做了特化——每个 bool 只占 1 个 bit 而非 1 个 byte。这导致:
vector<bool> vb = {true, false, true};
// vb[0] 返回的不是 bool&,而是一个"代理对象"
// bool& ref = vb[0]; // 编译错误!
// 如果需要真正的 bool 数组,用 vector<char> 或 deque<bool>
11.6 二维 vector 初始化
// 5行3列,全部初始化为0
vector<vector<int>> matrix(5, vector<int>(3, 0));
// 等价于:
// [[0,0,0],
// [0,0,0],
// [0,0,0],
// [0,0,0],
// [0,0,0]]
11.7 存储指针时注意内存管理
vector<int*> v;
v.push_back(new int(10)); // 记住要手动 delete!
// 内存释放
for (int* p : v)
{
delete p;
}
v.clear();
// 更推荐:用智能指针
vector<unique_ptr<int>> v;
v.push_back(make_unique<int>(10)); // 自动管理内存,更安全
十二、总结
12.1 核心知识速查表
| 分类 | 重点内容 |
|---|---|
| 构造 | 6种构造方式,注意 () vs {} 的区别 |
| 迭代器 | 随机访问迭代器,底层是原生指针 |
| 容量 | resize 改 size,reserve 预留空间;扩容 1.5x(VS) / 2x(g++) |
| 访问 | operator[] 快但不检查,at() 安全但略慢 |
| 增删 | 尾部 O(1),中间 O(n);emplace_back 优于 push_back |
| 迭代器失效 | 扩容全失效;insert/erase 使操作点及之后失效;用返回值更新 |
| 模拟实现 | 三指针模型,深拷贝不能 memcpy |
12.2 与相关容器对比
| 特性 | vector |
list |
deque |
|---|---|---|---|
| 内存布局 | 连续 | 链式(不连续) | 分段连续 |
| 随机访问 | O(1) | O(n) | O(1) |
| 头插/头删 | O(n) | O(1) | O(1) |
| 尾插/尾删 | O(1) | O(1) | O(1) |
| 中间插/删 | O(n) | O(1)(找到位置后) | O(n) |
| 迭代器失效 | 扩容全失效 | 永不失效(除非删元素本身) | 扩容全失效 |
12.3 推荐进一步学习
- cppreference.com — std::vector(最权威的参考文档)
- 《STL 源码剖析》—— 侯捷(深入理解底层实现)
- C++ Core Guidelines(最佳实践指南)
如果这篇博客对你有帮助,欢迎点赞、收藏,也欢迎在评论区指出任何问题或讨论~
更多推荐
所有评论(0)