【C++ STL】探索STL的奥秘——vector底层的深度剖析和模拟实现!

【C++ STL】探索STL的奥秘——vector底层的深度剖析和模拟实现!



🎬 MSTcheng个人主页
🔥 个人专栏: 《C语言》《数据结构》《C++由浅入深

⛺️路虽远行则将至,事虽难做则必成!


前言:

在上一篇文章中我们详细的向大家介绍了vector的一些核心接口的使用,那么本篇文章就来深度的剖析一下vector的底层实现。

文章目录

一、vector的基本成员变量

在模拟实现vector之前我们首先要了解vector的基本成员变量,然后在逐步进入到vector的一些核心接口的实现。如何知道这些成员变量呢?下面通过源码一探究竟:
在这里插入图片描述


有了上面的认识,那么我们模拟实现的vector的成员变量就仿照源码来实现:

#include<iostream>usingnamespace std;namespace my_vector {template<classT>classvector{public://vector的迭代器使用的是一个原生指针,因为原生指针本身就能完成迭代器相关的++ * --等这些操作typedef T* iterator;typedefconst T* const_iterator private: iterator _start;//指向空间头部的指针 iterator _finish;//指向最后一个有效数据的下一个位置 iterator _endofstorage;//指向空间的末尾};

以上就是模拟实现的vector的基本框架,成员变量就是_start_finish_endofstorage这三个指针。下面就正式的进入vector的模拟实现,模拟vector的五大步骤:

1、构造相关接口
2、迭代器相关接口
3、空间相关
4、元素访问
5、vector的修改操作

二、vector核心接口的实现

2.1构造相关接口的实现

构造相关的接口主要有以下几种:

默认构造使用n个元素构造拷贝构造初始化列表构造迭代器区间构造operator= 运算符重载
注意:以下实现的接口均是在vector类的内部实现,不像string那样声明和定义可以分离到两个文件。因为我们要自己实现一个vector的模板,而模板的声明和定义是不能分离到两个不同的文件的,同一个文件下可以。
//vector的默认构造vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}//使用n个val初始化//这里的T()是调用T对应的构造 是为了能够让任意类型都能够调用 如果T是内置类型 就会调用内置类型的构造 如果T是自定义类型就调用自定义类型自己的默认构造vector(size_t n, T val =T()){resize(n, val);//resize接口后面会讲}//拷贝构造 ```cpp //拷贝构造 v2(v1)vector(const vector<T>& v){//开与v1一样大的空间reserve(v.size());//reserve接口后面会介绍//底层也是调用push_backfor(auto e : v){push_back(e);//尾插,后面会介绍}}//使用初始化列表构造 示例:vector<int> v1={1,2,3,4,5}; 花括号的内容其实是转化成了initializer_list对象 这里不理解的可以看上一篇文章!vector(initializer_list<T> il){//根据所给的对象il开空间然后调用push_backreserve(il.size());for(auto e : il){//底层是this指针调用的pus_back this指针存的是要构造的vector对象的地址push_back(e);}}//迭代器区间构造 搞成函数模板支持泛型 形参可以是任意容器的迭代器template<classInputiterator>vector(Inputiterator first, Inputiterator last){//底层调的还是push_backwhile(first != last){push_back(*(first)); first++;}}//赋值重载 底层使用交换函数交换底层的成员变量 vector operator=(const vector<T>& v){swap(v);return*this;}voidswap(const vector<T>& v){ std::swap(_start,v._start); std::swap(_finish,v._finish); std::swap(_endofstorage,v._endofstorage);}//析构~vector(){if(_start){delete[] _start; _start = _finish = _endofstorage =nullptr;}}

2.2迭代器相关的接口实现

vector的迭代器主要实现的是普通迭代器和const迭代器:
//普通迭代器 iterator begin(){return _start;} iterator end(){//返回的是finish不是endofstoragereturn _finish;}//const迭代器 const_iterator begin()const{return _start;} const_iterator end()const{//返回的是finish不是endofstoragereturn _finish;}
反向迭代器就是与正向迭代器相反,rbegin()返回end()-1rend()返回begin()-1

2.3空间相关的接口的实现

与空间相关的接口有:

