C++初阶 | [八] (下) vector 模拟实现

摘要:vector 模拟实现讲解(附代码示例),隐藏的浅拷贝,迭代器失效

在进行 vector 的模拟实现之前,我们先粗略浏览一下 stl_vector.h 文件中的源码来确定模拟实现的大体框架。


这里提供一些粗略浏览源码的技巧:
1.不要一行一行地看

2.先不要研究细节,先看整体的框架(类:成员变量+成员函数)

3.理解的时候连蒙带猜,再验证自己的猜测

ps.在已经模拟实现过 string类的前提下,vector 的模拟实现的讲解在一些非必要的地方不多赘述,直接给出代码示例。

框架:👇

template<class T>
class vector
{
private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾(指向最后一个有效数据的下一个)iterator _endOfStorage = nullptr; // 指向存储容量的尾
};

首先,同 string 类的匿名实现一样,我们先创建一个自己的命名空间,将 vector 的模拟实现在这个自定义的命名空间中以区分库中的vector。

1. Constructor and Destructor

不多赘述。示例如下。

#pragma oncenamespace Bottle
{template<class T>class vector{public:// construct and destroyvector(){}~vector(){delete[]_start;_start = _finish = _endOfStorage = nullptr;}private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾(指向最后一个有效数据的下一个)iterator _endOfStorage; // 指向存储容量的尾};}

2. push_back

(1)这里需要顺便实现 size_t capacity()函数 size_t size()函数

(2)思路:检查容量 → 插入数据
(注意:检查容量时,如果是遵循两倍扩容的思路,就需要注意容量为0的情况,如果此时直接按两倍扩容会导致错误)

代码示例:

		size_t size() const{return  _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}//push_backvoid push_back(const T& x){if (_finish == _endOfStorage){//check capacitysize_t new_capacity = capacity() == 0 ? 4 : (capacity() * 2);reserve(new_capacity);}*(_finish) = x;//push back++_finish;}

3. reserve

思路:开新空间 → 拷贝数据到新空间 → 指向新空间(ps.原则上只扩容不缩容)

  • 关于指向新空间这个操作需要注意的问题:不同于 string类 size数据是由成员变量存储起来的,vector size是通过算两个指针之间的偏移量得出的。
    如图所示,当 _start 发生改变之后_finish ≠ _start + size(),而应该提前记录下 _finish 相对于 _start 的偏移量。

代码示例: 

//reserve	void reserve(size_t n){if (n > capacity()){T* tmp = new T[n + 1];//newsize_t sz = size();memcpy(tmp, _start, sizeof(T) * size());//copydelete[]_start;_start = tmp;_finish = tmp + sz;_endOfStorage = tmp + n;tmp = nullptr;}}

4. Access

1)operator[]

代码示例:

//operatorr[]T& operator[](const size_t pos){assert(pos < size());return *(_start + pos);}const T& operator[](const size_t pos)const{assert(pos < size());return *(_start + pos);}

2)iterator

(迭代器实现之后就可以使用范围for了)

代码示例:

	public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator cbegin()const{return _start;}const_iterator cend()const{return _finish;}

5. resize

有模板(template)之后,C++对内置类型进行了升级,即一切都是对象,以前在C语言里面只有变量,而在之后的C++中是变量,也是对象。
e.g. int vi = int(2);//该语句可看作调用默认构造函数用 ‘2’ 来构造一个 int 类型的对象 vi . (内置类型也会调用构造)

代码示例:

//resizevoid resize(size_t n, const T& value = T()){if (n <= size()){//delete_finish = _start + n;}else{reserve(n);//check capacitysize_t len = n - size();while (len--){*_finish = value;++_finish;//改变finish的指向就是改变size的大小}}}

6. insert

