vector的模拟实现

什么是vector

vector是一个封装了动态大小数组的顺序容器跟任意其它类型容器一样,它能够存放各种类型的对象。

模拟实现

实现前的准备

在实现vector之前,为了和库里的区分开需要将实现的vector放在一个自定义的命名空间里。而且vector需要实现成模版的方式,所以需要我们传模版参数,模版参数也就是顺序容器所存的数据类型。而且根据库里的vector,有三个关键的成员变量,分别指向顺序容器的起始位置,数据容量位置,空间容量位置。

namespace cr
{template<class T>class vector{public:private:iterator _start;iterator _finish;iterator _endofstorage;};
}

迭代器的相关实现 

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

这里要提醒的就是,_finish指向的是最后一个有效数据的下一个位置,_endofstorage指向的是对象的最大容量位置处的下一个位置。

下标引用operator[]

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

这里需要注意的是:不要忘记断言

容器大小

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

为了保证const对象调用该函数时也可以正常通过,所以对该函数进行const修饰。

析构函数

~vector()
{delete[] _start;
}

扩容reserve

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (sz != 0){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;//勿忘}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

这里进行扩容时一定要注意判断空间大小关系,尤其是要注意三个指针的指向问题,在此之前记录好原空间中有效数据的个数,因为执行new操作都是异地扩容,所以_start存的地址肯定发生改变,如果此时通过_start求得的_finish就一定会出错,所以需要提前记录好位置关系但是该函数不一定正确

尾插数据push_back 

void push_back(const T& x) 
{if (_finish == _endofstorage)reserve(size() == 0 ? 4 : capacity() * 2);*_finish = x;_finish++;
}

push_back需要注意的地方就是当空间大小为0时需要扩的大小,以及_finish++。这里来验证一下自内置类型的数据结果:

 再看看string类的数据结果:引发了异常: 读取访问权限冲突。

 其实这里的问题哦就是reserve的实现出了问题:

 当空间不够时会发生扩容,就会调用memcpy函数,memcpy是库里的,而且我们知道memcpy内部就是通过值拷贝将_start解引用进行拷贝给tmp(_start是一个string类的指针),所以此时拷贝好的数据相当于是浅拷贝,指向如下:

 然后调用析构函数,将_start空间内存释放,但是_start内部数据是string类,所以会先调用string的析构函数,释放string得空间,所以此时就是报错所在:tmp内的string同样也销毁了。

修改reverse函数

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (sz != 0){//memcpy(tmp, _start, sizeof(T) * sz);for (int i = 0; i < sz; i++){tmp[i] = _start[i];//如果T是string,调用std::string的赋值重载}delete[] _start;//勿忘}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

这种拷贝的方式就有效避免了浅拷贝的问题,如果模版参数T是string类的话,tmp[i]的类型就是string,所以此时的赋值就会调用string的赋值重载operator=()进行深拷贝,所以就不会出问题。

改变有效数据个数resize

void resize(size_t n, const T& x = T())//匿名对象
{if (n > size()){reserve(n);while (_finish != _endofstorage){*_finish = x;_finish++;}}else{_finish = _start + n;}}

这里要注意的就是T x给的缺省值T(),T()其实就是调用匿名对象的构造函数,然后在调用拷贝构造给x对象,而编译器会优化成直接的构造。匿名对象具有常性(不能&(同一块空间)不加const),出了该行就自动析构,const引用可以延长匿名对象的生命周期。但是如果是内置类型,这样写也是没问题的,其实内置类型也是可以看做有构造函数的。就如下测试:

 构造函数

vector(size_t n=0,const T& val=T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n);while (n--){push_back(val);}
}

拷贝构造

vector(const vector<T>& v):_start(new T[v.capacity()]),_finish(_start),_endofstorage(_start+v.capacity())
{for (auto ch : v){*_finish = ch;//如果T是string,调用std::string的赋值重载_finish++;}
}

通过迭代器区间初始化的构造函数

//在一个类模版里面还可以继续写模版函数
template<class InputIterator>
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{while (first != last){push_back(*first);first++;}
}

这里迭代器区间的构造函数是需要实现成模版类型的,因为库里面的STL说明不同的容器可以通过迭代器区间进行初始化,只不过有点不兼容的可以进行强制类型转换:

但是当执行到构造函数时:

cr::vector<int> vv(3, 1);

会报错非法的间接寻址  

原因:当我们调用构造函数时,会优先找最匹配的构造函数进行调用,所以此时以上的三种构造函数就会调用迭代器区间构造函数,而不是第一种


vector(size_t n=0,const T& val=T())//一般构造
vector(InputIterator first, InputIterator last)迭代器区间构造

调用第一种时会发生int类型到size_t类型的转换,而第三种构造函数会直接调用,因为第三种构造有模版参数,而对于两个参数(3,1)都是int类型,所以此时模版形参InputIterator就是替换成int型,所以此时第三种更匹配。

解决方法:

  1. 将第一个构造函数的形参改变成int类型
  2. 再生成一个构造函数形成重载
    vector(int n, const T& val = T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
    {reserve(n);while (n--){push_back(val);}
    }

赋值重载operator= 

vector<T>& operator=(vector<T> v)
{swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);return *this;
}

这里是要一个返回值的,主要是避免连等的情况:a=b=c;

在某位置插入数据insert

void insert(iterator pos, T x)
{assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage);{reserve(_start==nullptr?4:capacity() * 2);}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;
}

以上代码其实是有问题的,如下测试

调试时你会发现是在insert内部出了问题 而且会在下面代码不断循环

while (end >= pos)
{*(end + 1) = *end;end--;
}

所以问题极可能出现在上面的代码段: 

此时pos依旧是nullptr,就相当于pos指向的依旧是原空间的地址,插入数据扩容时,空间都发生了变化,所以再拿原空间的指针和新空间的指针比较就不妥

修改代码:其实就是记录pos位置和_start之间的差距,好在扩容到新空间时,将差距补上

void insert(iterator pos, T x)//insert使用之后迭代器失效最好不要使用
{assert(pos >= _start && pos <= _finish);//不要忘记断言if (_finish == _endofstorage);{size_t len = pos - _start;reserve(_start==nullptr?4:capacity() * 2);//异地扩容改变了_start,可能导致pos成为野指针pos = _start + len;}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;
}

 虽然修改了代码,编译起来也没问题,但是如果你执行下列操作还是跑不通:

 此时是断言处出问题了,其实这种情况属于迭代器失效因为你插入数据时发生了扩容了,所以同上面一样,it指针指向的是原空间的首个位置,所以就会错误

解决方法:每次插入数据是重新调用迭代器v.begin()或v.end()来获得迭代器的位置。

在某位置删除数据erase

void erase(iterator pos)
{assert(pos < _finish&& pos >= _start);iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;end++;}_finish--;
}

