当前位置: 首页 > news >正文

深度剖析 vector 底层原理:从手写实现到核心技术点全解析

文章目录

  • 0. 前言
  • 1. vector 核心结构:动态数组实现的基石
    • 1.1. 核心成员:三个指针
    • 1.2. 核心属性计算:size和capacity的实现
  • 2. vector构造家族:从无到有的实现逻辑
    • 2.1. 默认构造:空vector的初始化
    • 2.2. n个元素构造:批量初始化的实现
  • 2.3. 迭代器区间构造:通用容器的初始化方式
    • 2.4. 拷贝构造函数:深拷贝的实现与意义
  • 3. vector内存管理:扩容、预留空间与释放
    • 3.1. 预留空间(reserve):提前分配内存的艺术
    • 3.2. 扩容机制:vector的动态增长策略
      • 3.2.1. 扩容后的关键问题: 迭代器失效
  • 4. vector元素访问与修改:随机访问的实现
    • 4.1. 随机访问的底层逻辑:指针偏移
    • 4.2. 首位元素快速访问
  • 5. vector元素插入与删除:内存挪动与迭代器失效解析
    • 5.1. insert():插入元素
    • 5.2. earse():删除元素
      • earse()的关键陷阱:迭代器失效
    • 5.3. 按区间删除
    • 5.4. 清空元素
  • 6. 完整代码(带测试案例)
  • 7. 总结

0. 前言

C++ 标准模板库(STL)中,vector 是最常用的容器之一,它以动态数组的形式存在,兼顾了数组的随机访问效率和动态扩容的灵活性。很多开发者日常使用 vector 时,往往只关注其表层接口,却对其底层实现原理一知半解。本文将通过手写一个简化版 vectormy_vector),深入剖析 vector 的底层机制,重点讲解内存管理、深浅拷贝、迭代器失效、构造函数设计、交换机制等核心技术点,和大家交流我的一些理解。


1. vector 核心结构:动态数组实现的基石

在正式分析之前,我们要明确vector的本质——动态数组,与静态数组(如int arr[10])相比,vector的优势在于可以根据数据数量变化动态调整内存大小,合理使用内存空间。要实现这一功能,vector底层需要解决三个关键问题:

  1. 如何跟踪当前已存储的数据范围?
  2. 如何跟踪已分配的内存空间范围?
  3. 如何在数据数量超过内存容量时高效扩容?

1.1. 核心成员:三个指针

vector的底层实现依赖三个核心指针,这三个指针共同定义了vector的内存布局和元素范围。我们在my_vector中,这三个指针定义如下:

template <class T>
class my_vector {
private:iterator _start;          // 指向内存块的起始位置(第一个元素)iterator _finish;         // 指向已存储元素的末尾位置(最后一个元素的下一个位置)iterator _end_of_storage; // 指向已分配内存块的末尾位置(内存的最后一个位置的下一个位置)
};

三个指针关系可以用下图表示:

在这里插入图片描述

通过这三个指针,我们可以快速得到两个核心属性:

  1. 大小(size):已存储元素的个数,_finish - _start

  2. 容量(capacity):已分配内存可容纳最大元素个数,_end_of_storage - _start

这种设计的优势在于:

  • 随机访问效率高:通过_start + index可以直接定位到第index个元素,时间复杂度O(1)O(1)O(1)
  • 空间利用率可控:容量大于等于大小,预留的空间可以减少频繁扩容的开销
  • 状态计算简单:无需额外存储sizecapacity,只用指针差即可实现

1.2. 核心属性计算:size和capacity的实现

基于上述三个指针,my_vectorsizecapacity的实现非常简洁:

// 容量相关函数
size_t capacity() const {if (!_start || !_end_of_storage) return 0;return _end_of_storage - _start; 
}
size_t size() const { if (!_start || !_end_of_storage) return 0;return _finish - _start; 
}
bool empty() const { return _start == _finish; }

这里值得注意的是,当my_vector调用默认构造时,_start _finish _end_of_storage均为nullptr,直接计算差值会导致未定义行为,所以需要先判断指针是否为空


2. vector构造家族:从无到有的实现逻辑

