C++初阶:适合新手的手撕vector(模拟实现vector)

上次讲了常用的接口:C++初阶:容器(Containers)vector常用接口详解
今天就来进行模拟实现啦


文章目录

  • 1.基本结构与文件规划
  • 2.空参构造函数(constructor)
  • 4.基本函数(size(),capacity(),resize(),reserve())
  • 4.增删改查(push_back,pop_back,insert,erase)
  • 5.在实现Insert和erase时迭代器失效问题
  • 6.重载[]
  • 7. 完善构造函数
    • 7.1vector (size_type n, const value_type& val = value_type());
    • 7.2利用迭代器进行构造
    • 7.3拷贝构造
  • 8.重载=
  • 9.析构函数


1.基本结构与文件规划

请添加图片描述

  • vector.h头文件:包含类的全部(函数的声明与定义)
  • test.cpp源文件:进行调用test函数,测试和完善功能

基本结构,先看一下源码:

请添加图片描述

namespace MyVector
{template <class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//先定义好迭代器//各种函数private:iterator _start;iterator _finish;iterator _endOfStorage;};
}
  • _start:指向动态数组的起始位置的指针,即第一个元素的位置。
  • _finish:指向动态数组中最后一个元素之后的位置的指针。在这个实现中,_finish 指针始终指向当前元素范围的末尾,也就是下一个要插入元素的位置。
  • _endOfStorage:指向动态数组分配的内存空间的末尾之后的位置的指针。在这个实现中,_endOfStorage 指针指向当前分配的内存空间的末尾,当需要扩充容量时,会通过比较 _finish_endOfStorage 的位置来判断是否需要重新分配更大的内存空间

2.空参构造函数(constructor)

		vector():_start(nullptr), _finish(nullptr), _endOfStorage(nullptr)//直接使用初始化列表{}

都初始化为空指针


#3.迭代器(iterator)(begin(),end())

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

进行const的重载

