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中】
-
mystring.h:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义
-
test.cpp:存放main函数,以及测试代码
包含头文件
-
iostream:用于输入输出
-
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,就扩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;}
为什么扩容会导致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之后的迭代器会失效?
- 不确定是否删除到一定数据时,会不会减小容量,以适应size
此时和insert的一样,因为不能部分释放,所以会把原来的空间释放掉,申请新空间 - 不确定是否删除的是最后一个数据,如果是那么调用完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;};
}
相关文章:
vector模拟实现【C++】
文章目录 全部的实现代码放在了文章末尾准备工作包含头文件定义命名空间和类类的成员变量 迭代器迭代器获取函数 构造函数默认构造使用n个值构造迭代器区间构造解决迭代器区间构造和用n个值构造的冲突拷贝构造 析构函数swap【交换函数】赋值运算符重载emptysize和capacityopera…...
《每天5分钟用Flask搭建一个管理系统》第11章:测试与部署
第11章:测试与部署 11.1 测试的重要性 测试是确保应用质量和可靠性的关键步骤。它帮助开发者发现和修复错误,验证功能按预期工作。 11.2 Flask测试客户端的使用 Flask提供了一个测试客户端,可以在开发过程中模拟请求并测试应用的响应。 …...
Landsat数据从Collection1更改为Collection2
目录 问题解决 问题 需要注意!您使用的是废弃的陆地卫星数据集。为确保功能持续,请在2024年7月1日前更新。 在使用一些以前的代码时会遇到报错,因为代码里面用的是老的数据集 解决 对于地表反射率SR,需要在name中,将C01换为C02&…...
《每天5分钟用Flask搭建一个管理系统》第12章:安全性
第12章:安全性 12.1 Web应用的安全威胁 Web应用面临的安全威胁包括但不限于跨站脚本攻击(XSS)、SQL注入、跨站请求伪造(CSRF)、不安全的直接对象引用(IDOR)等。 12.2 Flask-Talisman扩展的使…...
Unity之创建与导出PDF
内容将会持续更新,有错误的地方欢迎指正,谢谢! Unity之创建与导出PDF TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 —— 不断努力,不断进步,不断探索 TechX —— 心探索、心进取! 助力快速…...
【Android面试八股文】优化View层次过深问题,选择哪个布局比较好?
优化深层次View层次结构的问题,选择合适的布局方式是至关重要的。以下是几点建议: 使用ConstraintLayout:ConstraintLayout是Android开发中推荐的布局,能够有效减少嵌套,提高布局性能。相比RelativeLayout,…...
什么是带有 API 网关的代理?
带有 API 网关的代理服务显著提升了用户体验和性能。特别是对于那些使用需要频繁创建和轮换代理的工具的用户来说,使用 API 可以节省大量时间并提高效率。 了解 API API,即应用程序编程接口,是服务提供商和用户之间的连接网关。通过 API 连接…...
sql拉链表
1、定义:维护历史状态以及最新数据的一种表 2、使用场景 1、有一些表的数据量很大,比如一张用户表,大约1亿条记录,50个字段,这种表 2.表中的部分字段会被update更新操作,如用户联系方式,产品的…...
STM32CubeMX实现矩阵按键(HAL库实现)
功能描述: 实现矩阵按键验证,将矩阵按键的按键值,通过串口显示,便于后面使用。 实物图 原理图: 编程原理: 原理很简单,就是通过循环设置引脚为低电平,另外引脚扫描读取电平值&…...
mmdetection3D指定版本安装指南
1. 下载指定版本号 选择指定版本号下载mmdetection3d的源码,如这里选择的是0.17.2版本 git clone https://github.com/open-mmlab/mmdetection3d.git -b v0.17.22. 安装 cd mmdetection3d安装依赖库 pip install -r requirment.txt编译安装 pip install -v e .…...
SQLMap工具详解与SQL注入防范
SQLMap工具详解与SQL注入防范 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨SQLMap工具的详细使用方法以及如何防范SQL注入攻击。 SQL注入简介 SQL注入是一种常见的安全漏洞&am…...
如何在Java中实现自定义数据结构:从头开始
如何在Java中实现自定义数据结构:从头开始 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨如何在Java中实现自定义数据结构ÿ…...
【机器学习】在【Pycharm】中的应用:【线性回归模型】进行【房价预测】
专栏:机器学习笔记 pycharm专业版免费激活教程见资源,私信我给你发 python相关库的安装:pandas,numpy,matplotlib,statsmodels 1. 引言 线性回归(Linear Regression)是一种常见的统计方法和机器学习算法&a…...
如何在 Linux 中后台运行进程?
一、后台进程 在后台运行进程是 Linux 系统中的常见要求。在后台运行进程允许您在进程独立运行时继续使用终端或执行其他命令。这对于长时间运行的任务或当您想要同时执行多个命令时特别有用。 在深入研究各种方法之前,让我们先了解一下什么是后台进程。在 Linux 中…...
软考-软件设计师
软考 软考科目 软考分为初级、中级、高级,初级含金量相对不够,高级考试有难度,所以大多数人都在考中级,中级也分很多科目,我考的是软件设计师(已经通过)。 合格标准 考试分为上午题和下午题…...
UOS系统中JavaFx笔锋功能
关于笔锋功能,网上找了很久,包括Java平台客户端,Android端,相关代码资料比较少,找了很多经过测试效果都差强人意,自己也搓不出来,在UOS平台上JavaFX也获取不到压力值,只能用速度的变…...
后端加前端Echarts画图示例全流程(折线图,饼图,柱状图)
本文将带领读者通过一个完整的Echarts画图示例项目,演示如何结合后端技术(使用Spring Boot框架)和前端技术(使用Vue.js或React框架)来实现数据可视化。我们将实现折线图、饼图和柱状图三种常见的数据展示方式ÿ…...
ValidateAntiForgeryToken、AntiForgeryToken 防止CSRF(跨网站请求伪造)
用途:防止CSRF(跨网站请求伪造)。 用法:在View->Form表单中: aspx:<%:Html.AntiForgeryToken()%> razor:Html.AntiForgeryToken() 在Controller->Action动作上:[ValidateAntiForge…...
《昇思25天学习打卡营第5天 | mindspore 网络构建 Cell 常见用法》
1. 背景: 使用 mindspore 学习神经网络,打卡第五天; 2. 训练的内容: 使用 mindspore 的 nn.Cell 构建常见的网络使用方法; 3. 常见的用法小节: 支持一系列常用的 nn 的操作 3.1 nn.Cell 网络构建&…...
SQLServer:从数据类型 varchar 转换为 numeric 时出错。
1.工作要求 计算某两个经纬度距离 2.遇到问题 从数据类型 varchar 转换为 numeric 时出错。 3.解决问题 项目版本较老,使用SQLServer 2012 计算距离需执行视图,如下: SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON GO ALTER view vi_ord…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