这里和inssert是不同的,erase不会发生扩容,所以也不存在迭代器失效,但是这erase要注意的就是,每次删除一个数据时位置会发生挪动,所以一般在刷题时一定要注意这个小细节。

而VS下的erase是并不是这样实现的,VS下的erase也只能使用一次,用完了该指针也就是失效了。尽管进行it++还是--来改变it再使用也是不可以的。

 所以可以借鉴insert的用法:

 但是VS下的erase其实是有返回值的,

其实返回的就是你删除的那个数据的下一个位置,但是位置会发生挪动,所以,返回的也就是pos位置 


如有解释不当,欢迎大家留言!

 

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

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

相关文章

论文阅读 - Neutral bots probe political bias on social media

论文链接&#xff1a;Neutral bots probe political bias on social media | EndNote Click 试图遏制滥用行为和错误信息的社交媒体平台被指责存在政治偏见。我们部署中立的社交机器人&#xff0c;它们开始关注 Twitter 上的不同新闻源&#xff0c;并跟踪它们以探究平台机制与用…

超导热催生meme,换汤不换药的投机轮回

文/章鱼哥 出品/陀螺财经 币圈对炒作meme概念的热情从未消亡过。 随着一种名为LK-99的物质被发现&#xff0c;围绕超导的兴奋不仅激发了科学界&#xff0c;加密货币相关概念也与之沸腾。不出所料&#xff0c;与此前围绕元宇宙、AI大肆炒作一样&#xff0c;许多meme代币已经出现…