4.基本函数(size(),capacity(),resize(),reserve())

		void reserve(size_t n){if (n > capacity()){int old_size = size();//保存一下长度,方便后续给_finish移到新的位置T* tmp = new T[n];if (_start != nullptr)//vector里存东西了{for (size_t i = 0; i < size(); ++i){tmp[i] = _start[i];//_start本质是指针}}delete[] _start;_start = tmp;_finish = _start + old_size;_endOfStorage = _start + n;}}void resize(size_t n, const T& x = T()){if (n > size()){reserve(n);//<capacity 的话,也没有进行处理while (_finish != _start + n){*_finish = x;++_finish; }}else{_finish = _start + n;//小于长度时,直接移动finish}}size_t size(){return _finish - _start;}size_t capacity(){return _endOfStorage - _start;}
  1. reserve 函数:
  • reserve 函数用于保留至少能容纳 n 个元素的内存空间。如果当前的容量小于 n,则会分配新的内存空间,并将原来的元素复制到新的内存空间中。
  • 首先,它会创建一个新的大小为 n 的临时数组 tmp,然后将原始数组中的元素复制到临时数组中。
  • 接着,释放原始数组的内存空间,将 _start 指针指向新分配的内存空间,同时更新 _finish_endOfStorage 的位置。
  1. resize 函数:
  • resize 函数用于改变数组的大小,使其包含 n 个元素,并使用值 x 进行初始化。
  • 如果 n 大于当前的大小,它会调用 reserve 函数以确保数组有足够的容量,然后将数组的大小增加到 n,并使用值 x 进行初始化。
  • 如果 n 小于当前的大小,它会直接将 _finish 指针移动到新的位置,从而改变数组的大小。
  1. size 函数:
  • size 函数用于返回数组中元素的个数,即 _finish_start 之间的距离。
  1. capacity 函数:
  • capacity 函数用于返回数组的容量,即 _endOfStorage_start 之间的距离

怎么来理解:const T& x = T()

实现给出各种类型的默认值,在这里为了妥协,其实内置类型也有构造函数在 C++ 中。内置类型(如 intfloatdouble 等)也有默认构造函数。默认构造函数对于内置类型来说,其实就是不带参数的构造函数,它会将变量初始化为默认值

  1. T() 表示创建一个类型 T 的临时对象,并进行值初始化。这里假设 T 是一个类或者结构体,那么这个语句会调用 T 的默认构造函数来创建一个临时对象。
  2. const T& x 表示创建一个类型为 T 的常量引用 x。这里的引用是 T 类型的引用,而且是常量引用,意味着 x 引用的对象是不可修改的。
  3. const T& x = T() 将这个临时对象绑定到常量引用 x 上。这样做的好处是可以避免不必要的拷贝,同时也可以确保 x 引用的对象是不可修改的。

使用如下来测试

	void test1(){vector<int> v;for (auto e : v){cout << e << " ";}cout << endl;v.resize(10);for (auto e : v){cout << e << " ";}cout << endl;v.resize(5);for (auto e : v){cout << e << " ";}cout << endl;}

请添加图片描述


4.增删改查(push_back,pop_back,insert,erase)

		void push_back(const T& x){if (_finish == _endOfStorage){int newcapacity = capacity() == 0 ? 2 : 2 * capacity();reserve(newcapacity);}*_finish = x;_finish++;}void pop_back(){assert(size() > 0);--_finish;}iterator insert(iterator pos, const T& x)//在pos前插入{assert(pos < _finish&& pos >= _start);if (_finish == _endOfStorage){size_t site = pos - _start;int newcapacity = capacity() == 0 ? 2 : 2 * (capacity());reserve(newcapacity);pos = _start + site;//pos到新空间的位置上}iterator end = _finish - 1;while (end >= pos)//开始整体向后退{*(end + 1) = *end;end--;}*pos = x;++_finish;return pos;}iterator erase(iterator pos)//删pos处{assert(pos < _finish&& pos >= _start);assert(size() > 0);//开始向前移动iterator start = pos + 1;while (start < _finish){*(start - 1) = *start;start++;}_finish--;return pos;//返回删除的位置}

使用test2函数看功能是否正常

void test2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);//尾插3个for (auto e : v){cout << e << " ";}cout << endl;v.pop_back();//尾删一个for (auto e : v){cout << e << " ";}cout << endl;v.insert(v.begin(), 0);//头插一个0for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin());//头删for (auto e : v){cout << e << " ";}cout << endl;}

请添加图片描述


5.在实现Insert和erase时迭代器失效问题

当使用迭代器遍历容器时,如果在遍历的过程中对容器进行了结构性的修改(例如插入、删除元素,重新分配内存等操作),可能会导致迭代器失效。迭代器失效意味着该迭代器不再指向有效的元素或容器的结尾,因此继续使用失效的迭代器可能会导致未定义行为。

迭代器失效的原因主要有以下几种:

  1. 插入操作:当在容器中插入元素时,可能会导致容器内部的元素发生移动或重新分配内存,这会导致原先的迭代器失效。因为插入元素后,原先的迭代器可能不再指向正确的位置。
  2. 删除操作:当在容器中删除元素时,可能会导致容器内部的元素发生移动,也会导致原先的迭代器失效。因为删除元素后,原先的迭代器可能指向了一个已经被删除的元素,或者指向了不正确的位置。
  3. 重新分配内存(扩容时):某些容器在元素数量达到一定阈值时会进行内存的重新分配,这会导致原先的迭代器失效。因为重新分配内存后,原先的迭代器可能指向了无效的内存地址。
  4. 容器的清空:当对容器进行清空操作时,所有的元素都被移除,迭代器也会失效。

迭代器失效可以大致分为两类:

  1. 结构性变化导致的失效:这类失效包括扩容时申请了新空间、插入或删除元素导致元素位置改变等情况。在这种情况下,原先的迭代器可能会指向已经被移动或者删除的元素,或者指向了新分配的内存空间,导致迭代器失效。
  2. 数据变化导致的失效:这类失效包括使用了 memmovestd::copy 等函数对容器内部元素进行移动或复制的情况。这些函数可能会导致容器内部的元素发生移动,导致原先的迭代器指向的位置发生变化,从而导致迭代器失效。
	void test3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;//删除偶数vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){it=v.erase(it);//这里不能只是v.erase(it); 删除后}else{it++;}}for (auto e : v){cout << e << " ";}cout << endl;}

在使用 erase 函数删除元素后,erase 函数会返回指向被删除元素之后的元素的迭代器,而不是原先被删除元素的迭代器。如果使用 v.erase(it);,则会导致 it 迭代器失效,因为它指向的元素已经被删除,而 it 没有更新。因此,为了确保迭代器的有效性,需要将返回的迭代器赋值给 it,以便在下一次循环中继续使用正确的迭代器。


6.重载[]

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

7. 完善构造函数

7.1vector (size_type n, const value_type& val = value_type());

		vector(size_t n, const T& val= T()){resize(n, val);}vector(int n, const T& val = T())//适用于  vector<int> v(5,1){resize(n, val);}

7.2利用迭代器进行构造

		template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}

