vector模拟实现【C++】

文章目录

  • 全部的实现代码放在了文章末尾
  • 准备工作
    • 包含头文件
    • 定义命名空间和类
      • 类的成员变量
  • 迭代器
    • 迭代器获取函数
  • 构造函数
    • 默认构造
    • 使用n个值构造
    • 迭代器区间构造
    • 解决迭代器区间构造和用n个值构造的冲突
    • 拷贝构造
  • 析构函数
  • swap【交换函数】
  • 赋值运算符重载
  • empty
  • size和capacity
  • operator[]
  • reserve【调整容量大小】
  • resize【调整size大小】
  • push_back
  • assign【把所有数据替换成迭代器区间中的数据】
  • insert
    • 为什么扩容会导致pos迭代器失效?
    • 为什么要返回pos-1?
  • erase
    • 为什么要返回pos?
  • 全部代码

全部的实现代码放在了文章末尾

准备工作

创建两个文件,一个头文件myvector.hpp,一个源文件 tesr.cpp
【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件myvector.hpp中】

  1. mystring.h:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义

  2. test.cpp:存放main函数,以及测试代码


包含头文件

  1. iostream:用于输入输出

  2. assert.h:用于使用报错函数assert


定义命名空间和类

在文件myvector.hpp中定义上一个命名空间myvector
把vector类和它的成员函数放进命名空间封装起来,防止与包含的头文件中的函数/变量重名的冲突问题

类的成员变量

参考了stl源码中的vector的实现
成员变量有3个,都是迭代器

在这里插入图片描述
画图理解一下
在这里插入图片描述


迭代器

迭代器
因为存放数据的空间是从堆区申请的连续的内存,且只是简单模拟

所以我用了指针T*作为普通迭代器,const T*作为const迭代器
T是vector中存储的数据的类型

直接把T*重命名为iterator,把const T*重命名为const_iterator就完成了迭代器的实现

在这里插入图片描述

迭代器获取函数

在这里插入图片描述

因为const修饰的对象只能调用const修饰的成员函数
所以如果是const修饰的对象调用begin()和end()的时候,就会自动调用到const修饰的begin和end.


构造函数

默认构造

因为stl库里实现的默认构造是没开空间的,所以默认构造直接让3个成员变量都为nullptr就行

直接在声明的时候给缺省值,缺省值会传给成员初始化列表

在这里插入图片描述

而成员初始化列表会比构造函数先调用
并且每个构造函数调用之前都会先调用成员初始化列表,这样不管调用哪一个构造函数初始化,都会先把3个成员变量初始化成nullptr


使用n个值构造

在这里插入图片描述


迭代器区间构造

在这里插入图片描述


解决迭代器区间构造和用n个值构造的冲突

当重载了迭代器区间构造和用n个值构造的时候
如果传入的两个参数都是int类型的话就会报错

为什么?
因为在模板函数构成重载时,编译器会调用更合适的那一个
什么叫更合适?
就是不会类型转

如果传入的两个参数都是int类型,那么调用的应该是使用n个值构造,因为没有int类型的迭代器

但是使用n个值构造的第一个参数是size_t,int传进去要隐式类型转换
而调用迭代器区间构造,两个int的实参传进去,就会直接把InputIterator推导成int,不会发生类型转换,所以编译器会调用迭代器区间构造

解决方法:
再重载一个使用n个值构造的函数,把第一个参数改成int
在这里插入图片描述


拷贝构造

因为成员申请了堆区空间,所以要深拷贝
【不知道什么是深拷贝的可以看我这篇文章:类和对象【三】析构函数和拷贝构造函数】
在这里插入图片描述


析构函数

在这里插入图片描述


swap【交换函数】

因为存放数据的空间是在堆区开辟的,用3个成员变量去指向的

所以直接交换两个对象的成员变量就可以了

不需要拷贝数据

在这里插入图片描述


赋值运算符重载

因为成员申请了堆区空间,所以要深拷贝
【不知道什么是深拷贝的可以看我这篇文章:类和对象【三】析构函数和拷贝构造函数】

在这里插入图片描述
为什么上面的两句代码就可以完成深拷贝呢?
这是因为:

使用了传值传参,会在传参之前调用拷贝构造,再把拷贝构造出的临时对象作为参数传递进去

