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

C++ vector深度剖析与模拟实现:探索模板的泛型应用

前引:在C++标准模板库STL中,vector是最常用的容器之一。它以动态数组的形式提供联系内存存储,支持随机访问和高效的尾部插入\删除操作。然而,其底层实现远非简单的数组封装,而是通过精妙的内存管理策略和数据结构设计,平衡了性能与灵活性。本文将深入探讨vector的底层实现原理,包括其核心数据结构、动态扩容机制、迭代器设计,以及实际应用中的注意事项~

在上一个容器 string 的模拟实现中,我们发现 string 的模拟实现有些单调,它仅仅操作字符串,通过划分空间+类实现它的各种接口功能即可,难度还比较正常,思维逻辑正确代码不是问题;今天的 vector 作为新学的一个容器,它比 string 要复杂一些,因为它可以接纳各种类型,这就需要用到我们之前学的模板,不仅仅是写一个类这么简单~下面开始今天的vector实现,正文开始!

目录

vector的核心数据结构

模板框架搭建

构造初始化

 析构函数

尾插数据 

扩容

扩容+初始化

​编辑​编辑​编辑

打印

迭代器

删除指定位置元素

插入元素在指定位置之前

迭代器失效

拷贝构造

赋值重载


vector的核心数据结构

vector 的底层实现依赖于三个关键指针(或者迭代器),它们共同管理内存空间与元素布局:

_start:指向数组的起始位置,即第一个元素的内存地址

_finish:指向最后一个元素的下一个位置,用于标记已分配但未使用的空间边界

_end_of_storage:指向当前分配内存的末尾,标记整个连续内存块的结束

 这三个指针的动态调整是 vector 实现高效内存管理的核心。例如:当插入元素空间不足时,_finish会触发扩容逻辑,分配更大的内存块并迁移数据!下面我们来通过2倍扩容来实现 vector

模板框架搭建

既然 vector 的实现是依靠模板来的,那么推理出来就是:在自定义空间中用模板实现类

namespace Seek
{//vector模板template<class T>class vector{public://实现private:T* _start;T* _finish;T* _end_of_storage;};
}

类模板的实例化:空间声明+模板类类型

Seek::vector<int> S1;

构造初始化

观察库里面的 Vector 我们发现在没有任何参数时capacity也是0,例如:

那么我们开始只需要将三个指针全部初始化为空就行了

//构造初始化
vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{ }

 析构函数

析构不能释放空,因此需要先判断指针是否开辟了空间,然后再置空

//析构
~vector()
{//判断assert(_start);//释放空间delete[]_start;//置空_start = _finish = _end_of_storage = nullptr;
}

尾插数据 

首次学习 vector 实现我们以整形为主进行学习

在尾插时我们可能需要更改三个指针的位置,因此需要先计算一下:size()、capacity

原理:指针-指针=中间的元素个数(通过这样我们可以控制三个指针的移动) 

//size
size_t size()const
{return _finish - _start;
}
//capacity
size_t capacity()const
{return _end_of_storage - _start;
}

在尾插时需要考虑:如果空间已满 或者 _start为空。如果_start为空,那么无法使用memcpy

