C++:模拟实现vector

目录

成员变量与迭代器

size

capacity

empty

迭代器有关函数

实现默认成员函数的前置准备

reserve

​编辑

​编辑

push_back

构造函数

无参构造

迭代器区间构造

n个val来进行构造 

析构函数

拷贝构造函数

赋值重载

增删查改

clear

resize

pop_back

insert

erase

重载[]


成员变量与迭代器

我们还是需要在一个命名空间里模拟实现vector,防止和标准库里的起冲突。

namespace zh
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}

解释说明:

1.vector是一个非常通用的容器,是一个动态大小的数组,可以存储任意类型的元素,并且能够自动调整大小以适应元素的添加和删除。所以我们的模拟实现要写成类模板

2.vector可以看做顺序表的升级,但是模拟实现vector跟我们以往实现顺序表有所不同,顺序表是使用一个动态开辟的数组、数组有效元素个数size和数组容纳最大有效数据的个数capacity维护的,而模拟实现vector需要三个(模板参数)T* 类型的指针,而vector的迭代器功能恰恰又和T*类型指针类似,所以干脆把T*封装成迭代器。当然迭代器需要有两个版本,普通版本和const版本。

3.参数的含义

_start指向数组首元素,_finish指向最后一个有效元素的下一个位置, _end_of_storage指向数组空间末尾。

通过三个指针也可以模拟出size和capacity的功能。

size

返回有效数据个数的函数

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

capacity

返回数组最大容纳有效数据个数(容量大小)的函数。

size_t capacity() const
{return _end_of_storage - _start;
}

empty

判断数组是否为空,判断_start与_finish是否相等即可

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

迭代器有关函数

主要实现begin函数和end函数

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

实现默认成员函数的前置准备

reserve

用于vector数组空间不足时扩容的函数(扩容成n个空间)。

void reserve(size_t n)
{if (n > capacity())                        //n大于数组容量才扩容{size_t oldsize = size();               //用oldsize避免新_start和老_finish的问题T* tmp = new T[n];//memcpy(tmp, _start, size() * sizeof(T));  //这里是浅拷贝,如果是内置类型,没问题 //如果vector存的是自定义类型,就是大坑for (size_t i = 0; i < oldsize; ++i){tmp[i] = _start[i];}delete _start;                 //这里delete_start,_finish 和_end_of_storage是野指针//更新成员变量_start = tmp;_finish = tmp + oldsize;               _end_of_storage = tmp + n;}
}

reserve有几个问题需要注意:

1.开空间的时候要使用new而不要用malloc,因为malloc只是去开空间,不会去调用构造函数。

2.新_start和_finish的问题。

错误示范。

将原有数据拷贝到新空间后,释放了旧空间的资源,_strat指向了新的空间,但是_finish和_end_of_storage还是指向旧空间,这两个指针就变成野指针了。而最关键的是_finish不能被正确赋值。

3.memcpy浅拷贝问题

memcpy(tmp, _start, size() * sizeof(T));

memcpy是浅拷贝,如果vector存的是内置类型,那么浅拷贝就没有问题,如果存的是自定义类型,那浅拷贝就是个大坑。假如vector存的是string类型,那么扩容时,将数据从旧空间拷贝到新空间时,因为是浅拷贝,所以两个空间里的string的_str是同一个地址释放旧空间的时候就连带这把新空间的资源也释放了

这样就扩容失败了,因为你把原空间的数据丢失了,而且搞不好有可能程序还会崩溃。

要解决这个问题,我们就得手动实现深拷贝, 因为new出来的空间如果是自定义类型的话就自动调用构造函数初始化了,所以这里走的是赋值重载来实现深拷贝

push_back

用于在数组末尾尾插一个元素的函数。

void push_back(const T& x)
{//插入之前先判断空间是否足够if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//插入元素,更新_finish*_finish = x;_finish++;
}

构造函数

vector的构造函数我们实现无参构造迭代器区间构造n个val构造

无参构造

无参构造其实我们并不需要写,因为已经在成员变声明时给了缺省值,编译器自动生成的无参构造函数走初始化列表满足需求了。但是由于我们写了其他构造函数编译器就不自动生成了

