一、核心数据结构:内存布局与指针管理
vector 的底层实现依赖于三个关键指针,它们共同维护了动态数组的内存布局:
cpp
iterator _start = nullptr; // 指向数据起始位置
iterator _finish = nullptr; // 指向最后一个有效元素的下一个位置
iterator _endofstorage = nullptr; // 指向已分配内存的末尾
其中 iterator 定义为 T*,即原生指针。这三个指针构成以下逻辑区间:
[ _start, _finish ):存放有效数据元素,是用户可访问的数据范围。[ _finish, _endofstorage ):预留空间,用于后续插入操作,避免频繁扩容。
关键计算公式:
size()=_finish - _start:当前实际存储元素个数。capacity()=_endofstorage - _start:当前可用总容量(未使用部分也计入)。
✅ 设计亮点:通过指针差值计算大小和容量,无需额外成员变量,高效且节省空间。
二、迭代器支持:兼容 STL 风格遍历
vector 提供完整的迭代器接口,支持标准算法和范围循环:
cpp
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
begin()/end()返回非const迭代器,允许修改元素。const版本返回const_iterator(即const T*),保证只读访问。
示例:支持范围 for 循环
cpp
vector<int> v = {1, 2, 3};
for (auto x : v) {
cout << x << " "; // 输出: 1 2 3
}
📌 说明 :
auto推导出的是int&(非const),若对const vector遍历,则自动变为const int&。
三、构造函数详解:多种初始化方式
1. 默认构造函数
cpp
vector() = default;
- 所有指针初始化为
nullptr。 - 初始
size = 0,capacity = 0。
2. 拷贝构造函数(深拷贝)
cpp
vector(const vector<T>& v) {
reserve(v.capacity());
for (const auto& elem : v) {
push_back(elem);
}
}
- 先调用
reserve(v.capacity())分配足够空间。 - 使用
push_back逐个调用拷贝构造函数,确保对string、unique_ptr等复杂类型进行深拷贝。 - ❌ 不使用
memcpy,防止浅拷贝导致悬空指针或重复释放。
🔥 重点强调 :对于含堆内存的对象(如
std::string),必须逐元素复制,否则会引发严重错误。
3. 迭代器区间构造模板
cpp
template <class InputIterator>
vector(InputIterator first, InputIterator last) {
while (first != last) {
push_back(*first++);
}
}
- 支持任意输入迭代器,包括原始指针、其他容器的迭代器。
- 示例:
cpp
int arr[] = {1, 2, 3};
vector<int> v(arr, arr + 3); // 构造 {1,2,3}
✅ 可以从
list、set等容器中提取数据构造vector。
4. 填充构造函数
cpp
vector(int n, T val = T()) {
resize(n, val);
}
- 调用
resize(n, val)扩容并填充默认值或指定值。 - 若
val是string,则每个元素都调用其构造函数。
5. initializer_list 构造函数
cpp
vector(initializer_list<T> il) {
reserve(il.size());
for (const auto& elem : il) {
push_back(elem);
}
}
- 支持
{1, 2, 3}初始化语法。 - 示例:
cpp
vector<int> v = {10, 20, 30}; // 正确!
⚠️ 注意:
initializer_list构造函数优先级高于其他构造函数,因此不能写成vector<int> v{10};以外的形式。
6. 赋值运算符:copy-and-swap 惯用法
cpp
vector<T>& operator=(vector<T> v) {
swap(v); // 交换资源,旧数据随临时对象析构自动释放
return *this;
}
- 参数按值传递 → 触发拷贝构造 → 创建副本。
swap(*this, v)交换两个对象的三个指针。- 原对象的旧资源被销毁,新资源保留。
✅ 优点:
- 自动处理自赋值(
v = v)。- 异常安全:若中间步骤抛异常,原对象不变。
- 代码简洁,易于维护。
四、内存管理:动态扩容机制
核心扩容逻辑(仅当容量不足时触发)
cpp
void reserve(size_t n) {
if (n <= capacity()) return;
T* new_start = new T[n]; // 动态分配新内存
size_t old_size = size();
// 逐元素移动(调用拷贝构造)
for (size_t i = 0; i < old_size; ++i) {
new_start[i] = std::move(_start[i]); // 移动语义优化
}
delete[] _start; // 释放旧内存
_start = new_start;
_finish = _start + old_size;
_endofstorage = _start + n;
}
扩容策略:初始容量为 4,之后每次翻倍
cpp
if (_endofstorage == _finish) {
size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(new_capacity);
}
✅ 均摊时间复杂度:O(1)
尽管单次扩容是
O(n),但长期来看,插入操作的平均开销仍为常数。
为什么不用 memcpy?
cpp
// ❌ 错误做法(浅拷贝风险)
memcpy(new_start, _start, sizeof(T) * size());
// ✅ 正确做法(深拷贝)
for (size_t i = 0; i < size(); ++i)
new_start[i] = _start[i];
memcpy仅适用于 POD 类型 (如int,double)。- 对
std::string、vector等类类型,会导致多个对象共享同一块堆内存,析构时重复释放,崩溃!
五、元素操作:插入与删除的安全性保障
插入元素到指定位置 insert(pos, x)
cpp
iterator insert(iterator pos, const T& x) {
if (_finish == _endofstorage) {
size_t len = pos - _start; // 记录偏移量
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; // 重新定位迭代器(因扩容后地址变化)
}
// 从末尾开始后移元素,腾出位置
for (iterator it = _finish; it != pos; --it) {
*it = std::move(*(it - 1));
}
*pos = x;
++_finish;
return pos;
}
🔑 关键点:
- 扩容前保存
pos相对于_start的偏移量。- 扩容后重新计算
pos,避免迭代器失效。- 使用
std::move提升性能(尤其对大对象)。
删除元素 erase(pos)
cpp
iterator erase(iterator pos) {
if (pos >= _finish) return _finish;
// 从 pos+1 开始向前移动元素,覆盖被删元素
for (iterator it = pos + 1; it != _finish; ++it) {
*(it - 1) = std::move(*it);
}
--_finish;
return pos; // 返回下一个位置的迭代器
}
✅ 符合 STL 规范 :
erase返回被删除元素的下一个位置。
- 支持经典模式:
for (auto it = v.begin(); it != v.end(); ) { if (*it % 2 == 0) it = v.erase(it); else ++it; }
六、辅助工具函数:实用性强,细节到位
| 函数 | 实现 | 作用 |
|---|---|---|
operator[] |
assert(pos < size()); return _start[pos]; |
下标访问,带越界检查 |
empty() |
return _start == _finish; |
判断是否为空 |
clear() |
_finish = _start; |
清空内容,不释放内存 |
resize(n, val) |
if (n > size()) reserve(n); _finish = _start + n; fill(_finish - n, _finish, val); |
扩展或截断 |
swap(vector&) |
std::swap(_start, other._start); std::swap(_finish, other._finish); std::swap(_endofstorage, other._endofstorage); |
快速交换资源 |
💡
clear()不释放内存,是为了提高后续插入效率(避免反复申请)。
七、测试代码解析(test.cpp)------验证功能完整性
test1:基本 push_back + 打印
cpp
void test1() {
vector<int> v;
for (int i = 1; i <= 5; ++i) {
v.push_back(i);
}
print(v); // 1 2 3 4 5
}
test2:pop_back 逐步删除
cpp
void test2() {
vector<int> v = {1, 2, 3, 4, 5};
while (!v.empty()) {
cout << v.back() << " ";
v.pop_back();
}
// 输出: 5 4 3 2 1
}
test3:insert 头部与中间插入
cpp
void test3() {
vector<int> v = {10, 20, 30};
v.insert(v.begin(), 5); // 头部插入
v.insert(v.begin() + 2, 15); // 中间插入
print(v); // 5 10 15 20 30
}
test4:erase 单个删除
cpp
void test4() {
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.erase(v.begin() + 2); // 删除 3
cout << *it; // 4
}
test5:删除所有偶数(经典用法)
cpp
void test5() {
vector<int> v = {1, 2, 3, 4, 5, 6};
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // erase 返回下一个迭代器
} else {
++it;
}
}
print(v); // 1 3 5
}
✅ 精髓 :
erase返回下一个有效位置,使得for循环不会跳过元素。
test6:resize 测试
cpp
void test6() {
vector<int> v = {1, 2, 3};
v.resize(5); // 扩大至 5,补 0
v.resize(2, 99); // 截断至 2,不传值则补 0
print(v); // 1 2
}
test7:综合构造测试
cpp
void test7() {
vector<int> v1(3, 10); // 填充构造
vector<int> v2({1, 2, 3}); // initializer_list
vector<int> v3(v1.begin(), v1.end()); // 区间构造
vector<int> v4 = v1; // 拷贝构造
v4 = v3; // 赋值运算符
}
test8:string 类型测试(深拷贝安全性)
cpp
void test8() {
vector<string> v1 = {"hello", "world"};
vector<string> v2 = v1; // 深拷贝!每个 string 都独立拥有自己的堆内存
v1[0] = "hi";
cout << v2[0]; // "hello" ------ 未受影响
}
✅ 结论:所有操作均保证深拷贝,对复杂类型安全。
八、设计亮点总结(强化版)
| 亮点 | 说明 |
|---|---|
| ✅ 不使用 memcpy | 保证对 std::string 等类类型的深拷贝,杜绝内存错误 |
| ✅ copy-and-swap 惯用法 | 赋值运算符简洁、异常安全、自动处理自赋值 |
| ✅ 插入时重定位迭代器 | 扩容后重新计算 pos 偏移量,避免迭代器失效 |
| ✅ erase 返回迭代器 | 符合 STL 规范,支持安全删除循环 |
| ✅ 均摊 O(1) 插入 | 2 倍扩容策略,降低频繁分配成本 |
| ✅ 支持任意迭代器构造 | 可从 array、list、set 等构造 vector |
| ✅ RAII 内存管理 | 所有资源由对象自身管理,无泄漏风险 |
九、完整简化版 vector 实现框架(核心骨架)
cpp
template <typename T>
class vector {
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _endofstorage = nullptr;
public:
using iterator = T*;
using const_iterator = const T*;
// 构造函数
vector() = default;
vector(const vector& v);
template <class InputIterator>
vector(InputIterator first, InputIterator last);
vector(int n, T val = T());
vector(std::initializer_list<T> il);
// 赋值运算符
vector& operator=(vector v) {
swap(v);
return *this;
}
// 迭代器
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
// 元素操作
void push_back(const T& x);
void pop_back();
iterator insert(iterator pos, const T& x);
iterator erase(iterator pos);
// 工具函数
size_t size() const { return _finish - _start; }
size_t capacity() const { return _endofstorage - _start; }
bool empty() const { return _start == _finish; }
void clear() { _finish = _start; }
void resize(size_t n, T val = T());
void reserve(size_t n);
void swap(vector& other);
// 下标访问
T& operator[](size_t pos) {
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const {
assert(pos < size());
return _start[pos];
}
};
十、结语:为何学习这个实现?
理解 vector 底层原理,不仅能写出更高效的代码,还能:
- 避免踩坑(如迭代器失效、浅拷贝崩溃);
- 在面试中展示深度;
- 为自定义容器打下坚实基础;
- 更好地使用 STL 算法与泛型编程。
🎯 掌握它,就是掌握了现代 C++ 的基石之一。