1、size() ——> 记录有效数据个数
2、capccity() ——> 记录总的空间大小
3、empty() ——> 判空
4、resize() ——> 扩容 影响size
5、eserve() ——> 扩容 不影响size
注意:vector中空间相关的接口就属reserve接口最为重要,它主要负责vector的扩容逻辑,而resize接口也可以扩容但是两者有本质的区别,通过下面的底层实现你就能一探究竟:
//size和capacity通过两个指针相减可以计算它们之间的数据个数 size_t size()const{return _finish - _start;} size_t capacity(){return _endofstorage - _start;}boolempty()const{return _start == _finish;}voidresize(size_t n, T val =T()){if(n >size()){reserve(n);//n>size后面的n-size个空间使用val来填充while(_finish != _start + n){*_finish = val;++_finish;}}else//n<size 缩容影响size 有效数据直接缩到n处{ _finish = _start + n;}}voidreserve(size_t n){//在start更新前要保存一下sizeauto oldsize =size();if(n >size()){//开辟新空间 拷贝旧数据 iterator tmp =new T[n];//拷贝旧数据if(_start){//memcpy深层次的拷贝问题 原因对于自定义类型是浅拷贝 delete的时候析构两次//memcpy(tmp, _start, size()*sizeof(T));//使用赋值重载来避免这种问题!!!for(size_t i =0; i < old_size; i++){ tmp[i]= _start[i];}delete[] _start;} _start = tmp;//更新有效数据个数 _finish = _start + oldsize; _endofstorage = _start + n;}}

2.3.1memcpy深层次的浅拷贝问题

注意这里有一个很容易留坑的点:就是memcpy生层次的浅拷贝问题:
怎么才能解决呢?调用赋值重载完成深拷贝就可以。

2.4元素访问相关的接口实现

元素访问相关的接口最常使用的就是operator[],而vector的迭代器使用的是原生指针,那么operator[]无非就是访问某个下标的元素,下面看代码:
//支持读和写操作const T&operator[](size_t i){//判断下标是否合法assert(i <size());return _start[i];//实际上转化为指针的解引用: *(_start+i)}//加上const后只读const T&operator[](size_t i)const{assert(i <size());return _start[i];}

2.5vector修改相关的接口实现

vector修改相关的接口无非就是插入删除,插入有尾插,任意位置插入,删除有尾删,以及任意位置的删除,实现这些插入,删除函数时有较多的细节需要注意。下面给出代码再一点点的剖析。
//尾插voidpush_back(const T& x){//插入要考虑空间是否足够if(_finish == _endofstorage){reserve(capacity()==0?4:2*capacity());}*_finish = x;++_finish;}//尾删voidpop_back(){assert(!empty());--_finish;} iterator insert(iterator pos,const T& x){assert(pos <= _finish);assert(pos >= _start);//插入首先判断空间是否足够if(_finish == _endofstorage){//法一 计算相对位置  size_t n = pos - _start;//扩容 异地扩容会导致迭代器失效 在这里就是野指针reserve(capacity()==0?4:2*capacity());//扩容后更新pos pos = _start + n;}//挪动数据 iterator end = _finish;while(end >= pos){*(end +1)=*(end); end--;}*(pos)= x; _finish++;return pos;}//版本二 返回迭代器 iterator erase(iterator pos){//检查pos的合法性assert(pos <= _finish);assert(pos >= _start);//删除要判断容器是否为空if(!empty()){//删除也会引发迭代器失效 iterator it = pos;while(it < _finish){//*(it)=*(it +1);++it;}--_finish;}return pos;}

三,插入删除引起的迭代器失效问题