这里时候可以自己写无参构造,也可以用default强制编译器生成(C++11的用法)。

//构造
/*vector()
{}*///c++11 强制生成构造
vector() = default;

迭代器区间构造

//类模板的成员函数,还可以继续是函数模版
template<class InputIerator>
vector(InputIerator first, InputIerator last)
{while (first != last){push_back(*first);	++first;}
}

这里给这个函数再套一层模板是为了让vector不仅能用vector的迭代器区间构造,还能用其他容器(list、string等)的迭代器来进行构造

这里又有个问题,就是while循环判断条件的!=不能改成<,因为<对于vector的迭代器时可以的,但是对于其他容器的迭代器,如list,last不一定比first要大

n个val来进行构造 

vector(size_t n, const T& val = T())
{//先开好空间reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}

使用的时候val可能不传参,所以要给缺省值。

因为val的类型不确定,可能是内置类型,也可能是自定义类型。

在不传参使用缺省值时

对于自定义类型,比如strng,先调用构造函数构造一个匿名对象,再拷贝构造给val。(编译器会优化,直接对val进行构造),这样val就有了缺省值

对于内置类型,本来是没有构造函数的说法的,但是为了适应这里,也支持类似类那种使用构造函数初始化的方式。

int a = int();
int b = int(2);
int c(3);
cout << a << endl;
cout << b << endl;
cout << c << endl;

析构函数

直接delete就可以了,把三个迭代器置空。

//析构
~vector()
{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
}

拷贝构造函数

先开好空间,然后尾插就可以了。

//拷贝构造
vector(const vector<T>& v)
{reserve(v.size());for (auto& e : v){push_back(e);}
}

赋值重载

首先实现一个交换函数,然后传值调用,将两个对象交换即可。

//void swap(vector& v) 可以这样写
void swap(vector<T>& v) 
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

增删查改

clear

不需要真的删除,直接将更改_finish的值即可。

void clear()
{_finish = _start;
}

resize

控制有效数据个数。

  1. 若n < size,直接将_finish更改为_start + n即可。
  2. 若_size < n < capacity或者n > capacity,直接扩容成n个空间(空间足够就不会扩容),从_finish拷贝足够数量的val即可。
void resize(size_t n, T val = T())
{if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish != _start + n)           {*_finish = val;++_finish;}}
}

pop_back

先判断数组是否为空,尾删一个元素,_finish-- 即可。

void pop_back()
{//判断下数组是否为空assert(!empty());--_finish;
}

insert

在pos位置插入一个元素。

iterator insert(iterator pos, const T& x) //pos不会为0,因为是有效的迭代器
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage)                   //涉及到扩容,pos会失效,pos指向原来的空间{size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//插入元素,更新*pos = x;++_finish;return pos;
}

注意的问题:

1.如果插入涉及到了扩容,要提前把pos相对于首元素的相对长度记录下来,扩容完毕后更新pos。因为扩容会导致pos失效。

2.插入之后要返回新元素的迭代器。(这里其实也算迭代器是失效了,因为pos指向的元素发生了更改,迭代器失效了就不要在使用了。)

erase

删除pos位置的元素,删除完后返回删除元素下一位置的迭代器

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

抛出一个问题,利用迭代器删除vector中所有的偶数。

错误做法

auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it);}it++;		
}

删完一个偶数后,it已经是下一元素的迭代器了,it不需要++了。

正确做法

auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it);}else{++it;}
}

重载[]

为了方便访问和修改数组中的元素。

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

通用打印容器函数,套一层模板即可。

注意:

​
template<class Container>
void print_Container(const Container& v)
{//typename vector<T>::const_iterator it = v.begin();   //typename标定为类型                          //从没有实例化的类模板取出来的可能是类型或者成员变量,编译器无法区分auto it = v.begin();                       while (it != v.end()){cout << *it << ' ';++it;}cout << endl;/*for (auto num : v){cout << num << ' ';}cout << endl;*/
}​