vector 提供了多种构造方式,以满足不同场景下的初始化需求。手写 my_vector 时,需要覆盖核心的构造函数,包括默认构造n 个元素构造迭代器区间构造拷贝构造。每种构造函数的实现都有其特定的设计考量,尤其是内存分配和元素初始化的逻辑。

2.1. 默认构造:空vector的初始化

创建一个空的vector,此时没有开辟内存,没有任何元素

my_vector() :_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{}

设计要点

  • 初始状态下不分配内存,避免不必要的空间浪费
  • 三个指针统一初始化为nullptr,保证状态一致性
  • 后续调用push_backreserve时才会分配内存

2.2. n个元素构造:批量初始化的实现

当需要创建包含 n 个相同元素的 vector 时,就需要用到 n 个元素的构造函数。实现如下:

my_vector(size_t n,const T& val = T()):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{reserve(n); // 预留n个空间的内存// 逐个尾插for(size_t i = 0; i < n; i++){push_back(val); // 调用尾插函数}
}

设计要点和注意事项

  • 提前预留空间,避免频繁扩容

  • 默认参数注意事项:

    1. T()的本质是 “调用默认构造生成默认值”:第二个参数val的缺省值是T(),在 C++ 中,T()是一种值初始化(value initialization) 的语法,它的核心作用是:为一个T类型的变量创建一个 “默认的、合法的初始值”,而这个过程必须依赖T的默认构造函数,如int为0,string为空字符串
    2. 省略val时必须支持默认构造:若调用my_vector(n)(没有传递第二个参数),T必须有默认构造函数,否则T()无法生成默认值,编译报错;
    3. 显式传val可规避限制:若T不支持默认构造,只要调用时显式传入一个已有的T对象(如my_vector(n, val)),就能正常构造,无需依赖默认构造。

画个图理解:

在这里插入图片描述

  • 类型匹配问题:

第一个参数类型是无符号整数,调用时传参需要注意类型匹配,例如:my_vector<int> v(3, 10)中,3会被视为int类型,需要转化为size_t类型:my_vector<int> v((size_t)3, 10)

注意看调用调试代码:

在这里插入图片描述

对于迭代器区间构造,我们马上实现,这样两份代码进行对比就更好理解了

2.3. 迭代器区间构造:通用容器的初始化方式

迭代器区间构造是vector的灵活构造方式之一,它支持了从任意提供迭代器的容器(如list array map等)中拷贝元素来初始化,实现如下:

template <class InputIterator>
my_vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{// 计算迭代器区间内元素个数size_t n = 0;InputIterator tmp = first;while(tmp != last){++tmp;++n;}// 逐个拷贝新元素reserve(n);while(first != last){push_back(*first);++first;}
}

设计要点:

  1. 函数模板通用性

构造函数本身是一个函数模板,支持任意类型的输入迭代器,这意味着可以实现跨容器的初始化

  1. 元素类型的隐式转换

支持从其他容器的初始化,例如从vector<double>的迭代器区间初始化my_vector<int>,此时*firstdouble类型)会隐式转换成int类型

使用示例:

//  测试迭代器范围构造
Vect::my_vector<int> v3(v1.begin() + 2, v1.begin() + 6);
print_my_vector(v3, "v3(迭代器范围v1[2]-v1[5])");// 从数组的迭代器区间初始化(数组名可视为指针)
int arr[] = { 10,20,30 };
my_vector<int> v0(arr, arr + 3); // v0包含元素10,20,30
print_my_vector(v0, "v0(迭代器范围arr[0] - arr[2])");

2.4. 拷贝构造函数:深拷贝的实现与意义

拷贝构造函数用于创建一个新 vector,其内容与另一个 vector 完全相同。这里的关键是深拷贝—— 新vector 拥有独立的内存空间,修改新 vector 不会影响原 vector,反之亦然。

实现如下:

my_vector(const my_vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{// 预留和源对象一样大的空间reserve(v.capacity());// 逐个拷贝for(const auto& tmp : v){push_back(tmp);}
}

设计要点:

  1. 为什么深拷贝?
  • 采用浅拷贝(直接拷贝三个指针),新的vector会和旧的vector指向同一块内存空间
  • 当其中一个被析构时,会释放内存空间,导致另一个vector的指针成为野指针,访问时会触发内存错误

在这里插入图片描述

  1. 范围for的使用:
  • 通过for (const auto& tmp : v)遍历原 vector 的元素,范围 for 的本质是遍历容器的每个元素,用循环变量接收元素const auto&可以避免不必要的拷贝(如果接收的是值而不是引用,就会调用拷贝构造),同时保证不修改原元素
  • 对于复杂类型(如my_vector<string>),push_back(tmp)会调用 string 的拷贝构造函数(深拷贝),实现元素级别的深拷贝

浅拷贝的危害:

// 错误写法:浅拷贝,导致内存共享
my_vector(const my_vector<T>& v):_start(v._start), _finish(v._finish), _end_of_storage(v._end_of_storage)
{ }

当发生如下操作时,会触发内存错误:

my_vector<int> v1;
v1.push_back(10);
my_vector<int> v2 = v1; // 浅拷贝,v2与v1共享内存
v1.~my_vector(); // 析构v1,释放内存
cout << v2[0]; // 错误:v2._start指向已释放的内存(野指针)

在这里插入图片描述

3. vector内存管理:扩容、预留空间与释放

内存管理是 vector 底层实现的核心,直接影响 vector 的性能和正确性。vector 的内存管理主要包括预留空间(reserve)、扩容机制、内存释放三个部分。理解这部分内容,能够帮助开发者写出更高效的代码(如避免不必要的扩容)。

3.1. 预留空间(reserve):提前分配内存的艺术

reserve函数的作用是提前分配足够的内存空间,但不创建任何元素。它的核心目的是避免后续插入元素时频繁扩容,提升性能。

实现如下:

void reserve(size_t input_num){// 保存旧空间的元素个数 后续需要重新定位_finishsize_t old_size = size();// 扩容if(input_num > capacity()){T* tmp = new T[input_num];if(_start){// 此处不能用memcpy 需要循环赋值实现深拷贝for(size_t i = 0; i < old_size;++i){tmp[i] = _start[i];}delete[] _start;}// 指向新的内存_start = tmp;_finish = _start + old_size;_end_of_storage = _start + input_num;}
}

设计要点:

  1. 扩容机制的触发:只有传递的input_num > capacity()时,才进行扩容,
  2. 旧元素的拷贝方式:不能用memcpy拷贝!!!memcpy是逐字节的浅拷贝,会导致新老元素共享同一块内存,析构时会析构两次;采用循环赋值,会调用T类型的赋值运算符,实现深拷贝
  3. 指针的更新:为什么保存旧元素的大小?我们开辟出一个新的空间,要释放_start
_start = tmp;// 致命错误:此时size() = _finish - _start,但_start已指向新内存,_finish还是旧内存的地址_finish = _start + size(); _end_of_storage = _start + input_num;

经过调试发现,size()这个值不确定,陷入了死循环

在这里插入图片描述

进一步思考:size() = _finish - _start,此时_start是新的,而_finish是旧的,两个指针指向的不是同一块空间,会引发错误,所以,一定要先记录旧元素大小

3.2. 扩容机制:vector的动态增长策略

vector 的元素个数(size)超过容量(capacity)时,就需要进行扩容。vector的扩容不是简单地在原有内存空间后追加,而是遵循以下步骤:

  1. 分配一块更大的新内存空间(通常是原容量的 2 倍)
  2. 将旧内存中的元素拷贝到新内存
  3. 释放旧内存空间
  4. 更新三个核心指针,指向新内存空间

my_vector中,扩容逻辑被封装在reserve函数中,由push_backinsert等函数间接触发。例如,push_back的实现如下:

void push_back(const T& input_val) {// 复用insert函数,在_end位置插入元素insert(_finish, input_val);
}// 在pos之后插入元素
iterator insert(iterator pos, const T& input_val) {// 检查pos的合理性assert(pos <= _finish && pos >= _start);// 判断是否需要扩容if (_finish == _end_of_storage) {//  在reserve的逻辑里 会将_start原来的空间删除 而pos指向的是_start的旧空间// 所以释放旧空间之后 pos是野指针 会引发迭代器失效// 需要记录pos和_start的相对位置size_t len = pos - _start;size_t new_cap = capacity() == 0 ? 4 : 2 * capacity();reserve(new_cap);// 更新pos位置pos = _start + len;}// 从后往前挪动数据iterator end = _finish;while (end != pos) {*end = *(end - 1);end--;}*pos = input_val;_finish++;return pos;
}

3.2.1. 扩容后的关键问题: 迭代器失效

insert函数中有个细节很容易被忽略

// 需要记录pos和_start的相对位置size_t len = pos - _start;
// ...
// 更新pos位置pos = _start + len;
// ...

这两行代码是为了解决扩容导致迭代器失效的问题,我们需要从迭代器本质和扩容对内存的影响两方面理解:

  1. 迭代器的本质:指向内存的像指针一样的东西

vector 的迭代器(如 iterator)本质是对 “指向元素内存地址的指针” 的封装(在我们的 my_vector 中,iterator 直接typedefT*)。迭代器的有效性依赖于 "它指向的内存地址始终有效且属于 vector"。

  1. 扩容为什么会导致迭代器失效?

扩容的核心是 分配新内存 → 拷贝旧元素 → 释放旧内存,这个过程会让所有指向旧内存的迭代器变成 野指针"

  • 假设扩容前,pos 指向旧内存中的某个位置(如 _start_old + 2);
  • 扩容后,旧内存被 delete[] 释放(变为无效内存),_start 指向新内存;
  • 此时若不更新 pospos 仍然指向已释放的旧内存地址,后续对 pos 的操作(如 *pos = input_val)会触发 “访问非法内存” 的未定义行为(程序崩溃、数据错乱等)。
  1. 如何解决迭代器失效?——重新计算迭代器位置

size_t len = pos - _start; pos = _start + len;这两行代码是先计算pos与旧的_start的相对偏移量,这个偏移量是索引,不受内存地址变化的影响,然后更新pos在新空间内相对于_start的位置。

利用这种方式pos指向旧内存的野指针变为了指向新内存对应位置的有效迭代器


4. vector元素访问与修改:随机访问的实现

vector 作为动态数组,核心优势之一是随机访问(即通过索引快速定位元素),这一特性通过operator[]front()back()等接口实现。但元素访问的背后,不仅有 “高效定位” 的逻辑,还有 “边界检查” 的安全性考量 —— 我们需要同时理解 “如何实现随机访问” 和 “如何避免越界错误”。

4.1. 随机访问的底层逻辑:指针偏移

vector 的随机访问能力,本质源于其连续的内存布局—— 所有元素在内存中连续存储,通过 “起始指针 + 索引偏移” 即可直接定位到目标元素,时间复杂度为$ O (1)$。

operator[]为例,其实现逻辑如下:

// 非const版本:支持修改元素
T& operator[](size_t index){// 边界检查assert(index < size());return _start[index];
}// const版本:仅支持读取元素,不允许修改
const T& operator[](size_t index) const {// 边界检查assert(index < size()); return _start[index];
}

关键细节:_start[index]的本质

_start是指向内存起始位置的指针(T*类型),_start[index]在编译器中会被解析为:*( _start + index )

即 “从起始指针向后偏移indexT类型大小的位置,再解引用获取元素”。例如,T=int(占 4 字节)时,_start[2]等价于*(0x100 + 2*4) = *(0x108),直接定位到第 3 个元素(索引从 0 开始)。

这种 “指针偏移 + 解引用” 的方式,是随机访问效率的核心 —— 无需遍历元素,直接通过数学计算定位。

4.2. 首位元素快速访问

// 非const版本
T& front() {assert(!empty()); // 确保vector非空return *_start; // 首元素即_start指向的位置
}T& back() {assert(!empty()); // 确保vector非空return *(_finish - 1); // 尾元素是_finish前一个位置(_finish指向最后一个元素的下一个)
}// const版本(逻辑一致,仅返回const引用)
const T& front() const { ... }
const T& back() const { ... }

关键细节:_finish - 1的合理性

由于_finish指向 “已存储元素的末尾(最后一个元素的下一个位置)”,因此_finish - 1恰好指向最后一个元素。例如:

  • 若 vector 有 3 个元素(size=3),_start指向 0x100,_finish指向 0x10C(int 类型,3*4=12 字节偏移)
  • _finish - 1 = 0x10C - 4 = 0x108,指向第 3 个元素(索引 2),即尾元素。

5. vector元素插入与删除:内存挪动与迭代器失效解析

vectorinsert()(插入元素)和erase()(删除元素)是高频操作,但这两个接口的底层逻辑复杂 —— 涉及 “元素挪动”“内存扩容” 和 “迭代器失效”,也是开发者最容易踩坑的地方。

5.1. insert():插入元素

insert()的功能是 “在指定迭代器pos位置插入一个元素”,其实现需要处理 “扩容”“元素挪动”“迭代器更新” 三个核心步骤,具体代码如下:

iterator insert(iterator pos, const T& input_val) {// 步骤1:检查pos的合法性(必须在[_start, _finish]范围内)assert(pos <= _finish && pos >= _start);// 步骤2:判断是否需要扩容(元素个数达到容量上限)if (_finish == _end_of_storage) {// 计算pos与旧_start的相对偏移量(用于扩容后更新pos)size_t len = pos - _start;// 计算新容量(空vector初始4,否则2倍)size_t new_cap = capacity() == 0 ? 4 : 2 * capacity();reserve(new_cap); // 调用reserve扩容(释放旧内存,分配新内存)// 步骤3:更新pos(扩容后旧pos失效,用偏移量重新计算)pos = _start + len;}// 步骤4:从后往前挪动元素,腾出pos位置iterator end = _finish;while (end != pos) {*end = *(end - 1); // 后一个元素 = 前一个元素(拷贝)end--;}// 步骤5:在pos位置插入新元素*pos = input_val;// 步骤6:更新_finish(元素个数+1)_finish++;// 返回插入位置的迭代器(供用户后续使用)return pos;
}

插入流程:

假设 vector 当前状态:size=3capacity=4,元素为[1,2,3],在索引1位置插入4

在这里插入图片描述

5.2. earse():删除元素

erase()的功能是 “删除指定迭代器pos位置的元素”,其实现需要处理 “元素挪动”“迭代器失效” 两个核心问题,代码如下:

	// 删除pos位置元素iterator erase(iterator pos) {// 检查pos的合理性assert(pos < _finish && pos >= _start);// 将pos之后的元素依次往前挪动一位iterator end = pos + 1;while (end != _finish) {*(end - 1) = *end; // 前一个元素 = 后一个元素(拷贝覆盖)end++;}_finish--;// 返回删除位置的下一个迭代器(原pos已失效,返回有效迭代器)return pos; // 注意:此时pos指向的是原pos+1的元素(因元素已挪动)
}