关于MySQL中的binlog

介绍 undo log 和 redo log是由Inno DB存储引擎生成的。 在MySQL服务器架构中&#xff0c;分为三层&#xff1a;连接层、服务层&#xff08;server层&#xff09;、执行层&#xff08;存储引擎层&#xff09; bin log 是 binary log的缩写&#xff0c;即二进制日志。 MySQL…

android开发之Android 自定义滑动解锁View

自定义滑动解锁View 需求如下&#xff1a; 近期需要做一个类似屏幕滑动解锁的功能&#xff0c;右划开始&#xff0c;左划暂停。 需求效果图如下 实现效果展示 自定义view如下 /** Desc 自定义滑动解锁View Author ZY Mail sunnyfor98gmail.com Date 2021/5/17 11:52 *…

数据结构——线性表

文章目录 线性表的定义和基本操作顺序表线性表的链式表示 线性表的定义和基本操作 线性表是具有相同数据类型的(n≥0)个数据元素的有限序列&#xff0c;其中n为表长&#xff0c;当n0时线性表是一个空表。若用L命名线性表&#xff0c;则其中一般表示为&#xff1a;L(a1,a2,a3, …

CCLINK转MODBUS-TCP网关cclink通讯接线图 终端电阻

大家好&#xff0c;今天我们要聊的是生产管理系统中的CCLINK和MODBUS-TCP协议&#xff0c;它们的不同使得数据互通比较困难&#xff0c;但捷米JM-CCLK-TCP网关的出现改变了这一切。 1捷米JM-CCLK-TCP是一款自主研发的CCLINK从站功能的通讯网关&#xff0c;它的主要功能是将各种…

D455+VINS-Fusion+surfelmapping 稠密建图(三)

继续&#xff0c;由surfelmapping建立的点云生成octomap八叉树栅格地图 一、安装OctomapServer 建图包 安装插件 sudo apt-get install ros-melodic-octomap-ros sudo apt-get install ros-melodic-octomap-msgs sudo apt-get install ros-melodic-octomap-server sudo apt-…

cubemx hal stm32 舵机 可减速 任意位置停止 驱动代码

CubeMX配置 对于 STM32 F407VE 这里的84是来自APB1那路2倍频得到&#xff1a; 代码部分 两个舵机都是180度的 servo.c #include "servo.h" #include "tim.h" #include "stdio.h"__IO uint32_t g_SteerUWT[2] {0}; uint16_t g_SteerDeg[…

青大数据结构【2014】

一、单选 二、简答 为了解决顺序队列的假溢出问题&#xff0c;提出了循环队列&#xff0c;即把存储队列的表从逻辑上看成一个环 判别队列空和满有三种方法&#xff1a; 1&#xff09;采用计数器判别&#xff0c;空时&#xff0c;计数器为0&#xff1b;满时&#xff0c;计数器…

