📌 C++ STL std::vector
底层实现
std::vector
是 C++ STL 中最常用的 动态数组容器,其底层实现依赖于 连续内存块,并采用 动态扩容策略 来管理内存。
📌 1. std::vector
的底层数据结构
🚀 std::vector
内部维护三个指针
template <typename T>
class vector {
private:
T* _start; // 指向数据存储的起始位置
T* _finish; // 指向数据存储的末尾(size)
T* _end_of_storage; // 指向已分配空间的末尾(capacity)
};
📌 关键点
_start
:指向数组起始地址,即begin()
_finish
:指向当前已存储数据的末尾(size()
)_end_of_storage
:指向分配内存的末尾(capacity()
)
📌 示意图
_capacity = 5, _size = 3
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ 10 │ 20 │ 30 │ - │ - │
├─────────┴─────────┴─────────┬─────────┬─────────┤
↑_start ↑_finish ↑_end_of_storage
✅ 这样 std::vector
支持 O(1)
访问,并允许动态扩容!
📌 2. std::vector
的关键操作
🚀 2.1 插入元素 (push_back()
)
📌 流程
- 检查
size()
是否小于capacity()
- 如果足够,直接
*(finish) = value
,然后_finish++
。 - 如果容量不够,触发 扩容(reallocate)。
- 如果足够,直接
📌 代码
void push_back(const T& value) {
if (_finish == _end_of_storage) {
reallocate(); // 扩容
}
*_finish = value; // 直接赋值
_finish++;
}
✅ push_back()
均摊 O(1)
,但扩容时 O(N)
!
🚀 2.2 vector
扩容机制(reallocate()
)
📌 当 size() == capacity()
时,vector 需要扩容
- 默认扩容 2 倍,减少扩容次数,提高性能!
- 分配更大的内存块,然后 移动旧数据到新内存。
📌 扩容代码
void reallocate() {
size_t new_capacity = capacity() ? 2 * capacity() : 1;
T* new_start = new T[new_capacity]; // 分配新内存
std::move(_start, _finish, new_start); // 移动数据
delete[] _start; // 释放旧内存
_finish = new_start + size();
_start = new_start;
_end_of_storage = _start + new_capacity;
}
✅ reallocate()
触发 O(N)
级别的拷贝,但均摊成本仍为 O(1)
!
📌 扩容过程
初始容量 4:
┌─────┬─────┬─────┬─────┐
│ 10 │ 20 │ 30 │ 40 │
└─────┴─────┴─────┴─────┘
↑_start ↑_finish
触发扩容(2 倍):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ 10 │ 20 │ 30 │ 40 │ │ │ │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
↑_start ↑_end_of_storage
✅ 这样 vector
避免了频繁 malloc()
,提高插入效率!
🚀 2.3 删除元素 (pop_back()
)
📌 流程
- 直接
_finish--
,然后析构最后一个元素。
📌 代码
void pop_back() {
if (_finish != _start) {
--_finish;
_finish->~T(); // 手动调用析构函数
}
}
✅ pop_back()
始终是 O(1)
!
🚀 2.4 erase()
删除任意位置元素
📌 流程
- 需要 移动后续元素覆盖删除的元素,因此时间复杂度
O(N)
。
📌 代码
iterator erase(iterator pos) {
if (pos + 1 != _finish) {
std::move(pos + 1, _finish, pos); // 移动数据覆盖删除位置
}
--_finish;
return pos;
}
✅ erase()
需要移动数据,因此时间复杂度 O(N)
!
🚀 2.5 reserve()
预分配容量
📌 作用
reserve(n)
预先分配n
个元素的空间,避免频繁扩容。
📌 代码
void reserve(size_t new_capacity) {
if (new_capacity > capacity()) {
T* new_start = new T[new_capacity];
std::move(_start, _finish, new_start);
delete[] _start;
_start = new_start;
_finish = new_start + size();
_end_of_storage = _start + new_capacity;
}
}
✅ 适用于大量 push_back()
,减少扩容开销!
📌 3. vector
的迭代器失效
std::vector
的操作可能导致迭代器失效:
操作 | 是否导致迭代器失效 | 原因 |
---|---|---|
push_back() | 是 | 可能触发扩容,导致指针失效 |
pop_back() | 是 | 最后一个元素被删除,指针失效 |
erase(it) | 是 | 后续元素前移,旧迭代器无效 |
clear() | 是 | 删除所有元素,所有迭代器失效 |
✅ 最佳实践
vec.push_back(10);
it = vec.begin(); // 重新获取迭代器
✅ erase()
后应使用返回值
it = vec.erase(it); // 获取新的有效迭代器
📌 4. vector
vs list
vs deque
容器 | 底层结构 | 插入效率 | 访问效率 | 迭代器稳定性 |
---|---|---|---|---|
vector | 连续数组 | 慢(O(N)) | 快(O(1)) | 可能失效 |
list | 双向链表 | 快(O(1)) | 慢(O(N)) | 稳定 |
deque | 分段数组 | 快(O(1)) | 快(O(1)) | 较稳定 |
✅ vector
适用于 频繁 随机访问(O(1)
)。
✅ list
适用于 频繁 插入/删除(O(1)
)。
✅ deque
适用于 频繁 头部/尾部插入(O(1)
)。
📌 5. 总结
✅ vector
采用动态数组存储,支持 O(1)
访问。
✅ 扩容时 2x
方式 reallocate()
,减少 malloc()
次数。
✅ push_back()
均摊 O(1)
,erase()
O(N)
,迭代器可能失效。
✅ reserve(n)
预分配可优化 push_back()
。
🚀 掌握 vector
底层原理,写出更高效的 C++ 代码! 🚀