为什么使用模版:

因为可能使用其他类型的迭代器来进行初始化

7.3拷贝构造

		vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endOfStorage(nullptr)//先利用初始化列表进行初始化{reserve(v.capacity());for (const auto& e : v){push_back(e);}}

8.重载=

		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;}

注意这里的参数不是常量引用,而是按值传递的。这是因为在赋值操作符中我们会调用 swap 函数,按值传递可以保证传入的参数会被复制一份,避免对原对象的修改。在函数体内,我们调用了 swap 函数,将当前对象和传入的对象进行内容交换,然后返回 *this,即当前对象的引用。


9.析构函数

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

好啦,今天就到这里啦,感谢大家支持!!!

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

软件文档测试

1 文档测试的范围 软件产品由可运行的程序、数据和文档组成。文档是软件的一个重要组成部分。 在软件的整人生命周期中&#xff0c;会用到许多文档&#xff0c;在各个阶段中以文档作为前阶段工作成果的体现和后阶段工作的依据。 软件文档的分类结构图如下图所示&#xff1a; …

fast.ai 深度学习笔记(七)

深度学习 2&#xff1a;第 2 部分第 14 课 原文&#xff1a;medium.com/hiromi_suenaga/deep-learning-2-part-2-lesson-14-e0d23c7a0add 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 来自 fast.ai 课程的个人笔记。随着我继续复习课程以“真正”理解它&#xff0c;…

InternLM大模型实战-1.书生浦语大模型全链路开源体系

文章目录 前言笔记正文大模型成为热门关键词书生浦语开源历程从模型到应用书生浦语全链条开源开放体系数据预训练微调评测部署部署智能体LagentAgentLego 总结 前言 本系列文章是参与书生浦语全链路开源体系学习的笔记文章。B站视频教程地址&#xff1a; 笔记正文 大模型成为…

【算法训练营】数字盒子,重编码,成绩排序(python实现)

数字盒子 问题描述 你有一个盒子&#xff0c;你可以往里面放数&#xff0c;也可以从里面取出数。 初始时&#xff0c;盒子是空的&#xff0c;你会依次做 Q 个操作&#xff0c;操作分为两类&#xff1a; 插入操作&#xff1a;询问盒子中是否存在数 x&#xff0c;如果不存在则把数…

Java图形化界面编程——菜单组件 笔记

2.7 菜单组件 ​ 前面讲解了如果构建GUI界面&#xff0c;其实就是把一些GUI的组件&#xff0c;按照一定的布局放入到容器中展示就可以了。在实际开发中&#xff0c;除了主界面&#xff0c;还有一类比较重要的内容就是菜单相关组件&#xff0c;可以通过菜单相关组件很方便的使用…

KAJIMA CORPORATION CONTEST 2024(AtCoder Beginner Contest 340)ABCDEF 视频讲解

这场比较郁闷&#xff0c;C题短路&#xff0c;连续4次WA&#xff0c;导致罚时太多 A - Arithmetic Progression Problem Statement Print an arithmetic sequence with first term A A A, last term B B B, and common difference D D D. You are only given inputs for w…

算法学习——LeetCode力扣栈与队列篇2

算法学习——LeetCode力扣栈与队列篇2 150. 逆波兰表达式求值 150. 逆波兰表达式求值 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。…

《Python 网络爬虫简易速速上手小册》第9章:爬虫项目的部署与运维(2024 最新版)

文章目录 9.1 爬虫的部署策略9.1.1 重点基础知识讲解9.1.2 重点案例&#xff1a;使用 Docker 部署爬虫到云服务平台9.1.3 拓展案例 1&#xff1a;使用 Kubernetes 管理爬虫的部署和扩展9.1.4 拓展案例 2&#xff1a;利用 GitHub Actions 实现 CI/CD 9.2 日志管理与错误处理9.2.…

“深度解析Java虚拟机:运行时数据区域、垃圾收集、内存分配与回收策略、类加载机制“