【设计模式——学习笔记】23种设计模式——中介者模式Mediator(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入案例一普通实现中介者模式 案例二 介绍基础介绍登场角色尚硅谷 《图解设计模式》 案例实现案例一&#xff1a;智能家庭类图实现 案例二&#xff1a;登录页面逻辑实现说明类图实现 总结文章说明 案例引入 案例一 普通实现 在租房过程中&#xff0c;客户可能…

css 实现 html 元素内文字水平垂直居中的N种方法

上一篇博文写了div 中元素居中的N种常用方法&#xff0c;那么单个html元素&#xff1a;div&#xff08;块级元素代表&#xff09;&#xff0c;span&#xff08;行内元素代表&#xff09;中的文字如何水平垂直都居中呢&#xff1f;实现方法如下&#xff1a; 本文例子使用的 html…

机器学习---对数几率回归

1. 逻辑回归 逻辑回归&#xff08;Logistic Regression&#xff09;的模型是一个非线性模型&#xff0c; sigmoid函数&#xff0c;又称逻辑回归函数。但是它本质上又是一个线性回归模型&#xff0c;因为除去sigmoid映射函 数关系&#xff0c;其他的步骤&#xff0c;算法都是…

阿里云预装LAMP应用导致MySQL不显示访问密码如何解决

&#x1f600;前言 本篇博文是关于阿里云云服务器ECS部署MySQL过程中出现的一下坑&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家…

SUB-1G SOC芯片DP4306F 32 位 ARM Cortex-M0+内核替代CMT2380F32

DP4306F是一款高性能低功耗的单片集成收发机&#xff0c;集成MO核MCU&#xff0c;工作频率可覆盖200MHiz^ 1000MHz。 支持230/408/433/470/868/915频段。该芯片集成了射频接收器、射频发射器、频率综合器、GFSK调制器、GFSK解调器等功能模块。通过SPI接口可以对输出功率、频道选…

gitlab-Runner搭建

root wget https://packages.gitlab.com/runner/gitlab-runner/packages/fedora/29/gitlab-runner-12.6.0-1.x86_64.rpm/download.rpm rpm -ivh download.rpm ---- 安装 rpm -Uvh download.rpm -----更新升级 然后运行&#xff1a; gitlab-runner register --url https://git…

38 | 浦发银行股票分析案例

本文将通过一个浦发银行股票分析案例,探讨如何从多个维度对股票进行分析,包括基本面、技术面和市场环境等因素。我们将深入挖掘浦发银行的财务数据、业务模式以及市场定位,以了解其内在价值和潜在风险。同时,我们还将考察技术面的指标,如价格走势、均线形态等,以揭示市场…

ThinkPHP8命名规范-ThinkPHP8知识详解

本文主要讲解thinkphp8的命名规范&#xff0c;主要包括&#xff1a;遵循PHP自身的PSR-2命名规范和PSR-4自动加载规范、目录和文件命名规范、函数和类、属性命名规范、常量和配置命名规范、数据表和字段命名规范、不能使用PHP保留字。 在使用thinkphp8开发项目之前&#xff0c;…

线程转换状态,傻傻分不清等待和阻塞吗?你还在暴力的停止线程吗?

线程切换 线程创建之后&#xff0c;调用start()方法开始运行。当线程执行wait()方法之后&#xff0c;线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态&#xff0c;而超时等待状态相当于在等待状态的基础上增加了超时限制&#xff0c;也就是超…

腾讯云服务器竞价实例是什么?适用于什么行业?有啥优惠?

腾讯云服务器CVM计费模式分为包年包月、按量计费和竞价实例&#xff0c;什么是竞价实例&#xff1f;竞价实例和按量付费相类似&#xff0c;优势是价格更划算&#xff0c;缺点是云服务器实例有被自动释放风险&#xff0c;腾讯云服务器网来详细说下什么是竞价实例&#xff1f;以及…

GUI、多线程编程、网络编程简介

GUI、多线程编程、网络编程简介 文章目录 GUI简介什么是GUIGUI有什么用使用方法 多线程编程什么是多线程编程多线程编程有什么用提高程序的响应能力提高程序的性能实现异步编程并发数据访问和共享资源实现复杂的算法和任务分解 进行多线程编程的步骤 网络编程什么是网络编程网络…