earse()的关键陷阱:迭代器失效

删除元素后,**被删除位置的pos**及之后的所有迭代器都会失效,这是因为元素发生了向前挪动,原迭代器指向的内存地址对应的元素已经改变:

在这里插入图片描述

  • 假设删除前,it指向pos=0x104(元素 4);
  • 删除后,0x104位置的元素变为 2,it仍然指向0x104,但此时it对应的 “原元素 4” 已被删除,若继续使用it(如*it),会访问到错误的元素(2);
  • 更严重的是,若删除后调用erase(it),会再次删除0x104位置的元素(2),导致逻辑错误。

所以:正确的删除方式是,利用erase()的返回值——erase()返回删除位置的下一个有效迭代器(即原来pos+1位置的元素在挪动后的地址),通过 “用返回值更新迭代器” 避免使用失效的迭代器。

// 错误示例:使用失效的迭代器
my_vector<int> v = {1,4,2,3};
auto it = v.begin() + 1; // it指向4(pos=0x104)
v.erase(it); // 删除4后,it失效(仍指向0x104,但元素已变为2)
v.erase(it); // 错误:使用失效的it,实际删除的是2(原pos+1的元素)// 正确示例:用erase的返回值更新迭代器
my_vector<int> v = {1,4,2,3};
auto it = v.begin() + 1; // it指向4
it = v.erase(it); // 删除4后,it被更新为指向2(原pos+1的元素)
v.erase(it); // 正确:删除2,此时v变为{1,3}