"深度解析Java虚拟机&#xff1a;运行时数据区域、垃圾收集、内存分配与回收策略、类加载机制" Java 虚拟机一、运行时数据区域程序计数器Java 虚拟机栈本地方法栈堆方法区运行时常量池直接内存 二、垃圾收集判断一个对象是否可被回收1. 引用计数算法2. 可达性分析算…

fast.ai 深度学习笔记(一)

深度学习 2&#xff1a;第 1 部分第 1 课 原文&#xff1a;medium.com/hiromi_suenaga/deep-learning-2-part-1-lesson-1-602f73869197 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 来自 fast.ai 课程的个人笔记。随着我继续复习课程以“真正”理解它&#xff0c;这…

使用vue-client-only 解决组件不兼容SSR问题

目录 前言 一、解决方案 1.基于Nuxt 框架的SSR应用 2.基于vue2框架的应用 3.基于vue3框架的应用 二、总结 往期回顾 前言 最近在我的单页面SSR应用上开发JSON编辑器功能&#xff0c;在引入组件后直接客户端跳转OK&#xff0c;但是在直接加载服务端渲染的时候一直报这…

解密输入输出迷局:蓝桥杯与ACM中C++/C语言常见问题揭秘

关于C中的常见输入输出汇总 带空格的字符串&#xff1a; ​ 对于这种输入方式我们选择使用gets() 函数来进行输入&#xff0c;gets用于从标准输入&#xff08;通常是键盘&#xff09;读取一行文本并将其存储为字符串&#xff0c;直到遇到换行符&#xff08;‘\n’&#xff09…

javaEE - 22( 5000 字 Tomcat 和 HTTP 协议入门 -3)

一&#xff1a;Tomcat 1.1 Tomcat 是什么 谈到 “汤姆猫”, 大家可能更多想到的是大名鼎鼎的这个: 事实上, Java 世界中的 “汤姆猫” 完全不是一回事, 但是同样大名鼎鼎. Tomcat 是一个 HTTP 服务器. 前面我们已经学习了 HTTP 协议, 知道了 HTTP 协议就是 HTTP 客户端和…

基于FPGA的图像最近邻插值算法verilog实现,包括tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 将FPGA数据导入matlab显示图片&#xff0c;效果如下&#xff1a; 2.算法运行软件版本 vivado2019.2&#xff0c;matlab2022a 3.部分核心程序 ti…

python高校实验室管理系统的django设计与实现81txp

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm .本高校实验室管理系统采用python语言、MySQL数据库&…

Flink基础篇|002_Flink前世今生

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、腾讯云优秀创作者 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注、&#x…

【计算机网络】协议层次及其服务模型

协议栈&#xff08;protocol stack&#xff09; 物理层链路层网络层运输层应用层我们自顶向下&#xff0c;所以从应用层开始探究应用层 协议 HTTP 提供了WEB文档的请求和传送SMTP 提供电子邮件报文的传输FTP 提供两个端系统之间的文件传输报文&#xff08;message&#xff09;是…

gem5学习(19):gem5内存系统——The gem5 Memory System

目录 一、Model Hierarchy 二、CPU 三、Data Cache Object 四、Tags & Data Block 五、MSHR and Write Buffer Queues 六、Memory Access Ordering 七、Coherent Bus Object 八、Simple Memory Object 九、Message Flow 1、Memory Access Ordering&#xff08;re…

【MySQL】MySQL表的增删改查(进阶)

MySQL表的增删改查&#xff08;进阶&#xff09; 1. 数据库约束1.1 约束类型1.2 NULL约束1.3 UNIQUE:唯一约束1.4 DEFAULT&#xff1a;默认值约束1.5 PRIMARY KEY&#xff1a;主键约束1.6 FOREIGN KEY&#xff1a;外键约束:1.7 CHECK约束&#xff08;了解&#xff09; 2. 表的设…

NTLM||LM算法lsasswinlogon进程

来填坑了&#xff0c;这篇blog我们就来讲一下mimikatz能抓到开机的密码的原理 1.lsass&&winlogon 不知道大家有没有好奇过&#xff0c;我们每次开机输入密码之后&#xff0c;电脑又怎么知道我们是否输入正确呢&#xff1f; &#xff1a;这就要的得益于我们的两个进程…