赋值运算符的左操作数,*this再与传入的临时对象obj交换,就直接完成了拷贝

在函数结束之后,存储在栈区的obj再函数结束之后,obj生命周期结束

obj调用析构函数,把指向的从*this那里交换来的不需要的空间销毁


empty

在这里插入图片描述


size和capacity

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述


operator[]

在这里插入图片描述
因为
const修饰的对象只能调用const修饰的成员函数

所以const对象只会调用下面的那个重载


reserve【调整容量大小】

在这里插入图片描述


resize【调整size大小】

在这里插入图片描述


push_back

在这里插入图片描述


assign【把所有数据替换成迭代器区间中的数据】

在这里插入图片描述


insert

	iterator insert(iterator pos, const T& val){assert(pos <= _finish);  防止插入的位置是 越界的if (_finish == _end_of_storage)  如果容量满了{记录一下扩容前的pos与start的相对位置因为扩容的话会导致pos迭代器失效size_t n = pos-_start;if (capacity() == 0)  如果容量为0reserve(2);else  容量不为0,就扩2reserve(capacity() * 2);更新pospos = _start + n;}iterator it = end()-1;把pos及其之后的数据向后挪动一位while (it >= pos){*(it + 1) = *it;it--;}_finish++;*pos = val;插入数据返回指向新插入的数据的迭代器  用于处理迭代器失效问题return pos-1;}

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

在这里插入图片描述
因为扩容之后原来的空间被释放了
又因为使用的扩容方式是reserve所以那3个成员变量的值扩容后可以指向正确的位置。
但是pos如果不更新的话,就还是指向被释放的空间,就成了野指针了。

更新方法也很简单,保存扩容之前的pos与start的相对距离n,扩容之后再让pos=_start+n就可以了。

为什么要返回pos-1?

这是stl库里面处理迭代器失效的方法之一
因为我们在使用stl库里面的insert函数的时候,是不知道什么时候会扩容的【每个平台实现的vector是不同的
只能默认使用了之后传进去pos,在调用一次insert之后就失效了,失效的迭代器是不能使用的。
所以如果还要用pos就要把它更新一下,stl库里提供的更新方式就是:
==让pos接收insert的返回值。【pos是传值调用,形参改变不影响实参】==并且规定insert的返回值要是指向新插入的数据的迭代器


erase

在这里插入图片描述

为什么要返回pos?

因为使用了erase之后的迭代器也会失效,需要提供更新的方法

为什么使用了erase之后的迭代器会失效?

  1. 不确定是否删除到一定数据时,会不会减小容量,以适应size
    此时和insert的一样,因为不能部分释放,所以会把原来的空间释放掉,申请新空间
  2. 不确定是否删除的是最后一个数据,如果是那么调用完erase之后pos指向的就
    不是vector的有效数据范围了

所以和insert一样,调用了erase之后如果还要使用pos,就要接收返回值。

stl库里面规定erase的返回值是指向删除数据的下一个数据的迭代器,因为挪动覆盖的原因,下一个数据就是pos指向的数据,所以返回pos【没有接收返回值的迭代器,在检测较严格的编译器中,不管指向的位置是否正确,都会禁止使用,使用了就报错


全部代码

