

💗博主介绍:计算机专业的一枚大学生 来自重庆 @燃于AC之乐✌专注于C++技术栈,算法,竞赛领域,技术学习和项目实战✌💗
💗根据博主的学习进度更新(可能不及时)
💗后续更新主要内容:C语言,数据结构,C++、linux(系统编程和网络编程)、MySQL、Redis、QT、Python、Git、爬虫、数据可视化、小程序、AI大模型接入,C++实战项目与学习分享。
👇🏻 精彩专栏 推荐订阅👇🏻
点击进入🌌作者专栏🌌:
Linux系统编程✅
算法画解✅
C++✅🌟算法相关题目点击即可进入实操🌟
感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!
文章目录
-
- [📚 一、前言:为什么需要理解Vector的实现?](#📚 一、前言:为什么需要理解Vector的实现?)
- [🏗️ 二、Vector的整体架构设计](#🏗️ 二、Vector的整体架构设计)
-
- [2.1 类模板声明](#2.1 类模板声明)
- [2.2 核心三指针模型](#2.2 核心三指针模型)
- [🔧 三、构造函数实现详解](#🔧 三、构造函数实现详解)
-
- [3.1 默认构造函数(现代C++风格)](#3.1 默认构造函数(现代C++风格))
- [3.2 拷贝构造函数(深拷贝实现)](#3.2 拷贝构造函数(深拷贝实现))
- [3.3 迭代器范围构造函数(泛型设计)](#3.3 迭代器范围构造函数(泛型设计))
- [3.4 数量+值构造函数](#3.4 数量+值构造函数)
- [💾 四、内存管理:深拷贝的艺术](#💾 四、内存管理:深拷贝的艺术)
-
- [4.1 错误的浅拷贝示例](#4.1 错误的浅拷贝示例)
- [4.2 正确的深拷贝实现](#4.2 正确的深拷贝实现)
- [🎯 五、迭代器失效:C++程序员的噩梦](#🎯 五、迭代器失效:C++程序员的噩梦)
-
- [5.1 insert操作中的迭代器失效](#5.1 insert操作中的迭代器失效)
- [5.2 erase操作的迭代器安全写法](#5.2 erase操作的迭代器安全写法)
- [5.3 迭代器失效总结表](#5.3 迭代器失效总结表)
- [🚀 六、现代C++技巧:拷贝交换惯用法](#🚀 六、现代C++技巧:拷贝交换惯用法)
-
- [6.1 传统的拷贝赋值实现](#6.1 传统的拷贝赋值实现)
- [6.2 现代写法:拷贝交换惯用法](#6.2 现代写法:拷贝交换惯用法)
- [🔄 七、扩容策略与性能优化](#🔄 七、扩容策略与性能优化)
-
- [7.1 扩容策略实现](#7.1 扩容策略实现)
- [7.2 扩容策略对比](#7.2 扩容策略对比)
- [7.3 预分配优化](#7.3 预分配优化)
- [📝 八、完整代码实现要点总结](#📝 八、完整代码实现要点总结)
-
- [8.1 必须实现的函数](#8.1 必须实现的函数)
- [8.2 易错点提醒](#8.2 易错点提醒)
- [8.3 扩展功能建议](#8.3 扩展功能建议)
- [🎓 九、面试常见问题](#🎓 九、面试常见问题)
- [📊 十、性能测试与对比](#📊 十、性能测试与对比)
- [🔚 十一、结语](#🔚 十一、结语)
摘要:本文详细解析了C++标准库vector的模拟实现,通过手把手教你实现一个完整的vector容器,深入理解动态数组的内存管理、迭代器设计、深拷贝机制等核心概念。
📚 一、前言:为什么需要理解Vector的实现?
在C++编程中,vector是最常用也最重要的容器之一。作为一个动态数组,它结合了数组的随机访问效率和动态扩容的灵活性。但是:
- 面试高频考点:vector的实现原理是C++面试的必考题
- 理解STL设计哲学:通过学习vector,可以掌握STL容器的通用设计模式
- 提升内存管理能力:深入理解深拷贝、迭代器失效等关键问题
- 掌握模板编程:vector是模板类的经典案例
🏗️ 二、Vector的整体架构设计
2.1 类模板声明
cpp
#pragma once
#include <assert.h>
#include <list>
#include <string>
namespace bit // 自定义命名空间
{
template<class T>
class vector
{
public:
// 类型别名
typedef T* iterator;
typedef const T* const_iterator;
// 构造函数族
vector() = default;
vector(const vector<T>& v);
template <class InputIterator>
vector(InputIterator first, InputIterator last);
vector(size_t n, const T& val = T());
// 析构函数
~vector();
// 赋值运算符
vector<T>& operator=(vector<T> v);
// 迭代器
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
// 容量操作
void reserve(size_t n);
void resize(size_t n, T val = T());
size_t size() const;
size_t capacity() const;
bool empty() const;
void clear();
// 元素访问
T& operator[](size_t i);
const T& operator[](size_t i) const;
// 修改操作
void push_back(const T& x);
void pop_back();
iterator insert(iterator pos, const T& x);
void erase(iterator pos);
// 工具函数
void swap(vector<T>& v);
private:
// 核心三指针
iterator _start = nullptr; // 指向数据起始
iterator _finish = nullptr; // 指向最后一个有效元素的下一个位置
iterator _end_of_storage = nullptr; // 指向分配空间的末尾
};
}
2.2 核心三指针模型
这是vector实现中最精妙的设计之一:
cpp
// 内存布局示意图
_start ----------> | 0 | 1 | 2 | 3 | ... | | |
|---有效数据区---|---未用空间---|
_finish -----------^ ^
_end_of_storage -------------------^
// 计算方法
size() = _finish - _start // 有效元素个数
capacity() = _end_of_storage - _start // 总容量
设计优点:
- 计算size和capacity只需指针相减,O(1)时间复杂度
- 清晰地划分了已用空间和未用空间的边界
- 方便判断是否需要扩容(
_finish == _end_of_storage)
🔧 三、构造函数实现详解
3.1 默认构造函数(现代C++风格)
cpp
vector() = default; // C++11强制生成默认构造
与传统写法的对比:
cpp
// 传统写法
vector()
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
// 现代写法(推荐)
vector() = default; // 结合成员变量初始化
3.2 拷贝构造函数(深拷贝实现)
cpp
vector(const vector<T>& v)
{
reserve(v.size()); // 第一步:预分配足够空间
for (auto& e : v) // 第二步:深拷贝每个元素
{
push_back(e);
}
}
3.3 迭代器范围构造函数(泛型设计)
cpp
template <class InputIterator> // 模板参数:可以是任意迭代器类型
vector(InputIterator first, InputIterator last)
{
while (first != last) // 关键:使用!=而不是<
{
push_back(*first); // 解引用获取元素值
++first;
}
}
设计亮点:
- 使用
!=而不是<:list等容器的迭代器不支持<操作 - 模板参数
InputIterator:可以接受vector、list、set等任何容器的迭代器
3.4 数量+值构造函数
cpp
vector(size_t n, const T& val = T())
{
reserve(n); // 预分配空间
for (size_t i = 0; i < n; i++)
{
push_back(val); // 插入n个val
}
}
注意 :这里使用size_t而不是int,避免与迭代器构造函数产生歧义。
💾 四、内存管理:深拷贝的艺术
4.1 错误的浅拷贝示例
cpp
// 错误实现:使用memcpy
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
T* tmp = new T[n];
memcpy(tmp, _start, old_size * sizeof(T)); // 问题所在!
delete[] _start;
_start = tmp;
// ...
}
}
问题分析 :
当T是string、vector等包含动态内存的类时:
memcpy只复制了string对象的指针,没有复制字符串内容- 两个string对象指向同一块内存
- 析构时会双重释放,导致程序崩溃
4.2 正确的深拷贝实现
cpp
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
T* tmp = new T[n]; // 分配新空间
// 关键:循环深拷贝
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i]; // 调用T::operator=,进行深拷贝
}
delete[] _start; // 释放旧空间(调用每个元素的析构函数)
// 更新指针
_start = tmp;
_finish = tmp + old_size;
_end_of_storage = tmp + n;
}
}
工作原理:
tmp[i] = _start[i]实际调用T::operator=(const T&)- 对于int/double等内置类型:直接值拷贝
- 对于string等自定义类型:调用其拷贝赋值运算符,进行深拷贝
🎯 五、迭代器失效:C++程序员的噩梦
5.1 insert操作中的迭代器失效
cpp
iterator insert(iterator pos, const T& x)
{
// 1. 参数检查
assert(pos >= _start && pos <= _finish);
// 2. 处理可能的扩容(迭代器失效场景)
if (_finish == _end_of_storage)
{
// 关键:保存相对偏移量
size_t len = pos - _start; // 计算pos相对于_start的位置
// 扩容操作(会改变_start的指向)
reserve(capacity() == 0 ? 4 : capacity() * 2);
// 重新计算pos在新空间的位置
pos = _start + len; // 修正迭代器
}
// 3. 移动元素
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end; // 向后移动
--end;
}
// 4. 插入元素
*pos = x;
++_finish;
return pos; // 返回新的有效迭代器
}
5.2 erase操作的迭代器安全写法
cpp
// 错误写法:迭代器失效后继续使用
for (auto it = v.begin(); it != v.end(); ++it)
{
if (condition(*it))
{
v.erase(it); // it失效后,下一轮++it是未定义行为!
}
}
// 正确写法:接收erase的返回值
auto it = v.begin();
while (it != v.end())
{
if (condition(*it))
{
it = v.erase(it); // erase返回下一个有效迭代器
}
else
{
++it; // 只有不删除时才手动递增
}
}
5.3 迭代器失效总结表
| 操作 | 是否导致迭代器失效 | 原因 | 解决方案 |
|---|---|---|---|
| push_back | 可能失效 | 扩容时所有迭代器失效 | 扩容后重新获取迭代器 |
| insert | 一定失效 | 扩容或元素移动 | 接收insert的返回值 |
| erase | 一定失效 | 元素移动 | 接收erase的返回值 |
| reserve | 一定失效 | 内存重新分配 | 避免在reserve后使用旧迭代器 |
🚀 六、现代C++技巧:拷贝交换惯用法
6.1 传统的拷贝赋值实现
cpp
// 传统写法:需要检查自赋值
vector<T>& operator=(const vector<T>& v)
{
if (this != &v) // 1. 检查自赋值
{
delete[] _start; // 2. 释放旧空间
// 3. 分配新空间并深拷贝
_start = new T[v.capacity()];
// ... 拷贝逻辑
// 4. 更新指针
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
return *this;
}
缺点:
- 代码冗余(与拷贝构造函数重复)
- 需要检查自赋值
- 异常不安全
6.2 现代写法:拷贝交换惯用法
cpp
// 现代写法:优雅且安全
vector<T>& operator=(vector<T> v) // 关键:传值,调用拷贝构造
{
swap(v); // 交换资源
return *this; // v是局部变量,离开作用域自动析构
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
优点分析:
- 异常安全:拷贝发生在参数传递时,如果new失败,原对象不受影响
- 自赋值安全:传值创建了临时对象,自动处理自赋值情况
- 代码复用:复用了拷贝构造函数和析构函数的逻辑
- 自动清理:临时对象v在函数结束时自动析构,释放旧资源
🔄 七、扩容策略与性能优化
7.1 扩容策略实现
cpp
void push_back(const T& x)
{
// 检查是否需要扩容
if (_finish == _end_of_storage)
{
// 成倍扩容策略
size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(new_capacity);
}
// 插入元素
*_finish = x;
++_finish;
}
7.2 扩容策略对比
| 策略 | 扩容因子 | 均摊复杂度 | 空间浪费 | 适用场景 |
|---|---|---|---|---|
| 固定大小 | +N | O(n) | 小 | 内存受限系统 |
| 成倍扩容 | ×2 | O(1) | 中等 | 通用场景(STL采用) |
| 黄金比例 | ×1.5 | O(1) | 较小 | 内存敏感场景 |
STL选择×2的原因:
- 均摊时间复杂度为O(1)
- 实现简单高效
- 在时间效率和空间效率间取得平衡
7.3 预分配优化
cpp
// 使用前预分配,避免多次扩容
vector<int> v;
v.reserve(1000); // 一次性分配足够空间
for (int i = 0; i < 1000; ++i)
{
v.push_back(i); // 避免多次扩容拷贝
}
📝 八、完整代码实现要点总结
8.1 必须实现的函数
- 基本构造析构:默认构造、拷贝构造、析构
- 迭代器支持:begin、end及其const版本
- 容量操作:size、capacity、reserve、resize
- 元素访问:operator[]、at(可选)
- 修改操作:push_back、pop_back、insert、erase
- 赋值操作:operator=、swap
8.2 易错点提醒
- 深拷贝问题:绝对不能使用memcpy
- 迭代器失效:insert/erase后迭代器失效
- 自赋值处理:operator=要处理a = a的情况
- 异常安全:new失败时要保证资源不泄漏
- 类型兼容:模板代码要兼容各种类型T
8.3 扩展功能建议
cpp
// 1. 移动语义支持(C++11)
vector(vector&& v) noexcept;
vector& operator=(vector&& v) noexcept;
void push_back(T&& x);
// 2. 列表初始化(C++11)
vector(std::initializer_list<T> il);
// 3. 原位构造(C++11)
template<class... Args>
void emplace_back(Args&&... args);
// 4. 数据访问(C++11)
T* data() noexcept;
const T* data() const noexcept;
🎓 九、面试常见问题
Q1:vector的底层实现原理是什么?
答:vector使用动态数组实现,通过三个指针(start、finish、end_of_storage)管理内存,支持随机访问和动态扩容。
Q2:vector的扩容机制是怎样的?
答:当size == capacity时,vector会重新分配一块更大的内存(通常是原来的2倍),将旧元素拷贝到新空间,然后释放旧空间。
Q3:什么是迭代器失效?vector哪些操作会导致失效?
答:迭代器失效指迭代器指向的元素位置变得无效。vector的insert、erase、push_back(扩容时)、reserve等操作都可能导致迭代器失效。
Q4:vector的深拷贝和浅拷贝有什么区别?
答:浅拷贝只复制指针,深拷贝复制指针指向的内容。vector必须实现深拷贝,否则多个vector对象会共享同一块内存,导致双重释放等问题。
Q5:vector的insert和erase操作时间复杂度是多少?
答:在尾部操作是O(1),在中间或头部操作是O(n),因为需要移动后续元素。
📊 十、性能测试与对比
| 操作 | 时间复杂度 | 空间复杂度 | 备注 |
|---|---|---|---|
| 随机访问 | O(1) | O(1) | 支持下标访问 |
| 尾部插入 | 均摊O(1) | O(n) | 可能触发扩容 |
| 中间插入 | O(n) | O(n) | 需要移动元素 |
| 查找 | O(n) | O(1) | 无序查找 |
🔚 十一、结语
通过手写vector的实现,我们不仅学会了如何实现一个动态数组容器,更重要的是:
- 深入理解了C++的内存管理机制
- 掌握了模板编程和泛型设计思想
- 认识了异常安全和资源管理的重要性
- 理解了STL容器的设计哲学
vector的实现是C++学习的重要里程碑,它融合了C++的多个核心特性:模板、异常、RAII、迭代器等。希望这篇文章能帮助你深入理解vector,并在实际编程中更加得心应手地使用它。

加油!志同道合的人会看到同一片风景。
看到这里请点个赞 ,关注 ,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!