未实例化的类取出来的有可能是类型或者成员变量,要加关键字typename告诉编译器是类型不加的话会发生编译错误

当然直接用auto更方便。


拜拜,下期再见😏

摸鱼ing😴✨🎞

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

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

相关文章

git add成功后忘记commit的文件丢了?

本文目标&#xff1a;开发人员&#xff0c;在了解git fsck命令用法的条件下&#xff0c;进行git add成功但由于误操作导致丢失的文件找回&#xff0c;达到找回丢失文件的程度。 文章目录 1 痛点2 解决方案3 总结/练习 1 痛点 开发过程中&#xff0c;分支太多&#xff08;基线分…

通信工程学习:什么是MIMO多输入多输出技术

MIMO:多输入多输出技术 MIMO(Multiple-Input Multiple-Output)多输入多输出技术是一种在无线通信中广泛应用的技术,它通过利用多个天线进行数据传输和接收,可以显著提高无线通信系统的性能和容量。以下是对MIMO技术的详细解释: 一、定义与原理 MIMO技术…

铺铜修改后自动重铺

很多初学者对于敷铜操作感到比较麻烦&#xff1a;为什么每次打过孔&#xff0c;修改走线后都需要手动右击-重新修改敷铜。如何提升layout的效率&#xff1f; 版本&#xff1a;Altium Designer 21.9.2 首先&#xff0c;点击面板右边的小齿轮&#xff0c;进入设置 接下来&#…

【国庆要来了】基于Leaflet的旅游路线WebGIS可视化实践

前言 转眼2024年的国庆节马上就要来临了&#xff0c;估计很多小伙伴都计划好了旅游路线。金秋十月&#xff0c;不管是选择出门去看看风景&#xff0c;还是选择在家里看人。从自己生活惯了的城市去别人生活惯了的城市&#xff0c;去感受城市烟火、去感受人文风景&#xff0c;为2…

SpringBoot整合JPA 基础使用

一、什么是JPA ‌‌1.JPA的定义和基本概念‌‌ ‌JPA&#xff08;Java Persistence API&#xff09;‌是Java中用于进行持久化操作的一种规范&#xff0c;它定义了一系列用于操作关系型数据库的API接口。通过这些接口&#xff0c;开发人员可以方便地进行数据库的增删改查等操…

DC00021基于springboot问卷调查管理系统web项目调查问卷管理系统MySQL(附源码)

1、项目功能演示 DC00021基于springboot问卷调查管理系统web项目调查问卷管理系统MySQL 2、项目功能描述 基于springboot问卷调查管理系统包括以下功能&#xff1a; 1、系统登录、系统注册 2、创建题目、题目信息查看 3、创建问卷、我的问卷信息查看 4、创建活动、我的活动信息…

看Threejs好玩示例,学习创新与技术(ThreePipe)

下面这个示例我觉得特别棒&#xff0c;我会推荐给我们的美工&#xff0c;以后产品的宣传图用它。比如下面这个图&#xff0c;不需要PS&#xff0c;仅需拖拽一个照片进去&#xff0c;它会自动铺到笔记本电脑上。完成后点击截图就可以得到高清图片&#xff0c;不需要摆拍和PS。大…

光伏设计难点在哪儿?如何解决?

一、光伏设计的主要难点 1.技术门槛高 光伏设计领域的一大难题在于技术使用的复杂性。用户往往需要下载并安装特定的软件和控件&#xff0c;这些工具操作复杂&#xff0c;增加了学习成本和使用难度。此外&#xff0c;现有的设计工具并非专为光伏设计而生&#xff0c;组件库不…

【华为】用策略路由解决双出口运营商问题

需求描述 不同网段访问互联网资源时&#xff0c;走不同的出口&#xff0c;即PC1走电信出口&#xff0c;PC2走移动出口。 客户在内网接口下应用策略路由后往往出现无法访问内网管理地址的现象&#xff0c;该举例给出解决办法。 拓扑图 基础配置 #sysname R1 # # interface G…

Android15音频进阶之新播放器HwAudioSource(八十六)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…

亚马逊IP关联揭秘:发生ip关联如何处理