//尾插
void push_back(const T pc)
{if (_finish == _end_of_storage){//说明此时空间已满 或者 空间为空size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();//如果_start为空,那么无法使用memcpyif (_start == nullptr){_start = new T[newcapacity];_finish = _start;_end_of_storage = _start + newcapacity;}else//开空间reserve(newcapacity);}//存*_finish = pc;_finish++;
}

扩容

扩容就是 reserve ,传入指定数量的空间,reserve负责开辟,然后更新三个指针指向新空间

//扩容
void reserve(size_t newcapacity)
{assert(newcapacity > 0);//记录size_t size_tmp = size();//开辟空间T* tmp = new T[newcapacity];//拷贝内容memcpy(tmp, _start, sizeof(T) * newcapacity);//释放空间delete[]_start;//更新指针_start = tmp;_finish = _start + size_tmp;_end_of_storage = _start + newcapacity;
}

注意:我们开辟新空间后,很容易忘记_finish的指向,而在释放原空间之前,需要标记元素个数

扩容+初始化

如果resize小于_finish,那么就保留resize及其以前的数据

如果resize大于_finish,那么就需要扩容+初始化(如果没有给初始值,调用构造函数)

//扩容+初始化
void resize(size_t n, const T& val=T())
{if (n < size()){//直接删while (size() > n){_finish--;}}else{//扩容reserve(n);while (n != size()){*_finish = T();_finish++;}}
}

效果展示:

打印

如果实现流提取,虽然可以用友元解决,因为是模板无法识别变量类型,所以在成员函数内完成

而vector的类型太多了,如果去为它实现一个流提取是没有必要的,不向string是确定的 

数组属于连续存储的,因此直接解引用\下标就可以拿到数组元素,连续的内存存储也支持加加的

//打印
void Print()const
{assert(_start);for (T* tmp = _start ; tmp != _finish ; tmp++){cout << *tmp << " ";}cout << endl;
}

效果展示:

迭代器

迭代器应该是返回有效元素的这个区间的指针指向,而不是放大到容量

这里数据的开始刚好是_start

末尾的下一个位置刚好是_finish \ _start+元素个数

//迭代器
iterator begin()const
{return _start;
}
iterator end()const
{return _start + size();
}

删除指定位置元素

可以看到删除指定位置元素的参数是迭代器类型:iterator,这里需要注意的是_finish的位置

//删除指定位置元素
void erase(iterator pos)
{//判断有效性assert(pos >= _start && pos < _finish);//挪动元素while (pos + 1 < _finish){*pos = *(pos + 1);pos++;}_finish--;
}

效果展示:

插入元素在指定位置之前

 可看到它的位置也是根据迭代器去做参数的

//插入元素在指定位置之前
void insert(iterator pos, T tmp)
{//检查范围assert(pos > _start && pos <= _finish);//看是否需要扩容if (_finish == _end_of_storage){size_t newcapacity = capacity() * 2;reserve(newcapacity);}//挪动元素iterator it = _finish;while (it >= pos){*it = *(it - 1);it--;}*(pos - 1) = tmp;_finish++;
}

迭代器失效

 迭代器失效的场景:

(1)insert 插入触发内存扩容,所有原有迭代器、指针、引用失效

(2)insert 插入未触发内存扩容,但元素位置移动,之后的迭代器失效

(3)erase 删除元素之后,被删除元素及其之后的所有迭代器失效

(4)clear 清理元素之后,所有迭代器失效

迭代器失效的原因(以下两种都会使原来的迭代器全部失效):

(1)内存的重新分配,vector是基于动态数组实现的容器,当元素超过容量时实现扩容,vector             重新分配内存(比如 insert )

(2)元素移动:即使没有内存重新分配,insert 会使插入的元素后移,erase 会使删除的元素前移

具有代表的就是 insert 和 erase 接口,迭代器失效与内部实现有关,比如STL里面的:

如果要解决可以接收 erase 的返回值获取下一个有效的迭代器,例如:

拷贝构造

拷贝构造是用一个已经存在的对象去创建+初始化另一个对象 

注意:不能使用memcpy,因为memcpy是按字节拷贝,如果是自定义类型会发生浅拷贝情况

           虽然这里的 T 是int类型,但是为了泛化使用,避免自定义类型发生浅拷贝

           对于连续的地址,可以使用下标[ ] 或者 直接解引用访问内容

//拷贝构造
vector(const vector<T>& V)
{_start = _finish = _end_of_storage = nullptr;//开空间_start = new T[V.capacity()];//拷贝iterator tmp = V._start;iterator it = _start;while (tmp != V._finish){*it = *tmp;it++ ;tmp++;}_finish = _start + V.size();_end_of_storage = _start + V.capacity();
}

效果展示:

赋值重载

我们可以通过拷贝构造出中间变量,再去交换临时变量的指向来完成初始化

//赋值运算符重载
vector<T>& operator=(const vector<T>& V1)
{swap(V1);return *this;
}
void swap(const vector<T>& V1)
{//V2是临时的,出了函数会销毁vector<T> V2(V1);std::swap(_start, V2._start);std::swap(_finish, V2._finish);std::swap(_end_of_storage, V2._end_of_storage);
}

注意:开始我们构造出的*this是空的,我们利用另一个对象V1去深拷贝出V2,再将V2的内容给交             换给*this,当出了swap函数去释放V2的时候,V2已经全部是空了,所以不要使用断言

                                                 【雾非雾】期待与你的下次相遇! 

相关文章:

  • 腾讯云国际站缩容:策略、考量与实践
  • 智慧园区建设资料合集(Wordppt原件)
  • Spring Boot 中的条件装配:@Conditional 系列注解详解
  • 答辩讲解387基于Spring Boot的心理健康管理系统
  • 【Python系列PyCharm实战】ModuleNotFoundError: No module named ‘sklearn’ 系列Bug解决方案大全
  • Windows Server系统只有命令行不显示桌面的解决方法
  • 【超详细】讯飞智能车PC电脑烧录指南(高级系统部署与恢复)
  • LDPC码校验矩阵和生成矩阵的生成
  • Java在IDEA中终端窗口输出正常,但打包成JAR后中文乱码问题
  • 《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- 实战基于CSI2 Rx 构建高性能摄像头输入系统
  • 51c嵌入式~电路~合集2
  • 【沉浸式解决问题】优化MySQL中多表union速度慢的问题
  • 数字化零售如何全面优化顾客体验
  • 深度融合数智化,百胜软件联合华为云加速零售行业转型升级
  • 删除大表数据注意事项
  • 字符操作函数续上
  • Vue 实现图片裁剪功能:基于 vue-cropper 和 Element Plus 的完整解决方案
  • AI Agent学习 -- (2)LangChain的安装
  • 全面探索 KingbaseES 在线数据库平台:从开发到优化的全方位体验
  • ROS学习话题通信之Python实现
  • 简单网站建设策划书范文/今日热点新闻2022
  • 济源市网站建设/杭州seo工作室
  • 网站建设公司那家好/广告宣传费用一般多少
  • 查找网站备案信息/seo关键词优化价格
  • asp.net开发移动网站模板下载/互联网营销师证书含金量
  • 一个主机可以放几个网站/市场推广方案和思路