C++:vector增删查改模拟实现

C++:vector增删查改模拟实现

  • 前言
  • 一、迭代器
    • 1.1 非const迭代器:begin()、end()
    • 1.2 const迭代器:begin()、end()
  • 二、构造函数、拷贝构造函数、赋值重载、析构函数模拟实现
    • 2.1 构造函数
      • 2.1.1 无参构造
      • 2.1.2 迭代器区间构造
      • 2.1.3 n个值构造
    • 2.2 拷贝构造
    • 2.3 赋值重载
    • 3 析构函数
  • 三、容量相关:capacity()、size()、reserve()、resize()
    • 3.1 capacity()
    • 3.2 size()
    • 3.3 reserve()
    • 3.4 resize()
  • 四、operator[ ]重载
  • 五、元素相关:insert、erase、push_back、pop_back
    • 5.1 insert()
    • 5.2 erase()
      • 5.2.1 erase迭代器失效
    • 5.3 push_bach()
    • 5.4 pop_back()
  • 六、所有代码


前言

提前在这说明下,vector增删查改模拟实现的成员变量博主采用SGI版本。下面给出其库中成员变量是哪些,后续的模拟实现都基于此。

在这里插入图片描述
我们发现库中定义了3个T*的变量。同时3个成员变量的意义如下:

在这里插入图片描述

一、迭代器

1.1 非const迭代器:begin()、end()

typedef T* iterator;iterator begin()
{return _start;
}iterator end()
{return _finish;
}

1.2 const迭代器:begin()、end()

typedef const T* const_iterator;const_iterator begin()const
{return _start;
}const_iterator end()const
{return _finish;
}

二、构造函数、拷贝构造函数、赋值重载、析构函数模拟实现

2.1 构造函数

我们先来看看vector库中的构造类型如下:
在这里插入图片描述
我们知道有三种构造方式,下面给出各种的实现方式。

2.1.1 无参构造

vector():_start(nullptr),_finish(nullptr), _endofstorage{}

2.1.2 迭代器区间构造

template<class InputIterator>//使用模板是为了,当数据类型匹配时就可以使用
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);first++;}
}

2.1.3 n个值构造

vector(size_t  n, const T& value = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(value);}
}//防止定义vector<int>这种类型走迭代区间的构造函数,我们在多实现一个以下类型函数
//当使用vector<int>类型的去构造时,此时调用的构造函数两个参数都是int。所有他会走最匹配的函数,即迭代器区间构造生成的构造函数,程序会出错。
//而下面给出了现成的最匹配构造函数,编译器调用时就不会走模板。
vector(int n, const T& value = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(value);}
}

2.2 拷贝构造

拷贝构造我们先调用开好一块空间,在依次插入数据即可。

//vector(const vector& x) //库中实现模式, 直接使用类名。但C++,类型不是类名。
//这里各位读者了解下这里直接类名做类型也正确即可,但不建议各位这样做。
vector(const vector<T>& v)
{reserve(v.capacity());//后面会给出实现for (auto& e : v){push_back(e);}
}

2.3 赋值重载

赋值重载这里我们不要传引用,而是直接传参即可。编译器调用拷贝构造生成形参后,在调用swap()函数依次交换形参和this即可。

//赋值重载
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v_endofstorage);
}vector<T>& operator= (vector<T> tmp)
{swap(tmp);return *this;
}		

3 析构函数

~vector()
{delete[] _start;_start = _finish = _endofstorage = nullptr;
}

三、容量相关:capacity()、size()、reserve()、resize()

3.1 capacity()

size_t capacity()const
{return _endofstorage - _start;
}

3.2 size()

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

3.3 reserve()

由于stl中我们一般不缩容,所以先判断reserve的空间大小是否比当前空间容量大。
如果reserve的空间更大,所以我们需要先开好目标大小的空间,在将原数据拷贝过去,最后析构原来空间即可。
但下面这两种实现方式对吗?

第一种:

void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];if (_start)//如果原来空间有数据,拷贝到新空间{memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}//更新_start、_finish、_endofstorage。指向新空间中相应位置_start = tmp;_finish = _start + size();_endofstorage = _start + n;}
}

先说结论:上诉这段代码是错的。
在我们调试后会发现_finish的值没有更新。(这里大家自行验证下接口)

原因:(win11画图一直很模糊,博主也很无奈,各位将就看吧)
在这里插入图片描述


第二种:
为了解决上诉问题,我们可以先记录_finish和_start的偏移量,用来代替size()函数。
所以初学者很容易写出以下代码:

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();//记录_finish 和 _start 的偏移量T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}_start = tmp;_finish = _start + sz;//不能用size()代替sz,否则会导致迭代器失效_endofstorage = _start + n;}
}

那这是否正确呢?答案是否定的。
我们来看看下面这种场景:
在这里插入图片描述


实际上对于这种情况,可以自己循环依次赋值即可。内置类型直接拷贝数据;内置类型调用赋值重载,是一种深拷贝。