5.3. 按区间删除

除了删除单个元素,vector 还支持 “删除[first, last)区间内的所有元素”(范围 erase),其实现逻辑是对单个删除的批量优化:

// 按区间删除
iterator erase(iterator first, iterator last) {// [first, last)assert(first <= last && first >= _start && last <= _finish);// 计算需要删除的元素个数size_t len = last - first;// 从last开始,向前挪动元素,覆盖被删除区间iterator end = last;while (end != _finish) {*(first++) = *end++; // 用后面的元素覆盖被删除区间}// 更新_finish(减少删除的元素个数)_finish -= len;// 返回删除区间的起始位置(此时已指向原last位置的元素)return first - len;
}

5.4. 清空元素

clear() 函数的功能是 “删除所有元素”,但它的实现非常简单:

void clear() {_finish = _start; // 直接将_finish重置为_start,逻辑上清空所有元素
}

6. 完整代码(带测试案例)

// ==== my_vector.h ====
#include <iostream>
#include <cassert>
#include <algorithm>
using namespace std;namespace Vect {template <class T>class my_vector {public:typedef T* iterator;typedef const T* const_iterator;// 迭代器iterator begin() { return _start; }iterator end() { return _finish; }iterator begin() const { return _start; }iterator end() const { return _finish; }const_iterator cbegin()const { return _start; }const_iterator cend() const { return _finish; }// 容量相关函数size_t capacity() const {if (!_start || !_end_of_storage) return 0;return _end_of_storage - _start; }size_t size() const { if (!_start || !_end_of_storage) return 0;return _finish - _start; }bool empty() const { return _start == _finish; }void clear() { _finish = _start; }// 构造函数// 默认构造my_vector():_start(nullptr), _finish(nullptr),_end_of_storage(nullptr){ }// 构造n个元素my_vector(size_t n, const T& val = T()) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {reserve(n);// 将所有数据尾插for (size_t i = 0; i < n; i++){push_back(val);}}// 迭代器区间初始化// 类模板的成员函数也能写成函数模板 支持了任意容器的迭代器初始化template <class InputIterator>my_vector(InputIterator first, InputIterator last) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){// 计算元素个数size_t n = 0;InputIterator tmp = first;while (tmp != last) {++tmp;++n;}// 尾插n个元素reserve(n);while (first != last) {push_back(*first);++first;}}// 拷贝构造my_vector(const my_vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){// 逐个拷贝源vector的元素reserve(v.capacity());for (const auto& tmp : v) push_back(tmp);}//// 浅拷贝//my_vector(const my_vector<T>& v)//	:_start(v._start)//	, _finish(v._finish)//	, _end_of_storage(v._end_of_storage)//{//}void swap(my_vector<T>& v) {// 交换三个核心指针std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}// 赋值运算符重载my_vector<T>& operator=(my_vector<T> v) {swap(v);return *this;}// 析构~my_vector() {delete[] _start;_start = _finish = _end_of_storage = nullptr;}// 元素访问T& operator[](size_t index){if(index < size()) return _start[index];}const T& operator[](size_t index) const {assert(index < size());return _start[index];}T& front() {assert(!empty());return *_start;}const T& front() const {assert(!empty());return *_start;}T& back() {assert(!empty());return *(_finish - 1);}const T& back() const {assert(!empty());return *(_finish - 1);}// 预留空间void reserve(size_t input_num) {// 提前保存旧空间大小 释放之后 _start是新的 但_finish是旧的size_t old_size = size();// 输入的值大于容量才触发扩容机制if (input_num > capacity()) {// 开辟一块新的空间 将旧空间数据拷贝到新空间T* tmp = new T[input_num];// 保证_start不为空 才可以将旧空间数据拷贝到新空间if (_start) {// memcpy有局限性 浅拷贝 遇到string list类会出问题//memcpy(tmp, _start, sizeof(T) * size());// 深拷贝for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i]; // 将值一个一个拷贝过去}delete[] _start;}_start = tmp;_finish = _start + size();_end_of_storage = _start + input_num;}}void push_back(const T& input_val) {//// 判断是否需要扩容//if (_finish == _end_of_storage) {//	size_t new_cap = capacity() == 0 ? 4 : 2 * capacity();//	reserve(new_cap);//}//*_finish = input_val;//_finish++;// 复用insertinsert(_finish, input_val);}// 在pos位置插入元素iterator insert(iterator pos, const T& input_val) {// 检查pos的合理性assert(pos <= _finish && pos >= _start);// 判断是否需要扩容if (_finish == _end_of_storage) {//  在reserve的逻辑里 会将_start原来的空间删除 而pos指向的是_start的旧空间// 所以释放旧空间之后 pos是野指针 会引发迭代器失效// 需要记录pos和_start的相对位置size_t len = pos - _start;size_t new_cap = capacity() == 0 ? 4 : 2 * capacity();reserve(new_cap);// 更新pos位置pos = _start + len;}// 从后往前挪动数据iterator end = _finish;while (end != pos) {*end = *(end - 1);end--;}*pos = input_val;_finish++;return pos;}// 删除pos位置元素iterator erase(iterator pos) {// 检查pos的合理性assert(pos < _finish && pos >= _start);// 将pos之后的元素依次往前挪动一位iterator end = pos + 1;while (end != _finish) {*(end - 1) = *end;end++;}_finish--;// 返回删除位置的下一个迭代器(原pos已失效,返回有效迭代器)return pos; // 注意:此时pos指向的是原pos+1的元素(因元素已挪动)}// 按区间删除iterator erase(iterator first, iterator last) {// [first, last)assert(first <= last && first >= _start && last <= _finish);// 计算需要删除的元素个数size_t len = last - first;// 从last开始,向前挪动元素,覆盖被删除区间iterator end = last;while (end != _finish) {*(first++) = *end++; // 用后面的元素覆盖被删除区间}// 更新_finish(减少删除的元素个数)_finish -= len;// 返回删除区间的起始位置(此时已指向原last位置的元素)return first - len;}private:iterator _start;iterator _finish;iterator _end_of_storage;};// 打印vector信息的辅助函数template<class T>void print_my_vector(const Vect::my_vector<T>& v, const std::string& name) {std::cout << name << " - 大小: " << v.size()<< ", 容量: " << v.capacity()<< ", 元素: ";for (size_t i = 0; i < v.size(); ++i) {std::cout << v[i] << " ";}std::cout << std::endl;}// 封装所有测试逻辑的函数void test() {std::cout << "===== 开始测试my_vector所有接口 =====" << std::endl;//  测试默认构造函数Vect::my_vector<int> v1;print_my_vector(v1, "v1(默认构造)");//  测试push_back、size、capacityfor (int i = 1; i <= 5; ++i) {v1.push_back(i);}print_my_vector(v1, "v1(push_back 1-5后)");// 测试reservev1.reserve(10);print_my_vector(v1, "v1(reserve(10)后)");//  测试带参数构造函数Vect::my_vector<int> v2((size_t)3, 10);print_my_vector(v2, "v2(构造3个10)");//  测试迭代器范围构造Vect::my_vector<int> v3(v1.begin() + 2, v1.begin() + 6);print_my_vector(v3, "v3(迭代器范围v1[2]-v1[5])");// 从数组的迭代器区间初始化(数组名可视为指针)int arr[] = { 10,20,30 };my_vector<int> v0(arr, arr + 3); // v0包含元素10,20,30print_my_vector(v0, "v0(迭代器范围arr[0] - arr[2])");//  测试拷贝构造Vect::my_vector<int> v4(v1);print_my_vector(v4, "v4(拷贝v1)");//  测试赋值运算符Vect::my_vector<int> v5;v5 = v2;print_my_vector(v5, "v5(赋值v2)");// 测试operator[]和修改元素v5[0] = 99;v5[1] = 88;print_my_vector(v5, "v5(修改第0位为99、第1位为88后)");//  测试front和backstd::cout << "v5 front: " << v5.front() << ", back: " << v5.back() << std::endl;//  测试insertauto it = v5.insert(v5.begin() + 1, 55);print_my_vector(v5, "v5(在第1位插入55后)");//  测试eraseit = v5.erase(v5.begin() + 3);print_my_vector(v5, "v5(删除第3位元素后)");// 测试emptyVect::my_vector<int> v6;std::cout << "v6是否为空: " << (v6.empty() ? "是" : "否") << std::endl;v6.push_back(1);std::cout << "v6添加1后是否为空: " << (v6.empty() ? "是" : "否") << std::endl;print_my_vector(v6, "v6(添加1后)");std::cout << "===== 所有接口测试完成 =====" << std::endl;}void test_shallow_copy() {// 浅拷贝测试my_vector<int> v10;v10.push_back(10);my_vector<int> v20 = v10; // 浅拷贝,v2与v1共享内存v10.~my_vector(); // 析构v1,释放内存cout << v20[0]; // 错误:v2._start指向已释放的内存(野指针)}void test_reserve() {Vect::my_vector<int> v;v.push_back(1);v.push_back(2); v.push_back(2); v.push_back(2); v.push_back(2); v.push_back(2); cout << "reserve前:size=" << v.size() << ", capacity=" << v.capacity() << endl;v.reserve(10); // 调用reserve,触发错误逻辑cout << "reserve后:size=" << v.size() << ", capacity=" << v.capacity() << endl;}
}// ==== test.cpp ====
#include "my_vector.h"int main() {Vect::test();//Vect::test_shallow_copy();// Vect::test_reserve();return 0;
}