voidtest_vector9(){ my_vector::vector<int> v{1,2,3,4};auto it = v.begin(); v.push_back(5);//这里会扩容while(it != v.end()){ cout <<*it <<" ";*it =100;++it;} cout << endl;}
在这里插入图片描述
注意:上面的插入元素的过程中会引发迭代器失效导致打印的全是随机值(有时候会奔溃),至于为什么会失效我们画图来分析:
1、插入引起的迭代器失效
voidtest_vector10(){//删除v中所有的偶数  my_vector::vector<int> v{1,2,3,4,5,6};auto it = v.begin();while(it != v.end()){if(*it %2==0) v.erase(it);++it;//erase删除的迭代器如果是最后一个元素++it导致程序崩溃}for(auto e : v) cout << e <<" "; cout << endl;return0;}
在这里插入图片描述
2、删除引起的迭代器失效

四、总结

以上就是vector的模拟实现、memcpy的深层次的浅拷贝问题、迭代器失效的所有内容啦,其中迭代器失效在我们平时的使用中稍不留神就会出错,所以我们要多多留意。有任何问题欢迎加我交流!
在这里插入图片描述

Read more

【YOLOv8+CAA+HSFPN】频率检测识别算法改进与实现_1

1. YOLOv8+CAA+HSFPN频率检测识别算法改进与实现 1.1. 摘要 本文针对频率检测识别任务中的复杂环境挑战,提出了一种基于YOLOv8的改进算法,结合通道注意力机制(CAA)和高效特征金字塔网络(HSFPN)。通过在骨干网络中引入CAA模块增强特征表达能力,并在特征融合阶段采用HSFPN结构,有效提升了模型对不同频率特征的捕捉能力。实验表明,改进后的算法在频率检测任务中mAP提升了3.2%,推理速度提高了15%,为实时频率信号处理提供了新思路。 1.2. 引言 频率检测识别在通信、雷达、声呐等领域具有广泛应用。传统方法如FFT、小波变换等在复杂环境下表现不佳,而深度学习方法虽然取得了一定进展,但仍面临特征提取不充分、计算效率低等问题。YOLOv8作为最新的目标检测框架,其高效的C2f模块和SPPF结构为频率检测提供了新思路。本文通过引入通道注意力机制和改进特征金字塔网络,构建了YOLOv8+CAA+HSFPN模型,显著提升了频率检测性能。 1.3. 频率检测任务特点分析 频率检测任务与一般目标检测存在显著差异: 1. 信号特性:频率信号通常呈现周期

By Ne0inhk
【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 目录 027  寻找数组的中心下标  1.1  算法思路:前缀和 1.2  算法实现 1.2.1  C++实现 1.2.2  Java实现 1.3  博主手记 028  除自身以外数组的乘积 2.1  算法思路 2.2  算法实现

By Ne0inhk
数据结构:⼆叉树(1)

数据结构:⼆叉树(1)

目录 前言  树部分知识: 一.树的概念和结构 二.树的一些相关术语和定义  三.树的实现结构(了解部分) 四、树的应用场景 二叉树部分知识讲解: 一.二叉树概念与结构 二.特殊二叉树类型 1.满二叉树 2.完全二叉树 3.性质补充 三、⼆叉树存储结构 顺序结构: 编辑应用: 链式结构: 四、堆的概念与结构 1.实现顺序结构⼆叉树: 2.堆的概念与结构 (重点) 3.堆的实现 五、堆的实现代码部分 1.堆的初始化:(本次实现选取大堆为例) 2.堆的销毁: 3.堆的插入数据 : 4.堆打印值 : 六、

By Ne0inhk
Flutter 三方库 libsignal 的鸿蒙化适配指南 - 实现 Signal 协议加密通信、双大鼠(Double Ratchet)算法与前向安全性保障

Flutter 三方库 libsignal 的鸿蒙化适配指南 - 实现 Signal 协议加密通信、双大鼠(Double Ratchet)算法与前向安全性保障

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 libsignal 的鸿蒙化适配指南 - 实现 Signal 协议加密通信、双大鼠(Double Ratchet)算法与前向安全性保障 前言 在 Flutter for OpenHarmony 的高度安全通信领域,Signal 协议是目前全球公认的即时通讯加密标准。libsignal 是 Signal 协议的核心 Dart 实现。它能够为鸿蒙应用提供从身份认证到会话加密的全套解决方案,确保每一个字节的通信都具备前向安全性(Forward Secrecy)。本文将深入解析如何在鸿蒙端利用该库构建极致安全的加密通信能力。 一、原理解析 / 概念介绍 1.1 基础原理 Signal 协议的核心在于“双大鼠(Double Ratchet)”算法。它结合了 Diffie-Hellman

By Ne0inhk