iterator insert(iterator pos, const T& x){……}

  • string 模拟实现 insert 

    思路:①检查 pos 位置的有效性;②检查容量;③挪动数据;④插入数据(strncpy)。

    特殊情况:头插(pos==0)

  • vector
    思路:①检查 pos 位置的有效性;②检查容量;③挪动数据;④插入数据。

    特殊情况:头插(pos==0)

  • 区别
    string中的挪动数据时比较的是下标和下标,即 size_t 数据类型的比较,头插时比较特殊,下标不断变小的过程中可能会出现“-1>0” 的情况(详情见string类模拟实现的文章)
    vector中挪动数据时比较的是迭代器和迭代器,即 T* (因为这里迭代器的底层实现用的是原生指针)数据类型的比较,所以这里头插不属于特殊情况。但是,这也由此引发了另外的问题——迭代器失效

迭代器失效

iterator pos 底层是指针。reserve 扩容之后原空间被释放,而 pos 还指向原空间。所以我们需要获取 pos 相对于 _start 的偏移量。

② iterator pos 是传值传参。如下图所示。(提醒💡:这里也不适合用 传引用传参 (iterator& pos),要引用只能是 const 引用 (const iterator& pos),因为在类似 v.insert(v.begin(),数据) 的函数调用中,begin()函数是传值返回,则它的返回值具有常属性,只能用 const 引用,但 const 引用会使 pos 无法被修改,则对于下图所示的迭代器是没有意义的)

代码示例:

//insertiterator insert(iterator pos, const T& x){//check posassert(pos >= _start);assert(pos <= _finish);size_t len = pos - _start;//偏移量//check capacityif (_finish == _endOfStorage){reserve(capacity() == 0 ? 4 : (capacity() * 2));pos = _start + len;//reserve之后更新pos}for (iterator end = _finish; end > pos; --end){*end = *(end - 1);//move data}*pos = x;//pos位置insert data++_finish;return pos;}

7. erase

思路:依次挪动数据覆盖

注意:erase 同样会导致迭代器失效

  • 关于erase导致的迭代器失效:

    如下图,删除数据中偶数的行为出错是因为 erase 导致的迭代器失效,删除符合条件的元素之后,数据的内容发生了改变,就导致原本指向该元素(被删除的这个元素)的迭代器的指向是未知的,当我们在 erase 之后再去使用这个迭代器,行为也是位置的。

    库里面的做法是 erase 函数会返回要删除的指定 pos 位置的元素的下一个元素的位置。
     

代码示例:

		iterator erase(iterator pos){//check posassert(pos >= _start);assert(pos <= _finish);//move datafor (iterator it = pos + 1; it < _finish; ++it){*(it - 1) = *(it);}--_finish;return pos;}

8. 隐藏的浅拷贝_reserve

memcpy:隐藏的浅拷贝 ( from reserve)(如下图所示,tip.如果这里运用 引用计数的浅拷贝就会很高效)

由上图可知,delete 释放空间,对于自定义类型 string 会自动调用析构函数,导致新空间指向已经被析构掉的空间。因此,拷贝数据最好通过赋值操作来实现,赋值会自动调用拷贝构造进行深拷贝

代码示例:

//reservevoid reserve(size_t n){if (n > capacity()){T* tmp = new T[n + 1];//newsize_t sz = size();for (size_t i = 0; i < sz; ++i)//copy{tmp[i] = *(_start + i);}//memcpy(tmp, _start, sizeof(T) * size());//copydelete[]_start;_start = tmp;_finish = tmp + sz;_endOfStorage = tmp + n;tmp = nullptr;}}

9. Copy Constructor

思路:
1)初始化成员变量
2)reserve
3)拷贝数据(push_back

代码示例:

//copy constructorvector(const vector<T>& v)//copy constructor:_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){reserve(v.capacity());for (size_t i = 0; i < v.size(); ++i){push_back(v[i]);}}

10. 赋值重载

现代写法同 string类 的模拟实现,详情见 string类 模拟实习的文章。

代码示例:

		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> v){swap(v);return *this;}

11. 其他构造函数重载

注意:自己写了构造函数之后,编译器不会在生成再生成默认构造,所以在构造函数中必须要有一个默认构造函数

