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

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


在这里插入图片描述

🎬 MSTcheng:个人主页

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

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

前言:

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

文章目录

  • 一、vector的基本成员变量
  • 二、vector核心接口的实现
    • 2.1构造相关接口的实现
    • 2.2迭代器相关的接口实现
    • 2.3空间相关的接口的实现
      • 2.3.1memcpy深层次的浅拷贝问题
    • 2.4元素访问相关的接口实现
    • 2.5vector修改相关的接口实现
  • 三,插入删除引起的迭代器失效问题
  • 四、总结

一、vector的基本成员变量

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

在这里插入图片描述
有了上面的认识,那么我们模拟实现的vector的成员变量就仿照源码来实现:

#include<iostream>
using namespace std;
namespace my_vector
{template<class T>class vector{public://vector的迭代器使用的是一个原生指针,因为原生指针本身就能完成迭代器相关的++ * --等这些操作typedef T* iterator;typedef const T* const_iteratorprivate: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<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
{//底层调的还是push_backwhile (first != last){push_back(*(first));first++;}
}//赋值重载 底层使用交换函数交换底层的成员变量
vector operator=(const vector<T>& v)
{swap(v);return *this;
}void swap(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;
}bool empty() const
{return _start == _finish;
}void resize(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;}
}void reserve(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修改相关的接口无非就是插入删除,插入有尾插,任意位置插入,删除有尾删,以及任意位置的删除,实现这些插入,删除函数时有较多的细节需要注意。下面给出代码再一点点的剖析。

//尾插
void push_back(const T& x)
{//插入要考虑空间是否足够if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;++_finish;
}
//尾删
void pop_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());//扩容后更新pospos = _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;
}

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

void test_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、插入引起的迭代器失效在这里插入图片描述

void test_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;return 0;
}

在这里插入图片描述

2、删除引起的迭代器失效
在这里插入图片描述

四、总结

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

在这里插入图片描述

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

相关文章:

  • STM32CUBEMX安装离线库
  • 体验 Suno v5:最新的 Suno AI 音乐模型
  • 2.4 欧拉集群安装Nova计算服务
  • 贵港网站建设兼职免费广告设计网站
  • Cell Mol Biol Lett|Runx2诱导超级沉默子形成下调Lpl表达:重塑雪旺细胞脂质代谢的新机制
  • 国自然·医工交叉热点|泛癌组织学重建AI模型
  • Python依赖管理与环境迁移实战:Poetry+Docker构建高效开发体系
  • 山西网站建设推荐景区网站如何建设
  • Flutter---CupertinoPicker滚动选择器
  • 全面掌握PostgreSQL关系型数据库,备份和恢复,笔记46和笔记47
  • Python SQLAlchemy模块:从入门到实战的数据库操作指南
  • 天津哪里有做网站的jquery wordpress
  • 流媒体网站建设规划亚马逊网站建设案例
  • PHP 异步IO扩展包 AsyncIO v2.0.0 发布
  • 《信息系统项目管理师》案例分析题及解析模拟题5
  • Jenkins上实现CI集成软件信息Teams群通知案例实现。
  • ZYNQ平台中断服务函数中的变量不加volatile修饰导致的奇怪问题解决
  • 2026年UX/UI五大趋势:AI、AR与包容性设计将重新定义用户体验
  • 网站做跳转自己建网站卖鞋
  • 百度网站服务器外贸网站优化
  • 应广单片机烧录跳线J7专用PCB使用说明
  • Java 前后端加密与编码技术:从概念到实战场景全解析
  • 拒绝笨重,一款轻量、极致简洁的开源接口管理工具 - PostIn
  • 建设银行信用卡网站是哪个茶叶seo网站推广与优化方案
  • vant van-uploader上传file文件;回显时使用imageId拼接路径
  • Java常用中间件整理讲解——Redis,RabbitMQ
  • JavaEE初阶7.0
  • 从“天书”到源码:HarmonyOS NEXT 崩溃堆栈解析实战指南
  • 个人网站收款google play 应用商店
  • _撸猫websocket服务器端,手机远程服务端