7. 总结

  1. 核心结构:以 _start(内存起始)、_finish(元素末尾)、_end_of_storage(容量末尾)三个指针为基础,size = _finish - _startcapacity = _end_of_storage - _start,所有功能围绕内存布局展开。
  2. 构造与初始化:支持默认构造、n 个元素构造、任意容器迭代器区间构造(先计数再 reserve 优化性能),以及深拷贝构造(避免内存共享问题)。
  3. 扩容机制:容量不足时触发扩容,空容器初始容量默认 4,非空容器按 2 倍扩容;扩容流程为 “分配新内存→拷贝旧元素→释放旧内存→更新指针”,需注意扩容后迭代器失效,需通过偏移量重新计算。
  4. 元素操作
    • 插入(insert/push_back):需先检查扩容,再从后往前挪动元素,避免覆盖;
    • 删除(erase/clear):erase 从后往前覆盖元素,返回有效迭代器,clear 仅重置 _finish 不释放内存;
    • 访问(operator[]/front/back):需加边界检查,避免越界访问。
  5. 效率与安全
    • reserve 提前分配内存,减少扩容次数;
    • 区分深 / 浅拷贝,坚决避免内存共享导致的双重释放、野指针问题。

完结撒花,欢迎各位在评论区交流~

http://www.dtcms.com/a/394002.html