1)template <class InputIterator>vector (InputIterator first, InputIterator last);
		template<class InputIterator>vector(InputIterator first, InputIterator last){size_t len = last - first;reserve(len);for (size_t i = 0; i < len; ++i){*(_start + i) = *(first + i);}_finish = _start + len;_endOfStorage = _finish;}
2)vector (size_t n, const T& val = T())

T 为 int 类型时,会出现类型匹配的问题,如下图:因此,对于 int 类型需要专门实现的一个函数重载。

		vector(size_t n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(int n, const T& value = T()){reserve(n);while (n--){push_back(value);}}

 

ps.默认构造函数:

		vector(){}

完整代码链接:My_vector/My_vector/My_vector.h · fantansy-13-07/Cpp - 码云 - 开源中国 (gitee.com)


END

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

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

相关文章

go环境安装-基于vscode的Windows安装

1、vscode安装 官网链接&#xff1a;https://code.visualstudio.com/ 选择相应的版本&#xff0c;这里选择Windows下的 下载得到一个VSCodeUserSetUp-x64的可执行文件&#xff0c;双击执行&#xff0c;选择要安装的路径&#xff0c;下一步。 2、go语言安装 官网链接&#x…

【Unity自制手册】Unity—Camera相机跟随的方法大全

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

搭建freqtrade量化交易机器人

本文采用python量化机器人框架 freqtrade 开始操作&#xff01; freqtrade官方文档 官方文档内容过多&#xff0c;请先跟随本文入门阅读&#xff0c;后续深入学习可参考官方文档&#xff5e; 1. 准备云服务器 docker 环境 这里以云服务器选择 ubuntu 系统开始&#xff0c;先…

微信小程序的医院体检预约管理系统springboot+uniapp+python

本系统设计的目的是建立一个简化信息管理工作、便于操作的体检导引平台。共有以下四个模块&#xff1a; uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 语言&#xff1a;pythonjavanode.js…

VBA来创建一个新的 Excel 文件

前言 其他的宏指令执行的前提条件是创建一个新的xlsx文件,来存储操作完成后的结果.否则会因为缺少操作对象,出现1004错误. Sub CreateNewFile()Dim xlApp As ObjectDim xlWB As Object 创建一个新的 Excel 应用程序对象Set xlApp CreateObject("Excel.Application")…

docker学习快速入门

目录 Linux下安装docker配置阿里云镜像加速docker命令部署安装Tomcat、ES容器数据卷DockerFiledocker网络制作tomcat镜像Redis集群部署SpringBoot微服务打包docker镜像拓展 什么是Docker Docker是内核级别的虚拟化&#xff0c;可以在一个物理机上可以运行很多的容器实例。服务…

Unity使用PlayableAPI 动态播放动画

1.初始化animator&#xff0c;创建Playable图&#xff0c;创建动画Playable private void InitAnimator(GameObject headGo) {if (headGo){_headAnimator headGo.GetComponent<Animator>();if (_headAnimator){_headAnimator.cullingMode AnimatorCullingMode.AlwaysA…

【面试】找工作历程

简单介绍一下自己&#xff1a;本科双非一本&#xff0c;22年毕业&#xff0c;工作一段时间到24年1月份辞职&#xff0c;怎么说实习加正式&#xff0c;工作了大概两年&#xff0c;年后准备换工作&#xff0c;目前IP上海。 2024.2.26 第一天正式投简历投了boss的上限&#xff0c;…

Sentinel 动态规则扩展

一、规则 Sentinel 的理念是开发者只需要关注资源的定义&#xff0c;当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则&#xff1a; 通过 API 直接修改 (loadRules)通过 DataSource 适配不同数据源修改 手动通过 API 修改比较直观&#xff0c;…

主机字节序与网络字节序

大端序和小端序 大端序&#xff08;Big Endian&#xff09;和小端序&#xff08;Little Endian&#xff09;是两种计算机存储数据的方式。 大端序指的是将数据的高位字节存储在内存的低地址处&#xff0c;而将低位字节存储在内存的高地址处。这类似于我们阅读多位数时从左往右…

YOLOv6代码解读[05] yolov6/core/engine.py文件解读

#!/usr/bin/env python3 # -*- coding:utf-8 -*- from ast import Pass import os import os.path as osp import time from copy import deepcopy from tqdm import tqdm import cv2 import numpy as np import mathimport torch from torch.cuda

新版vscode remote ssh不兼容老系统 (waiting for server log)

参考知乎-萌萌哒赫萝​ 最近vscode发布了1.86版本&#xff0c;该版本中&#xff0c;更新了对glibc的要求( ≥ \geq ≥ 2.28)&#xff0c;导致各种旧版本的linux发行版&#xff08;如centos 7&#xff09;都无法用remote-ssh来连接了&#xff0c;会一直控制台报错waiting for s…

迁移学习 领域自适应

迁移学习 什么是迁移学习 迁移学习是机器学习领域用于标记数据难获取这一基础问题的重要手段&#xff0c; 将训练好的内容应用到新的任务上被称为迁移学习。 由于这个过程发生在两个领域间&#xff0c;已有的知识和数据也就是被迁移的对象被称为源域&#xff0c;被赋予经验…

防火墙的内容安全

目录 1. 内容安全 1.1 IAE引擎 DPI---深度包检测技术 DFI---深度流检测技术 结论(优缺点)&#xff1a; 1.2 入侵防御&#xff08;检测&#xff09;(IPS) IPS的优势: 入侵检测的方法: 入侵检测的流程 签名 查看预定义签名的内容 新建自定义签名 入侵防御的检测…

面试题解答

题目 为管理业务培训信息&#xff0c;现需建立3个表&#xff1a; 表S(S#,SN,SD,SA)S#,SN,SD,SA分别代表学号&#xff0c;学员姓名&#xff0c;所属单位&#xff0c;学员年龄、 表C(C#,CN)C#,CN分别代表课程编号&#xff0c;课程名称 表SC(S#,C#,G)S#,C#,G分别代表学号&#xf…

热闹元宵进行中,如何利用VR全景展示民宿品牌形象?

错峰出游闹元宵&#xff0c;元宵节恰逢周末&#xff0c;而且还是春节假期返工之后的首个休息日&#xff0c;不少人都想通过短途度假来缓解“节后综合征”。两位数的特价机票、打折的各种酒店让你实现“旅行自由”&#xff0c;那么如何知道特价酒店服务好不好呢&#xff1f;先别…

Leetcode.901 股票价格跨度

题目信息 LeetoCode地址: . - 力扣&#xff08;LeetCode&#xff09; 题目理解 价格跨度的定义在题目中很明确&#xff0c;就是韭菜持有一只股票且该股票保持连续上涨最大的天数。 直观的想&#xff0c;我们可以保存第一天到当前天的所有股价&#xff0c;并一天一天往前找单…

2-27练习

1、请用fscanf和fprintf实现文件拷贝。 &#xff08;fputc和fgetc&#xff09; #include <stdio.h> int main(int argc, const char *argv[]) {FILE* fp NULL;fp fopen("./z1.txt","r");//用fscanf统计文件大小int count 0;char c; // while(fs…

对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

从铺天盖地的市场消息来看&#xff0c;华为即将面世的鸿蒙NEXT系统已经势不可挡了 想必大家都已经迫不及待地想要进行尝试。 估计大家都有着同样的疑问&#xff1a; 会不会是下一个风口&#xff1f;转鸿蒙应用开发难吗&#xff1f; 会不会是下一个风口&#xff1f; 自从鸿蒙…

江科大stm32 定时器 TIM输出比较--学习笔记

这几天遇到输出比较相关的问题&#xff0c;于是来学习下TIM输出比较部分知识点&#xff01; 输出比较简介 CNT是计数器的值&#xff0c;CCR寄存器是捕获/ 比较寄存器 简单的讲&#xff0c;输出比较就是用来输出PWM波形。 PWM简介 占空比&#xff1a;高电平占一个周期的比例。…