C++:Vector类核心技术解析及其模拟实现
目录:
- vector类
- 一、 vector容器的基本概念
- 二、vector创建对象的构造方式
- 三、常用接口解析
- 1.容量操作
- 2.元素访问
- 3.修改操作
- 4.二维(多维)数组
- 四、vector底层数据结构与动态扩容机制
- 1. 核心数据结构
- 2. 动态扩容机制
- 五、vector模拟实现关键代码
- 1. 基础框架
- 2. 内存管理核心函数
- 3. 元素访问与修改
- 六、迭代器失效问题深度解析
- 1. 失效场景
- 2. 解决方案
C++:Vector类核心技术解析及其模拟实现
vector类
一、 vector容器的基本概念
vector是C++标准模板库(STL)中的动态数组容器,能够存储相同类型的元素。其核心特点包括:
- 动态扩容:内存空间自动增长,无需手动管理。
- 随机访问:支持通过下标(如
v[i]
)快速访问元素,时间复杂度为O(1)。 - 连续存储:元素在内存中连续排列,兼容C风格数组操作。
二、vector创建对象的构造方式
初始化方式 | 语法示例 | 功能描述 |
---|---|---|
默认构造 | std::vector<T> v; | 创建空vector,元素类型为T ,容量为0 |
列表初始化 | std::vector<T> v({a, b, c}); | 使用花括号直接初始化元素,C++11引入 |
拷贝构造 | std::vector<T> v2(v1); | 通过另一个同类型vector复制所有元素 |
迭代器区间构造 | std::vector<T> v2(v1.begin(),v1.end()); | 构造一个容器,其中包含与范围 [first,last] 一样多的元素,每个元素都按相同的顺序从该区域中的相应元素构造。 |
填充构造 | std::vector<T> v2(10,1); | 构造一个包含 10 个元素的容器,每个元素都是1. |
代码示例:
// 默认构造
std::vector<int> v1; // 列表初始化
std::vector<int> v2 ({1, 2, 3}); // 拷贝构造
std::vector<int> v3(v2); //迭代器区间构造
int myints[] = {16,2,77,29};std::vector<int> v4 (myints, myints + sizeof(myints) / sizeof(int) );
列表初始化是C++11引入的 原型是
vector (initializer_list il, const allocator_type& alloc = allocator_type());
initializer_list是一个类,使用时要包含头文件 #include <initializer_list>
auto il = { 10, 20, 30 }; il就是一个initializer_list类型
区别:
std::vector<int> v2 ({1, 2, 3});
std::vector<int> v2 = {1, 2, 3};
std::vector<int> v2 {1, 2, 3};
三、常用接口解析
1.容量操作
接口 | 参数 | 返回值 | 功能 | 示例 |
---|---|---|---|---|
resize(len) | len :新长度 | void | 调整动态数组大小 | v.resize(3); |
reserve(size) | size :预留容量 | void | 预分配内存避免多次扩容 | v.reserve(100); |
size() | 无 | size_t | 返回动态数组大小 | v.size(); |
capacity() | 无 | size_t | 返回空间总大小 | v.capacity; |
resize();同string类一样,如果小于当前数组大小会缩容
对比string你会发现STL这些容器的有些接口名字相同,功能也类似,大大减小了我们的学习成本
2.元素访问
同string一样下标访问、范围for、迭代器访问,用法也类似,祥见上一篇文章string
其次值得注意的是vector不提供流插入运算符重载
3.修改操作
4.二维(多维)数组
以这道题为例:
class Solution
{
public:vector<vector<int>>generate(int numRows){vector<vector<int>>vv;vv.resize(numRows,vector<int());for(size_t i = 0; i < numRows; ++i){vv[i].resize(i+1,1);}for(size_ti=2;i<vv.size();++i){for(size_tj=1;j<vv[i].size()-1;++j){vv[i][j]=vv[i-1][j]+vv[i-1][j-1];}}return vv;}
};
四、vector底层数据结构与动态扩容机制
1. 核心数据结构
vector通过三个指针实现动态数组管理:
template <class T>
class vector
{
private:T* _start;// 指向数组首元素T* _finish;// 指向最后一个有效元素的下一位T* _end_of_storage;// 指向内存空间末尾
};
_finish - _start
= 有效元素数量(size()
)_end_of_storage - _start
= 总容量(capacity()
)- 连续内存布局:兼容C风格数组操作,支持随机访问(O(1))
2. 动态扩容机制
触发条件:当 size() == capacity()
时执行 push_back
或 insert
会触发扩容。
扩容流程:
- 申请新内存空间(大小为原空间的k倍)
- 拷贝旧数据到新空间(深拷贝)
- 释放旧内存
- 更新三个指针的值
扩容策略对比:
平台 | 扩容因子 | 优势 | 劣势 |
---|---|---|---|
GCC | 2倍 | 时间复杂度低(均摊O(1)) | 内存碎片率高 |
VS | 1.5倍 | 内存复用率高,碎片少 | 扩容次数稍多 |
五、vector模拟实现关键代码
1. 基础框架
template<class T>
class Vector
{
public:typedef T* iterator;//迭代器iterator begin()const{return _start;}iterator end()const{return _finish;}Vector() : _start(nullptr),_finish(nullptr), _end_of_storage(nullptr) {}vector(int n, const T& value = T()){resize(n, value);}// 迭代器区间构造
template <class InputIterator>
Vector(InputIterator first, InputIterator last)
{while (first != last) {push_back(*first);++first;}
}// 列表初始化(C++11)
Vector(std::initializer_list<T> il)
{reserve(il.size());for (auto& e : il) push_back(e);
}//赋值重载
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}vector<T>& operator= (vector<T> v)
{swap(v);return v;
}//析构
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}
private:iterator _start;iterator _finish;iterator _end_of_storage;
};
注意:
我们自己写的迭代器区间构造又缺陷,如果我们的实例化类型不是迭代器类型,比如是int,那么我们解引用的时候就会出问题。
针对这个问题,库里使用一种名为"萃取"的技术,了解即可
2. 内存管理核心函数
reserve():预分配空间(不初始化元素)
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) {// 深拷贝(不能用memcpy)tmp[i] = _start[i];// 调用T的赋值运算符}delete[] _start;// 释放旧空间_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}
resize():调整有效元素数量
void resize(size_t n, const T& val = T())
{if (n < size()) {// 缩容_finish = _start + n;} else {// 扩容+初始化reserve(n);while (_finish != _start + n) {*_finish = val;++_finish;}}
}
3. 元素访问与修改
operator[](支持const重载):
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}
push_back()(含扩容逻辑):
void push_back(const T& x)
{if (_finish == _end_of_storage) {// 需扩容size_t new_cap = capacity() == 0 ? 4 : capacity() * 2;reserve(new_cap);}*_finish = x;// 尾部插入++_finish;
}
insert()(需处理迭代器失效):
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);// 扩容前保存偏移量
size_t offset = pos - _start;
if (_finish == _end_of_storage)
{reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + offset;// 重定位pos
}// 后移元素
iterator end = _finish - 1;
while (end >= pos)
{*(end + 1) = *end;--end;
}*pos = x;// 插入新元素
++_finish;
return pos;// 返回新位置的迭代器
}
pop_back() 与 erase():
void pop_back()
{if (_finish > _start) --_finish;
}iterator erase(iterator pos)
{iterator begin = pos + 1;while (begin != _finish) {// 前移元素*(begin - 1) = *begin;++begin;
}--_finish;return pos;// 返回删除位置的下一位置
}
六、迭代器失效问题深度解析
1. 失效场景
操作 | 失效原因 | 影响范围 |
---|---|---|
insert | 扩容导致内存重分配 | 所有迭代器失效 |
erase | 元素前移覆盖被删位置 | 被删位置及之后的迭代器 |
push_back | 尾部扩容 | 所有迭代器可能失效 |
2. 解决方案
- 重新赋值:
auto it = v.begin();
while (it != v.end())
{if (*it == 3) {it = v.erase(it);// 接收erase返回的新迭代器} else {++it;}
}
- 避免扩容后使用旧迭代器:在
insert
/push_back
后更新迭代器(通过返回值或重新计算偏移量)。