相关文章:

  • 嵌入式开发学习日志29——stm32之定时器中断
  • 通俗范畴论17.3 向量空间的对偶与双对偶
  • 表格 表头增加悬浮提示内容
  • emacs段落重排快捷键
  • 第九届人单合一模式引领论坛举行 构建AI时代的智能交互生态
  • 不用搜驱动!惠普官方工具:自动适配,坏了直接重装
  • JAVA八股文——java虚拟机栈
  • 华为MindSpeed 训练加速库:架构解析
  • Java的Stream实现对list实用操作【持续更新】
  • 【AI智能体】Dify集成 Echarts实现数据报表展示实战详解
  • 【01】EPGF 架构搭建教程之 Anaconda 安装指南
  • 深度学习周报(9.15~9.21)
  • MCP实战:使用 LangGraph 和 MCP 协议无缝集成外部工具
  • 【嵌入式总线通信协议库】
  • 06.【Linux系统编程】命令行参数(给main传参)、环境变量(概念+使用)、进程的虚拟地址空间(用户实际访问的空间)
  • esp32墨水屏天气预测学习
  • LabelImg 操作指南:提高标注速度
  • redhat7.2迁移ssh免密到麒麟v10
  • Linux基操
  • 如何解决 pip install 安装报错 ModuleNotFoundError: No module named ‘protobuf’ 问题
  • EXCEL中公式和文字混合和数字自动变成大写金额
  • Linux软件安装与项目部署
  • Config-配置中心2.0
  • Meta 开源 MobileLLM-R1 系列小参数高效模型,颠覆大模型竞赛
  • 【论文阅读】One-Minute Video Generation with Test-Time Training
  • 玄鸟12600M矿机ETC/ETHW挖矿性能解析与技术参数分析
  • Rust_2025:阶段1:day7.1 类型转换
  • Composer在PHP项目中的手动类自动加载策略
  • kubeasz二进制部署k8s生产环境集群
  • 浏览器缓存