最终代码如下:

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();//记录_finish 和 _start 的偏移量T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * sz);for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;//不能用size()代替sz,否则会导致迭代器失效_endofstorage = _start + n;}
}

3.4 resize()

resize逻辑还是很简单的。
首先判断resize()的目标大小n和有效数据个数size()谁大。如果有效个数size()更大,只需更改_finish即可;否则要先进行扩容(reserve会将原有数据拷贝到新空间),然后从_finish开始向扩充的空间插入新的值。

代码如下:

//const会延长匿名对象的生命周期, 匿名对象具有常性
//模板出来后,对类进行了升级,内置类型也有构造函数
//void resize(size_t n, T val = T())
void resize(size_t n, const T& val = T())
{if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;_finish++;}}
}

四、operator[ ]重载

T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos)const
{assert(pos < _finish);return _start[pos];
}

五、元素相关:insert、erase、push_back、pop_back

5.1 insert()

任意位置插入数据,首先需判断是否需要扩容。然后将插入位置pos开始往后的数据向后移动,最后将新数据插入到pos处即可。
tips:

  • 如果发生扩容,需要先记录pos和_start之间的偏移量。在将pos位置跟新,指向新空间中对应位置。否则会导致迭代器失效
void insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstroage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}//挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = x;_finish++;
}

5.2 erase()

任意位置删除数据,只需要从pos+1开始,将后续数据全部依次向前移动覆盖,最后更新_finish即可。

iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;
}

5.2.1 erase迭代器失效

void testvector4()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(4);v.push_back(5);v.push_back(6);auto it = v.begin();while (it < v.end()){if (*it % 2 == 0){v.erase(it);}it++;}for (auto e : v){cout << e << " ";}cout << endl;
}

上述代码本意是将偶数全部删除,结果本该是1 、5。但结果却是:
在这里插入图片描述
为什么呢?
这是因为我们删除元素后,后续数据会补上空缺。所以当使用erase后,迭代器会失效。(上述结果是g++的实现机制,在vs2019下上述代码会直接报错。原因在于vs2019对erase后的空间做强制检查,不允许访问)。为此stl库给出的解决方案是接受删除位置的下一个元素的返回值。(这也是为什么整个模拟实现中只有erase函数具有返回值),并接收返回值。

正确删除偶数方法:

void testvector4(){//std::vector<int> v;vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(4);v.push_back(5);v.push_back(6);auto it = v.begin();//迭代器失效/*while (it < v.end()){if (*it % 2 == 0){v.erase(it);}it++;}*/while (it < v.end()){if (*it % 2 == 0){it = v.erase(it);}else{it++;}}for (auto e : v){cout << e << " ";}cout << endl;}

5.3 push_bach()

头插复用insert函数即可。

void push_back(const T& x)
{//if (_finish == _endofstroage)//{//	reserve(capacity() == 0 ? 4 : capacity() * 2);//}插入数据//*_finish = x;//_finish++;insert(_finish, x);
}

5.4 pop_back()

复用erase,尾删

void pop_back()
{erase(--end());
}

六、所有代码

vector增删查改模拟实现gitee链接

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

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

相关文章

vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)

目录 一、什么是Vue路由导航守卫&#xff1f; 二、全局守卫 1、beforeEach 下面是一个beforeEach的示例代码&#xff1a; 2、beforeResolve 下面是一个beforeResolve的示例代码&#xff1a; 3、afterEach 下面是一个afterEach的示例代码&#xff1a; 三、路由独享守卫…

044:vue中引用json数据的方法

第044个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

多相Buck的工作原理

什么是多相Buck电源&#xff1f; 多相电源控制器是一种通过同时控制多个电源相位的设备&#xff0c;以提供稳定的电力供应。相位是指电源中的电流和电压波形。多相控制器的设计旨在最大程度地减小电力转换系统的纹波&#xff0c;并提高整体能效。它通常包含一系列的功率级联&a…

结构化布线系统

满足下列需求&#xff1a; 1.标准化&#xff1a;国际、国家标准。 2.实用性&#xff1a;针对实际应用的需要和特点来建设系统。 3.先进性&#xff1a;采用国际最新技术。5-10年内技术不落后。 4.开放性&#xff1a;整个系统的开放性。 5.结构化、层次化&#xff1a;易于管理和维…

Matplotlib数据可视化

绘图基础语法 &#xff11; 创建画布并且创建子图 首先创建一个空白的画布&#xff0c;并且可以将画布分为几个部分&#xff0c;这样就可以在同一附图上绘制多个图像。 plt.figure 创建一个空白画布&#xff0c;可以指定画布大小、像素 figure.add_subplot 创建并且选中子…

【web安全】文件读取与下载漏洞

前言 菜某整理仅供学习&#xff0c;有误请赐教。 概念 个人理解&#xff1a;就是我们下载一个文件会传入一个参数&#xff0c;但是我们可以修改参数&#xff0c;让他下载其他的文件。因为是下载文件&#xff0c;所以我们可以看到文件里面的源码&#xff0c;内容。 文件读取…

swiftUi——颜色

