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…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
 
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
 
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
 
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
 
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
 
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
 
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
