【无标题】C++ STL -->模拟实现vector

这篇文章将模拟实现vector类的常用函数

vector类的函数接口

namespace ding
{template<class T>class vectot{public:typedef T* iterator;typedef const T* const_iterator;//Member functionsvector();                                      vector(size_t n, const T& val);             template<class InputIterator>vector(InputIterator first, InputIterator last);   vector(const vector<T>& v);                       vector<T>& operator=(const vector<T>& v);           ~vector();  //Iterators:iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//Capacity:size_t size()const;size_t capacity()const;void reserve(size_t n);void resize(size_t n, const T& val = T());bool empty()const;//Element access:T& operator[](size_t i);const T& operator[](size_t i)const;//Modifiers:void push_back(const T& x);void pop_back();iterator insert(iterator pos, const T& x);iterator erase(iterator pos);void swap(vector<T>& v);private:iterator _start;iterator _finish;iterator _end_of_storage;};
}

使用三个指针来维护vector容器,让_start指向容器的头,_finish指向有效数据的尾,
_end_of_storage指向容器的尾。
image.png

Member functions

成员函数

构造函数

无参构造函数

构造一个空的容器,让三个成员变量都置为空指针即可

vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{}
带参构造函数

用n个val去构造初始化vector,首先开够n个元素的空间

image.png
然后让_finish从头到尾遍历进行赋值即可
image.png

vector(size_t n, const T& val):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{//开空间_start = new T[n];_finish = _start;_end_of_storage = _start + n;//赋值while (_finish != _end_of_storage){*_finish = val;++_finish;}
}
迭代器构造
//模板函数
template<class InputIterator>
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{//开空间int n = last - first;_start = new T[n];_finish = _start;_end_of_storage = _start + n;//赋值while (first != last){*_finish = *first;++first;++_finish;}
}

注意:

  • 这里迭代器构造是模板函数,如果使用这个代码vector<int> v1(10, 1);实例化对象v1。
  • 本意是去调用vector(size_t n, const T& val)这个构造函数去实例化对象v1。
  • 但是编译器的匹配原则,实参10是字面量,默认是int类型,而带参构造函数是size_t类型,则会调迭代器构造函数。就会导致对int类型进行解引用操作,导致程序出错。

解决办法:

  • 重载一份带参构造函数,第一个参数设置成为int即可vector(int n, const T& val)

拷贝构造函数

这里要自己实现,编译器默认生成的拷贝构造是浅拷贝,这里需要深拷贝实现。
开一块与源容器空间大小一样的空间,然后将源容器中的数据拷贝过来即可

vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{size_t capacity = v._end_of_storage - v._start;//v的容量size_t size = v._finish - v._start;//v的元素个数_start = new T[capacity];memcpy(_start, v._start,sizeof(T)*size);//将源容器的数据拷贝过来。_finish = _start + size;_end_of_storage = _start + capacity;
}

注意
如果使用memcpy进行实现,对于内置类型是没有任何问题的,但是对于自定义类型,会出错
比如下面的代码

vector<std::string> v4(3,"Hello World");
vector<std::string> v5(v4);
  • v4中存放的是4个string类型,每一个sting分别指向对应得地址空间

image.png

  • 执行vector<std::string> v5(v4),用v4构造v5.
  • v5会开辟一块和v4一样大小得空间地址
    image.png
  • memcpy拷贝得时候会把v4中得string原封不动按字节依次拷贝下来,这里就会出现问题。
  • v4和v5 虽然是不同的空间地址,但是里面的string却是同一块地址。就会导致string调用析构函数时会被析构两次,就会出错。
    image.png

解决方法:

  • 不要使用memcpy进行拷贝数据,memcpy本身就是浅拷贝。调用string类的赋值运算符重载函数进行深拷贝。
vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{size_t capacity = v._end_of_storage - v._start;//v的容量size_t size = v._finish - v._start;//v的元素个数_start = new T[capacity];for (size_t i = 0; i < size; i++){_start[i] = v._start[i];}_finish = _start + size;_end_of_storage = _start + capacity;
}
  • 这里对于内置类型来说直接赋值,没有任何问题,对于自定义类型,会调用自己的赋值运算符重载函数进行深拷贝
  • 拷贝之后的示意图 每个对象之间指向不同地址空间,互不影响。

image.png
总结:拷贝数据对于内置类型和自定义类型的浅拷贝来说使用memcpy进行拷贝没有任何问题,但是涉及到自定义类型的深拷贝,memcpy是完成不了的。
浅拷贝:两个对象会指向同一块地址空间,一个的改变会影响到另一个
深拷贝:两个对象分别指向不同的地址空间,彼此之间互不影响

赋值运算符重载

赋值运算符重载函数的实现也要实现深拷贝,实现思路和拷贝构造一样。
唯一需要注意的就是防止自己给自己赋值和返回值问题

vector<T>& operator=(const vector<T>& v)
{if (this != &v)//防止自己给自己赋值{size_t capacity = v._end_of_storage - v._start;//v的容量size_t size = v._finish - v._start;//v的元素个数delete[] _start;_start = new T[capacity];for (size_t i = 0; i < size; i++){_start[i] = v._start[i];}_finish = _start + size;_end_of_storage = _start + capacity;}return *this;
}

如果自己给自己赋值,delete后,自己的数据已经没了,在进行拷贝赋值,则会出错。
返回*this是为了支持连续赋值。

析构函数

_start、_finish、_end_of_storage指向的是同一块地址空间,析构_start,让其它置空即可。

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

写到这回头看代码,除了析构函数其他的这是什么鬼东西,代码冗余度太高了。重复的代码太多了。
解决这个问题等后面的iterator 和capacity是实现完就可以解决了。简单来说就是把重复出现的代码封装成一个函数,函提函数复用即可。

Iterators

迭代器的模拟实现 主要实现正向迭代器。

begin && end

begin()返回容器的首地址,end()返回容器当中有效数据的下一个数据的地址
如下图所示
image.png

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

capacity

size && capacity

  • size 返回返回容器当中有效数据的个数
  • capacity 返回容器的最大容量
    image.png
size_t size()
{return _finish - _start;
}
size_t capacity()
{return _end_of_storage - _start;
}
//const版本
size_t size()const
{return _finish - _start;
}
size_t capacity()const
{return _end_of_storage - _start;
}

reserve

reserve函数的规则:

  • 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
  • 当n小于对象当前的capacity时,什么也不做。即不会缩容
void reserve(size_t n)
{if (n > capacity()){size_t sz = size();//记录当前容器当中有效数据的个数T* tmp = new T[n];for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}

注意

  • 这里也不能使用memcpy进行拷贝数据,如果是内置类型没有问题,一但是自定义类型,就会是浅拷贝,在析构时会出错。
  • 这里需要提前记录一下容器的数据个数,如果代码这样写
void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start +  size();_end_of_storage = _start + n;}
}
  • 12行代码去调用size函数,此时size函数中的_start已经是新空间的地址,而_finish还是旧空间的地址,在物理空间上已经不连续了,让两个指针进行相减是拿不到元素个数的。
  • 解决这个问题只能先记录元素个数,在让新空间的_start加上元素个数就是_finish的地址空间了。

resize

resize()规则

  • 当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
  • 当n小于当前的size时,将size缩小到n
void resize(size_t n, const T& val = T())
{if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}//填数据while (_finish != _start + n){*_finish = val;++_finish;}}
}

empty

判断容器是否为空,这个很简单,直接比较_start 和 _finish即可

bool empty()const
{return _start == _end_of_storage;
}

这里的empty函数定义成const成员函数更合理 const修饰类的成员函数实际上是修饰该成员函数隐含的this指针,表明在该成员函数中不对类的任何成员进行修改。还能防止写错写成赋值,写错编译器也能找出问题。

Element access

[]重载

T& operator[](size_t i)
{//检查下标合法性assert(i < size());return _start[i];
}
const T& operator[](size_t i)const
{assert(i < size());return _start[i];
}

Modifiers

push_back

尾插 首先考虑容器是否已满,若容量已满应先进行扩容操作,在插入数据。

void push_back(const T& x)
{//检查容量if (_finish == _end_of_storage){//扩容size_t capacity = capacity() == 0 ? 2 : capacity() * 2;//对空容器进行判断reserve(capacity);}*_finish = x;++_finish;
}

如果容器为空不加以判断,reserve(0)没意义,导致尾插失败

pop_back