在SwiftUI中&#xff0c;您可以使用Color结构来表示颜色。Color可以直接使用预定义的颜色&#xff0c;例如.red、.blue、.green等&#xff0c;也可以使用自定义的RGB值、十六进制颜色代码或者系统提供的颜色。 1. 预定义颜色 Text("预定义颜色").foregroundColor(.…

Swing程序设计(9)复选框,下拉框

文章目录 前言一、复选框二、下拉框总结 前言 该篇文章简单介绍了Java中Swing组件里的复选框组件、列表框组件、下拉框组件&#xff0c;这些在系统中都是常用的组件。 一、复选框 复选框&#xff08;JCheckBox&#xff09;在Swing组件中的使用也非常广泛&#xff0c;一个方形方…

hadoop安装与配置-shell脚本一键安装配置(集群版)

文章目录 前言一、安装准备1. 搭建集群 二、使用shell脚本一键安装1. 复制脚本2. 增加执行权限3. 分发脚本4. 执行脚本5. 加载用户环境变量 三、启动与停止1. 启动/停止hadoop集群(1) 复制hadoop集群启动脚本(2) 增加执行权限(3) 启动hadoop集群(4) 停止hadoop集群(5) 重启hado…

智慧社区前景无限,科技引领未来发展

社区是城镇化发展的标志&#xff0c;作为人类现代社会的生活的基本圈子&#xff0c;是人类生活离不开的地方&#xff0c;社区人口密度大、车辆多&#xff0c;管理无序&#xff0c;社区的膨胀式发展多多少少带来一定的管理上的缺失。社区作为智慧城市建设的重要一环&#xff0c;…

编译基于LIO-SAM的liorf“Large velocity, reset IMU-preintegration!“

使用LIO-SAM修改的代码liorf&#xff08;因自己使用的IMU传感器是 6-axis ouster&#xff09;&#xff1a; LIO-SAM代码连接&#xff1a; https://github.com/TixiaoShan/LIO-SAM liorf代码连接&#xff1a; https://github.com/YJZLuckyBoy/liorf 编译运行出现错误&#…

eve-ng山石网科HillStone镜像部署

HillStone 部署 author&#xff1a;leadlife data&#xff1a;2023/12/4 mains&#xff1a;EVE-ng HillStone 镜像部署 - use hillstone-sg6000 default&#xff1a;hillstone/hillstone 传输 scp hillstone-sg6000.zip root192.168.3.130:/opt/unetlab/addons/qemu/部署 cd …

echarts绘制一个环形图

其他echarts&#xff1a; echarts绘制一个柱状图&#xff0c;柱状折线图 echarts绘制一个饼图 echarts绘制一个环形图2 效果图&#xff1a; 代码&#xff1a; <template><div class"wrapper"><!-- 环形图 --><div ref"doughnutChart…

C++STL的string(超详解)

文章目录 前言C语言的字符串 stringstring类的常用接口string类的常见构造string (const string& str);string (const string& str, size_t pos, size_t len npos); capacitysize和lengthreserveresizeresize可以删除数据 modify尾插插入字符插入字符串 inserterasere…

如何将腾讯混元大模型AI接入自己的项目里(中国版本ChatGPT)

如何将腾讯混元大模型AI接入自己的项目里 一、腾讯混元大模型API二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、腾讯混元大模型API 基于腾讯混元大模型AI的智能文本对话AI机器人…

TypeScript 的修饰符(modifier)和装饰器(decorator)

装饰器是一种特殊类型的声明&#xff0c;它能够被附加到类声明....上。 装饰器使用 expression这种形式

使用消息队列遇到的问题—kafka

目录 1 分区2 消费者3 Kafka 如何保证消息的消费顺序&#xff1f;3.1 方案一3.2 方案二 4 消息积压 在项目中使用kafka作为消息队列&#xff0c;核心工作是创建生产者—包装数据&#xff1b;创建消费者----包装数据。 欠缺一些思考&#xff0c;特此梳理项目中使用kafka遇到的一…

浅析以太网接口及串口转以太网技术

浅析以太网接口 以太网相关接口主要包括&#xff1a;MII/RMII/SMII以及GMII/RGMII/SGMII接口。 一、MII接口 MII&#xff08;Media Independent Interface&#xff09;介质无关接口或称为媒体独立接口&#xff0c;它是IEEE-802.3定义的以太网行业标准。它包括一个数据接口和…

Python 小红书评论区采集 小红薯xhs精准用户获客

成品图 评论接口https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?note_id笔记id&cursor光标 初次使用cursor为空,该接口为GET&#xff0c;需要x-s,x-t签名验证 子评论接口https://edith.xiaohongshu.com/api/sns/web/v2/comment/sub/page?note_id%s&r…

python爬取robomaster论坛文章数据,携带登录信息

一. 内容简介 python爬取robomaster论坛文章数据。 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3代码 三.主要流程 3.1 接口分析&#xff0c;以及网页结构分析 # 这是文章链接,其实id就是文章的id # https://bbs.robomaster.com/forum.php?modview…