#include<iostream>
#include<assert.h>using namespace std;namespace myvector
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector(){}vector(size_t n,const T& val=T()){_start = new T[n];//从堆区可容纳申请n个元素大小的空间_finish = _start;//还没有 有效数据 时start与finish重合_end_of_storage = _start + n;//指向最大容量 的 下一个位置for (size_t i = 0; i < n; i++)//循环n次{push_back(val);//把数据尾插进去}}vector(int n, const T& val = T()){_start = new T[n];_finish = _start;_end_of_storage = _start + n;for (size_t i = 0; i < n; i++){push_back(val);}}template<class InputIterator>vector(InputIterator first, InputIterator last){//使用迭代器进行循环while (first != last){push_back(*first);//把数据尾插进去++first;}}void swap(vector<T>& obj){//使用库里面的swap交换3个成员变量std::swap(_start, obj._start);std::swap(_finish, obj._finish);std::swap(_end_of_storage, obj._end_of_storage);}vector(const vector<T>& obj){size_t size = obj.size();//记录有效数据个数size_t capacity = obj.capacity();//记录容量大小_start = new T[capacity];//申请与obj相同大小的空间_end_of_storage = _start + capacity;//指向最大容量 的 下一个位置for (size_t i = 0; i < size; i++)//循环size次{_start[i] = obj._start[i];//把有效数据拷贝过去}_finish = _start + size;//指向最后一个有效数据的  下一个 位置}~vector(){//释放从堆区申请的空间delete[] _start;//把3个成员变量  置空_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}vector<T>& operator= (vector<T> obj){swap(obj);return *this;}bool empty() const{//如果size等于0,就是空的return size() == 0;}size_t size()const{//finish指向最后一个有效数据的 下一个//start指向第一个有效数据//两个指针相减就是  两个指针之间的  数据个数return _finish - _start;}size_t capacity()const{//end_of_storage指向最大容量的 下一个位置//start指向第一个有效数据//两个指针相减就是  两个指针之间的  数据个数return _end_of_storage - _start;}T& operator[] (size_t n){//防止越界访问assert(n < size());//因为start是T*类型,所以可以像数组一样直接随机访问return _start[n];}const T& operator[] (size_t n) const{//防止越界访问assert(n < size());//因为start是T*类型,所以可以像数组一样直接随机访问return _start[n];}iterator begin()//普通起始迭代器{return _start;}iterator end()// 普通结束迭代器{return _finish;}const_iterator begin()const//const起始迭代器{return _start;}const_iterator end()const//const结束迭代器{return _finish;}void reserve(size_t n){if (n > capacity())//要调整的容量n,大于capacity才扩容{size_t origsize = size();//记录扩容前的sizeT* tmp = new T[n];//申请空间//把原来的数据拷贝到  新空间for (size_t i = 0; i <origsize; i++){tmp[i] = _start[i];}delete[] _start;//释放旧空间//让成员变量指向  新的空间的相对位置_start = tmp;_finish = _start + origsize;_end_of_storage = _start + n;}}void resize(size_t n, const T& val = T()){if (size() == n)//如果size与要调整的n相等return;//直接返回else if (size() < n)//如果size小于n{if (n > capacity())//如果n大于capacity{reserve(n);//把容量调整到n}//再把size到n 之间的空间用  val填上for (size_t i = size(); i < n; i++){push_back(val);}}else//如果size  大于  n{//就调整标识有效数据的末尾的finish//让size=_finish - _start = n_finish = _start + n;}}void push_back(const T&val){if (_end_of_storage == nullptr)//如果容量为0{reserve(2);//把容量调整到可容纳 2个元素大小}else if (_finish==_end_of_storage)//容量满了{reserve(capacity()*2);//扩容}//在下标为size【最后一个有效数据的下一个】//插入值_start[size()] = val;_finish++;//更新有效数据的末尾}void pop_back(){assret(size() > 0);_finish--;}template <class InputIterator>void assign(InputIterator first, InputIterator last){delete[] _start;//释放原来申请的空间//把3个成员变量置空_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;while (first != last){//一个一个尾插进去push_back(*first);++first;}}iterator insert(iterator pos, const T& val){assert(pos <= _finish);//防止插入的位置是 越界的if (_finish == _end_of_storage)//如果容量满了{//记录一下扩容前的pos与start的相对位置//因为扩容的话会导致pos迭代器失效size_t n = pos-_start;if (capacity() == 0)//如果容量为0reserve(2);else//容量不为0,就扩2倍reserve(capacity() * 2);//更新pospos = _start + n;}iterator it = end()-1;//把pos及其之后的数据向后挪动一位while (it >= pos){*(it + 1) = *it;it--;}_finish++;*pos = val;//插入数据return pos-1;}template <class InputIterator>void insert(iterator pos, InputIterator first, InputIterator last){assert(pos <= _finish);size_t len = 0;InputIterator in = first;while (in != last){in++;len++;}if (_finish + len >= _end_of_storage){size_t n = pos - _start;if (capacity() == 0){reserve(len);}reserve(capacity()+len);pos = _start + n;}iterator it = end() - 1;while (it >= pos){*(it + len) = *it;it--;}_finish+=len;it = pos;while (it != pos + len){*it = *first;it++;first++;}}iterator erase(iterator pos){// 防止传入的pos 是越界的assert(pos < _finish);iterator it = pos;//把pos之后的数据都向前挪动一位,把pos指向的位置给覆盖掉while (it <end()-1){*it = *(it + 1);it++;}//更新数据末尾 迭代器_finish--;//返回posreturn pos;}iterator erase(iterator first, iterator last){assert(first >= begin());assert(last <= end());iterator fi = first;iterator la = last;size_t len = last - first;while (la != end()){*fi = *la;fi++;la++;}_finish -= len;return first;}private://start指向从堆区申请的空间的  起始  位置,与begin()返回的迭代器相等//标识有效数组的开始iterator _start = nullptr;//finish指向  最后一个有效数据的  下一个位置,与end()返回的迭代器相等//标识有效数据的结束iterator _finish = nullptr;//_end_of_storage指向从堆区申请的空间的 末尾的 下一个位置//标识容量iterator _end_of_storage = nullptr;};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/39960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

N5 使用Gensim库训练Word2Vec模型

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊# 前言 前言 这周学习训练一个Word2Vec模型&#xff0c;并进行一些基本的词向量操作。 Word2Vec 模型 Word2Vec 是一种基于神经网络的词向量表示方法&#x…

mysql8.0为什么要移除查询缓存,对应又做了哪些优化,进来看看,一定获益匪浅

前言 MySQL 查询缓存是一个用于缓存查询结果的机制&#xff0c;通过减少对数据库的重复查询来提高性能。在 MySQL 5.7 及其之前的版本中&#xff0c;查询缓存的实现主要依赖于哈希表和内存管理。当查询字符串完全匹配且表未发生变动时&#xff0c;查询缓存可以显著提高查询性能…

HMI 的 UI 风格成就经典

HMI 的 UI 风格成就经典

力扣61. 旋转链表(java)

思路&#xff1a;用快慢指针找到最后链表k个需要移动的节点&#xff0c;然后中间断开节点&#xff0c;原尾节点连接原头节点&#xff0c;返回新的节点即可&#xff1b; 但因为k可能比节点数大&#xff0c;所以需要先统计节点个数&#xff0c;再取模&#xff0c;看看k到底需要移…

MySQL之高可用性(二)

高可用性 避免单点失效 找到并消除系统中的可能失效的单点&#xff0c;并结合切换到备用组件的机制&#xff0c;这是一种通过减少恢复时间(MTTR)来改善可用性的方法。如果你够聪明&#xff0c;有时候甚至能将实际的恢复时间降低至0&#xff0c;但总的来说这很困难。(即使一些…

16. Java的 Unsafe 类方法介绍

1. 前言 本节内容主要是对 Unsafe 类方法进行介绍&#xff0c;JDK jar 包中的 Unsafe 类提供了硬件级别的原子性操作&#xff0c;Unsafe 类中的方法都是 native 方法&#xff0c;它们使用 JNI 的方式访问本地 C&#xff0b;&#xff0b;实现库。 本节我们来了解一下 Unsafe 提…

目标检测技术概述与最新进展

目标检测技术概述与最新进展 一、引言 目标检测是计算机视觉中的一个重要研究方向&#xff0c;旨在在图像或视频中识别并定位目标对象。它在自动驾驶、智能安防、医疗影像分析等领域有着广泛的应用。本文将介绍目标检测的经典方法和最新进展&#xff0c;并给出相关论文的链接…

Python数据可视化书籍推荐:利用Python进行数据分析

《利用Python进行数据分析》 这本书几乎是数据分析入门必读书了 主要介绍了python 3个库numpy&#xff08;数组&#xff09;&#xff0c;pandas&#xff08;数据分析&#xff09;和matplotlib&#xff08;绘图&#xff09;的学习 阅读本书可以获得一份关于在Python下操作、处…

Rustdesk如何编译代码实现安装后不会显示主界面,不会在右下角出现托盘图标,作为后台服务运行

环境&#xff1a; Rustdesk1.1.9 问题描述&#xff1a; Rustdesk如何编译代码实现安装后不会显示主界面&#xff0c;不会在右下角出现托盘图标&#xff0c;作为后台服务运行 解决方案&#xff1a; 可以自定义进程名称和图标&#xff0c;不会显示主界面&#xff0c;不会在…

LLM大模型中LoRA是什么?面试经验回答汇总(2024.7月最新)

目录 1 什么是 LoRA&#xff1f; 2 LoRA 的思路是什么&#xff1f; 3 LoRA 的特点是什么&#xff1f; 4 简单描述一下 LoRA? 5 QLoRA 的思路是怎么样的&#xff1f; 6 QLoRA 的特点是什么&#xff1f; 7 AdaLoRA 的思路是怎么样的&#xff1f; 8 LoRA权重是否可以合入…

笛卡尔乘积算法js实现

全因子实验设计( DOE) &#xff1a;指所有因子的所有水平的所有组合都至少进行一次实验&#xff0c;可以估计所有的主效应和所有的各阶交互效应。 笛卡尔乘积&#xff1a;指在数学中&#xff0c;两个集合X和Y的笛卡尔积&#xff08;Cartesian product&#xff09;&#xff0c;…

视频监控汇聚和融合平台的特点、功能、接入方式、应用场景

目录 一、产品概述 二、主要特点 1、多协议支持 2、高度集成与兼容性 3、高性能与可扩展性 4、智能化分析 5、安全可靠 三、功能概述 1. 视频接入与汇聚 2. 视频存储与回放 3. 实时监控与预警 4. 信息共享与联动 5. 远程管理与控制 四、接入方式 1、直接接入 2…

大模型日报 2024-07-03

大模型日报 2024-07-03 大模型资讯 不到60秒生成3D「手办」&#xff0c;Meta 3D Gen引领3D内容创造新纪元 Meta最新发布的3D Gen技术&#xff0c;通过Meta 3D AssetGen和Meta 3D TextureGen两个阶段&#xff0c;实现了从文本到3D资产的快速生成。这一创新方法不仅大幅提高了3D内…

flask与vue实现通过websocket通信

在一些情况下&#xff0c;我们需要实现前后端之间的时刻监听&#xff0c;本文是一篇工具文档&#xff0c;用于解决前后端之间使用websocket交互。 一. Flask的相关配置 1. 下载相关依赖库 如果还没有配置flask的话&#xff0c;需要先安装flask,同时为解决跨域问题&#xff0…

创建线程的五种方式

一.继承Thread ,重写run class MyThread extends Thread{Overridepublic void run() {//这里的内容就是该线程要完成的工作while(true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeExceptio…

Qt开发 | qss简介与应用

文章目录 一、qss简介与应用二、QLineEdit qss介绍与使用三、QPushButton qss1.常用qss1.1 基本样式表1.2 背景图片1.3 图片在左文字在右 2.点击按钮弹出菜单以及右侧箭头样式设置3.鼠标悬浮按钮弹出对话框 四、QCheckBox qss妙用&#xff1a;实时打开关闭状态按钮五、QComboBo…

UE4_材质_使用彩色半透明阴影

学习笔记&#xff0c;不喜勿喷&#xff01;侵权立删&#xff0c;祝愿大美临沂生活越来越好&#xff01; 本教程将介绍如何配置虚幻引擎来投射彩色半透明阴影。 此功能在许多应用中都很有用&#xff0c;常见例子就是透过彩色玻璃窗的彩色光。 一、半透明阴影颜色 阴影在穿过半…

面试题--SpringCloud

SpringCloud SOA 和微服务的区别?(必会) 谈到 SOA 和微服务的区别, 那咱们先谈谈架构的演变 1. 集中式架构 项目功能简单, 一个项目只需一个应用, 将所有功能部署在一起, 这样的架构好处是减 少了部署节点和成本. 缺点: 代码耦合&#xff0c;开发维护困难 2. 垂直拆分架构 …

博途S7-1500PLC“虚轴“编程应用

1、CODESYS如何添加虚轴 如何添加虚轴(AM400PLC)-CSDN博客文章浏览阅读164次。EtherCAT运动控制总线启用的时候,选择EtherCAT总线任务周期。选择好后,选择点击添加。https://rxxw-control.blog.csdn.net/article/details/139898985虚轴是利用软件算法实现的运动控制轨迹规划…

HarmonyOS ArkUi 官网踩坑:单独隐藏导航条无效

环境&#xff1a; 手机&#xff1a;Mate 60 Next版本&#xff1a; NEXT.0.0.26 导航条介绍 导航条官网设计指南 setSpecificSystemBarEnabled 设置实际效果&#xff1a; navigationIndicator&#xff1a;隐藏导航条无效status&#xff1a;会把导航条和状态栏都隐藏 官方…