为删时则应该考虑容器是否为空,对空容器进行检查,在删除数据
删除数据直接对_finish指针自减即可。

void pop_back()
{assert(!empty());--_finish;
}

insert

void insert(iterator pos, const T& x)
{if (_finish == _end_of_storage){//扩容size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;//对空容器进行判断reserve(newcapacity);}//挪动数据插入iterator end = _finish -1 ;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}

注意

  • insert如果按照上述的逻辑写,如果不发生扩容,没有问题,一旦在insert时发生扩容就会出问题。
  • 一旦发生扩容10行代码中的_finish已经是扩容后新空间的地址,而pos还是旧空间的地址,让end和pos去比较,没有任何意义。

image.png
解决方法
扩容前记录pos到_start 的距离 扩容后更新pos

void insert(iterator pos, const T& x)
{if (_finish == _end_of_storage){//记录pos到_start的距离size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);//扩容完更新pospos = _start + len;}//挪动数据插入iterator end = _finish -1 ;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}

erase

iterator erase(iterator pos)
{assert(!empty());iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;
}

成员函数重构

//带参构造函数
vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(n);for (int i = 0; i < n; ++i){push_back(val);}
}
//迭代器构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{//开空间while (first != last){push_back(*first);++first;}
}
//拷贝构造函数
vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{_start = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start +v.capacity();
}
//赋值运算符重载
vector<T>& operator=(const vector<T>& v)
{if (this != &v)//防止自己给自己赋值{delete[] _start;_start = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}

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

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

相关文章

JVM的内存分区以及垃圾收集

1.JVM的内存分区 1.1方法区 方法区(永久代&#xff09;主要用来存储已在虚拟机加载的类的信息、常量、静态变量以及即时编译器编译后的代码信息。该区域是被线程共享的。 1.2虚拟机栈 虚拟机栈也就是我们平时说的栈内存&#xff0c;它是为java方法服务的。每个方法在执行的…

最大公因数,最小公倍数详解

前言 对于初学编程的小伙伴们肯定经常遇见此类问题&#xff0c;而且为之头疼&#xff0c;今天我来给大家分享一下&#xff0c;最大公因数和最小公倍数的求法。让我们开始吧&#xff01; 文章目录 1&#xff0c;最大公因数法1法2法3 2&#xff0c;最小公倍数3&#xff0c;尾声 …

[C++] 虚函数、纯虚函数和虚析构(virtual)

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/weixin_43197380&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 Loewen丶原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&…

cmake 最基础示例

C 代码 文件名&#xff1a;first_cmake.cpp #include <iostream> using namespace std;int main() {cout<< "A" << endl;return 0; }CMakeLists.txt 文件 #CMakeLists.txt # 设置:版本 cmake_minimum_required(VERSION 3.20)# 定义 :项目名称 …

宏基因组学及宏转录组学分析工具MOCAT2(Meta‘omic Analysis Toolkit 2)安装配置及常用使用方法

详细介绍 尽管这个工具已经暂停后续开发&#xff0c;但其工具功能还是挺好的&#xff0c;大家可以参考一下&#xff0c;尤其对于喜欢自定义开发流程的可以参考是流程。 MOCAT 2&#xff08;Metaomic Analysis Toolkit 2&#xff09;是一个用于宏基因组和宏转录组数据分析的工具…

Java实现快速排序算法

快速排序算法 &#xff08;1&#xff09;概念&#xff1a;快速排序是指通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后再按此方法对这两部分数据分别进行快速排序。整个排序过程可以递归进行&…

TSINGSEE青犀城市道路积水AI检测算法视频智能监管解决方案

近年来&#xff0c;由于城市区域内涝频发&#xff0c;遇到强降水天气出现路面严重积水的情况时有发生&#xff0c;影响交通通行甚至引发事故。所以&#xff0c;对下穿隧道、下沉式道路等路面积水情况的监测显得尤为重要。传统的监管方式很难及时发现道路积水情况&#xff0c;那…

物易管预测性维护平台3.6.0版本上线,工况数据处理、设备故障模型、数据可视化等方面带来全新功能体验

物易管设备预测性维护平台V3.6.0版本近日正式发布上线&#xff0c;相较V3.5.0版本次主要新增优化设备工况数据接入、工况数据模型训练、数据可视化以及设备监测详情优化四个板块。新版本在处理工况数据、设备故障模型、数据分析展示以及设备监测方面带来全新的体验。 01设备工况…

Jtti:ssl协议未开启怎么解决?

如果你的服务器上的SSL协议未开启&#xff0c;可以按照以下步骤检查和解决问题。SSL协议通常由Web服务器配置和启用。以下是基于常见的Web服务器的步骤&#xff1a; Apache Web 服务器&#xff1a; 1.检查 Apache 是否加载了 SSL 模块&#xff1a; 在终端中运行以下命令&#x…

基于SSM的高校疫情管理系统设计与实现论文

摘 要 当下疫情不容松懈&#xff0c;此次新冠肺炎疫情是传播速度最快、感染范围最广、防控难度最大的一次重大突发公共卫生事件&#xff0c;疫情防控尤为重要&#xff0c;传统的基于纸质文本的疫情信息管理模式&#xff0c;效率低&#xff0c;无法满足管理需求&#xff0c;所以…

若依源码分析

一.登录 1.1 生成验证码 基本思路 后端生成一个表达式,74?11 74?转成图片,传到前端进行展示 将结果11存入redis 前端代码实现: 请求后端地址:http://localhost/dev-api/captchaImage,通过反向代理解决前后端跨域问题,将请求路径变为:http://localhost:8080/captchaImag…

ESXI 6.7升级update3

一、适用场景 1、企业已有专业服务器&#xff0c;通过虚拟化环境搭建了vm server&#xff1b; 2、备份整个vm server时&#xff0c;需要使用ovftool工具完成&#xff0c;直接导出ovf模板时报错&#xff1b; 3、升级EXSI6.7的build 8169922版本为update 3版本后&#xff0c;已保…

The method show() from the type Window is deprecated

java.awt.Window.show() java.awt.Component.setVisible(true); Window.show() java.awt.JFrame java.awt.Frame java.awt.Windows java.awt.Component.setVisible(true);

论文查重过多怎么降重 神码ai

大家好&#xff0c;今天来聊聊论文查重过多怎么降重&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 论文查重过多怎么降重 当论文查重率过高时&#xff0c;需要进行降重处…

LeetCode力扣每日一题(Java)66、加一

每日一题在昨天断开了一天&#xff0c;是因为作者沉迷吉他&#xff0c;无法自拔……竟然把每日一题给忘了&#xff0c;所以今天&#xff0c;发两篇每日一题&#xff0c;把昨天的给补上 一、题目 二、解题思路 1、我的思路 其实乍一看这道题还是比较简单的&#xff0c;就是让…

商业智能BI和数据可视化的区别

现在市场上有非常多的商业智能BI产品&#xff0c;几乎都在着重宣传其数据可视化功能的强大&#xff0c;给人造成一种商业智能BI就是数据可视化的印象。事实上商业智能BI并不等于数据可视化。要探究商业智能BI和数据可视化的区别&#xff0c;我们先要分别弄清楚这两个概念。 1、…

gitlab下载,离线安装

目录 1.下载 2.安装 3.配置 4.启动 5.登录 参考&#xff1a; 1.下载 根据服务器操作系统版本&#xff0c;下载对应的RPM包。 gitlab官网&#xff1a; The DevSecOps Platform | GitLab rpm包官网下载地址: gitlab/gitlab-ce - Results in gitlab/gitlab-ce 国内镜像地…

智能优化算法应用:基于狮群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于狮群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于狮群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.狮群算法4.实验参数设定5.算法结果6.参考文献7.MA…

常见的Linux基本指令

目录 什么是Linux&#xff1f; Xshell如何远程控制云服务器 Xshell远程连接云服务器 Linux基本指令 用户管理指令 pwd指令 touch指令 mkdir指令 ls指令 cd指令 rm指令 man命令 cp指令 mv指令 cat指令 head指令 ​编辑 tail指令 ​编辑echo指令 find命令 gr…

记一次挖矿病毒的溯源

ps&#xff1a;因为项目保密的原因部分的截图是自己在本地的环境复现。 1. 起因 客户打电话过来说&#xff0c;公司web服务异常卡顿。起初以为是web服务缓存过多导致&#xff0c;重启几次无果后觉得可能是受到了攻击。起初以为是ddos攻击&#xff0c;然后去查看web服务器管理…