在亚马逊这一全球领先的电商平台上&#xff0c;IP关联是一个不可忽视的问题&#xff0c;尤其是对于多账号运营的卖家而言。本文将深入解析亚马逊IP关联的含义、影响以及应对策略&#xff0c;帮助卖家更好地理解和应对这一问题。 什么是亚马逊IP关联&#xff1f; 亚马逊IP关联…

Redis篇(应用案例 - 优惠卷秒杀)

目录 一、全局唯一ID 1. 简介 2. Redis实现全局唯一Id 3. 测试类 3.1. 关于 countdownlatch 3.2. CountDownLatch 中有两个最重要的方法 二、添加优惠卷 三、实现秒杀下单 四、库存超卖问题分析 六、乐观锁解决超卖问题 七、优惠券秒杀-一人一单 八、集群环境下的并…

1比25万基础电子地图(港澳版)

我们为你分享过四川、云南、江西、贵州、重庆、青海、西藏、新疆、甘肃、黑龙江、吉林、湖北、内蒙古、广东、广西、浙江、河南、湖南、宁夏、山西、陕西、天津、山东、河北、江苏、福建、辽宁、北京、安徽、上海、海南和台湾的1比25万基础电子地图&#xff0c;现在再为你分享港…

MySQL --数据类型

文章目录 1.数据类型分类2.数值类型2.1 tinyint类型2.2 bit类型2.3小数类型2.31float2.32decimal 3.字符串类型3.1 char3.2varchar3.3 char和varchar比较 4.日期和时间类型5.enum和set 1.数据类型分类 2.数值类型 2.1 tinyint类型 数值越界测试&#xff1a; create table tt1…

ubuntu内网穿透后在公网使用ssh登录

需求&#xff1a; 我有一台内网可以通过ssh 22端口访问的设备操作系统是ubuntu server我还有1台拥有公网IP的服务器&#xff0c;IP地址是 6.66.666.6666我想随时从其他网段通过ssh访问我的ubuntu server设备 实现&#xff1a; 工具准备&#xff1a;frp 网址&#xff1a;https…

Spring源码学习:SpringMVC(3)mvcannotation-driven标签解析【RequestMappingHandlerMapping生成】

目录 前言mvc:annotation-driven标签概述mvc:annotation-driven标签解析【RequestMappingHandlerMapping生成】AnnotationDrivenBeanDefinitionParser#parse &#xff08;解析入口&#xff09;RequestMappingHandlerMapping的实例化类图afterPropertiesSetAbstractHandlerMetho…

MySQL数据库——索引

目录 什么是索引&#xff08;Index&#xff09;&#xff1f; 怎样加索引&#xff1f; 索引的特点 索引类型 主键索引(Primary Key) 辅助索引&#xff08;二级索引&#xff09; 聚集索引和非聚集索引 聚集索引 非聚集索引 单列索引和联合索引 单列索引 联合索引 创…

mac Wireshark You do not have permission to capture on device “rvio“.

原因&#xff1a; 权限不足 解决方案&#xff1a; 打开终端在终端输入 whoamin (会在终端显示本机的实际用户名字) 例如&#xff1a;xiaoming进入 /dev 目录 cd /dev输入命令&#xff1a;ls -la | grep bp输入命令&#xff1a;sudo chown whoamin xiaoming:admin bp*重新打开 …

Python(五)-函数

目录 函数的定义与调用 特点 语法格式 函数的参数 函数的返回值 函数嵌套调用 变量的作用域 局部变量 全局变量 函数的多种参数 位置参数 关键字参数 默认参数 可变参数 函数的定义与调用 python函数需要使用def关键字来定义,需要先定义,后调用 特点: 先定义…

【数据结构与算法】LeetCode:二分查找

文章目录 二分查找二分查找搜索插入位置 &#xff08;Hot 100&#xff09;x 的平方根搜索二维矩阵&#xff08;Hot 100&#xff09;在排序数组中查找元素的第一个和最后一个位置 &#xff08;Hot 100&#xff09;搜索旋转排序数组 &#xff08;Hot 100&#xff09;寻找旋转排序…