C++【STL容器系列(二)】vector的模拟实现

文章目录

  • 1. vector的结构
  • 2. vector的默认成员函数
    • 2.1构造函数
      • 2.1.1 默认构造
      • 2.1.2 迭代器构造
      • 2.1.3 用n个val初始化构造
    • 2.2 拷贝构造
    • 2.3 析构函数
    • 2.4 operator=
  • 3. vector iterator函数
    • 3.1 begin 和 cbegin函数
    • 3.2 end() 和 cend()函数
  • 4. vector的小函数
    • 4.1 size函数
    • 4.2 capacity函数
    • 4.3 swap函数
  • 5. vector的访问函数
    • 5.1 operator[]
    • 5.2 front
    • 5.2 back
  • 6 vector的增删改
    • 6.1 reserve
    • 6.2 push_back(尾插)
    • 6.3 pop_back(尾删)
    • 6.4 insert(在任意位置插入数据)
    • 6.5 erase(删除数据)
    • 6.6 clear(清除数据)
    • 6.7 empty(判空)
  • 结语

1. vector的结构

我们通过查看vector的底层,能发现结构并不是我们所想的数组,_size,_capacity
在这里插入图片描述
而是由三个指针所组成的,这三个指针分别为;start:开始元素finish:末尾元素的后一个位置end_of_storage:容量(iterator是在前面typedef过的,原型为 T*)

我们前面讲结构,vector是通过模板来实现的,但模板并不能分离成两个文件,所以本次模拟只会有"vector.h"这个头文件。

由于我们是模拟实现,那么底层结构也要是三个指针

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

2. vector的默认成员函数

2.1构造函数

		vector(); //默认构造template <class InputIterator>vector(InputIterator first, InputIterator last) //迭代器构造vector(size_t n, const T& x = T()) //用n个val初始化构造

2.1.1 默认构造

其实默认构造我们并不需要写,因为我们在声明成员变量的时候就已经给了缺省值nullptr,所以写不写都无所谓,用编译器生成的默认构造就可以了,但是如果你写了一个构造函数,那么编译器就不会生成默认构造,这时候就可以使用C++11引入的default,来让编译器生成默认构造。

vector() = default; //让编译器生成默认构造

2.1.2 迭代器构造

这里其实并不复杂,push_back迭代器所代表的内容就好了。

但问题是:为什么要在多写一个模板出来呢?

答案也很简单,因为外面的模板和这个模板推导的类型不一样,外面是推导vector<T>中的T,这个是推导整个vector<T>

拿vector<int>举例,外面模板类型是 int,里面模板类型是vector<int>

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

2.1.3 用n个val初始化构造

这个也很简单,用一个循环来push_back(val)就好了

		vector(size_t n, const T& x = T()){reserve(n); for (size_t i = 0; i < n; ++i){push_back(x);}}
  • reserve: 用于提前开好空间,这样就不用反复扩容了

但是这样写会有问题,我使用其他类型不会报错,但是使用vector<int>(n,val)的时候就会报错!!!
在这里插入图片描述
竟然调用了迭代器初始化的构造函数,这是为什么呢,原因就在模板这里。

调用函数的机制是只要类型匹配,怎么简单怎么来,由于用n个val初始化是模板参数(const T& x = T()),迭代器构造也是模板参数,但是用n个val初始化还有一个 size_t 的参数,那么模板推导后有需要类型转换,势必会麻烦一点,而迭代器是两个模板参数,推导完可以直接使用,所以编译器在调用函数的时候,自然而然就会调用迭代器版本的构造。

解决方法也很简单,就是自己再写一个 int 版本的构造(其实库里也是这样实现的)
在这里插入图片描述

所以我们实现的时候,要多实现一个整形版本的构造(这里只实现了int版本的)

		vector(size_t n, const T& x = T()){reserve(n);for (size_t i = 0; i < n; ++i){push_back(x);}}vector(int n, const T& x = T()){reserve(n);for (size_t i = 0; i < n; ++i){push_back(x);}}

2.2 拷贝构造

拷贝构造其实也很简单,不需要局限于传统写法和现代写法,使用范围for就能完美解决拷贝构造。

vector(const vector& v)
{reserve(v.capacity());for (auto& e : v){push_back(e);}
}

2.3 析构函数

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

2.4 operator=

在完成operator=的时候,现代写法是目前最好的写法;传参的时候不传引用,而是传值传参(这样会进行拷贝构造,而拷贝构造就完成了形参的深拷贝),然后再交换形参。

vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

3. vector iterator函数

还是和string一样,只实现iteratorconst_iterator这两个版本

		typedef T* iterator;typedef const T* const_iterator;

3.1 begin 和 cbegin函数

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

3.2 end() 和 cend()函数

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

4. vector的小函数

4.1 size函数

左闭右开,一减就是元素个数

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

4.2 capacity函数

只是被减数的不同,返回容量数

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

4.3 swap函数

调用库里面的swap函数,避免进行深拷贝。

		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}

5. vector的访问函数

5.1 operator[]

直接返回该位置元素的引用

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

5.2 front

返回第一个元素的引用

		T& front(){return *_start;}const T& front() const{return *_start;}

5.2 back

返回最后一个元素的引用

		T& back() {return *(_finish - 1);}const T& back() const{return *(_finish - 1);}

6 vector的增删改

6.1 reserve

void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];memcpy(tmp, _start, size() * sizeof(T));delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

注意:我们需要在delete[] _start之前存储原本的数据个数,不然会出问题。
如果我们delete在刷新_finish,那么就会用到size()这个函数,但size这个函数是_start - _finish,由于_start这整个空间已经被释放了(_star、_finish、_end_of_storage是一块空间的不同位置),所以我们需要在释放_start之前先存储原来的数据个数size_t old_size = size();,再用old_size来刷新_finish。

但是这会有一个问题,如果我的vector是自定义类型且内部自己有申请空间,这时候扩容就会出问题,因为memcpy是浅拷贝,也就是一个字节一个字节的拷贝,这样虽然tmp是new出来的新空间,但是经过memcpy,_start 和 tmp就会指向同一个空间,delete[] _start,就相当于把tmp也delete了,这时候在尾插,析构的时候程序就会崩溃掉。
在这里插入图片描述
解决方法就是调用operator=,这样自定义类型就会调用自己的operator=。

		void reserve(size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//浅拷贝 -> 遇到有申请空间的对象出问题//memcpy(tmp, _start, size() * sizeof(T));//深拷贝 调用他们自己的 operator=for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}}

6.2 push_back(尾插)

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

6.3 pop_back(尾删)

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

6.4 insert(在任意位置插入数据)

iterator insert(iterator pos, const T& val)
{assert(pos >= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (pos <= end){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

这里和扩容后的size同理,括完容后要更新pos,要提前记录pos与_start之间的距离。
使用完insert会造成迭代器失效,解决方法:使用完这个函数后,认为这个迭代器已经失效,不要使用

6.5 erase(删除数据)

iterator erase(iterator pos)
{assert(pos <= end());if (pos == end()){pop_back();}else{iterator end = pos;while (end != _finish){*end = *(end + 1);++end;}--_finish;}return pos;
}

用后面的数据覆盖前面就好了。
这个函数同样会造成迭代器失效,解决方法和 insert 一样。

6.6 clear(清除数据)

void clear()
{_finish = _start;
}

6.7 empty(判空)

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

结语

那么这次的分享就到这里结束了~
最后感谢您能阅读完此片文章~
如果您认为这篇文章对你有帮助的话,可以用你们的手点一个免费的赞并收藏起来哟~
如果有任何建议或纠正欢迎在评论区留言~
也可以前往我的主页看更多好文哦(点击此处跳转到主页)。

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

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

相关文章

边缘检测的100种方法

文章目录 什么是边缘检测 ?一、边缘检测算子&#xff1a;Sobel算子、Scharr算子、Laplacian算子、Canny算子二、梯度计算 顶帽 黑帽 拉普拉斯金字塔三、相位一致性&#xff08;Phase Congruency&#xff0c;PC&#xff09;3.1、底层代码&#xff08;2D&#xff09;3.2、ski…

【Linux探索学习】第十二弹——初识进程:进程的定义、描述和一些简单的相关操作

Linux学习笔记&#xff1a; https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 在前面经过那么多篇的铺垫后&#xff0c;今天我们正式进入Linux学习的第一个重难点——进程&#xff0c;理解进程对于我们学习操作系统的其…

Java项目实战II基于微信小程序的订餐系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导 一、前言 随着移动互联网技术的飞速发展&#xff0…

触想染织厂MES产线终端工位机,打造数字化高效车间

一、行业发展背景 在纺织细分领域中&#xff0c;印染行业一直是整个产业链的效率短板&#xff0c;因其涉及染色、定型及后整理加工等多个复杂工艺、上百个参数变量&#xff0c;质量波动较大&#xff0c;依赖个人经验和手工操作&#xff0c;常常陷入高成本、低效率发展困境。 △…

CSS查缺补漏 two

11.6~11.11查缺补漏 一、熟记1.结构伪类选择器2.伪元素选择器3.盒子模型4.居中对齐&#xff08;重中之重&#xff01;&#xff01;&#xff01;&#xff09;5.清除默认样式6.元素溢出&#xff08;滚动条&#xff09;7.行内元素 – 内外边距问题8.圆角9 .盒子阴影&#xff08;拓…

Taro React-Native IOS 打包发布

http网络请求不到 配置 fix react-native facebook::flipper::SocketCertificateProvider‘ (aka ‘int‘) is not a function or func_rn运行debug提示flipper-CSDN博客 Xcode 15&#xff08;iOS17&#xff09;编译适配报错_no template named function in namespace std-CS…

本地搭建你的私有网盘:在Ubuntu上使用Portainer CE安装NextCloud

文章目录 前言1. 在PortainerCE中创建NextCloud容器2. 公网远程访问本地NextCloud容器2.1 内网穿透工具安装3.2 创建远程连接公网地址 3. 固定NextCloud私有云盘公网地址 前言 本篇文章介绍如何在本地使用Portainer CE可视化图形界面创建NextCloud私有网盘容器&#xff0c;并结…

超好用shell脚本NuShell mac安装

利用管道控制任意系统 Nu 可以在 Linux、macOS 和 Windows 上运行。一次学习&#xff0c;处处可用。 一切皆数据 Nu 管道使用结构化数据&#xff0c;你可以用同样的方式安全地选择&#xff0c;过滤和排序。停止解析字符串&#xff0c;开始解决问题。 强大的插件系统 具备强…

游戏引擎中LOD渲染技术

一.LOD(Level Of Detail) 为了降低GPU渲染压力,根据摄像机距离模型距离将面数较高的模型替换为面数较低的模型. LOD LOD0(distance<10) LOD1(distance<20) LOD2(distance<30) 故通常引擎中MetaMesh是由一个或多个LOD模型构成. MetaMesh mesh mesh.lod1 mesh.lod…

web前端动画按钮(附源代码)

效果图 源代码 HTML部分 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> …

昇思大模型平台打卡体验活动:项目5基于MindSpore实现Transformer机器翻译

首先仍然是先登录大模型体验平台 https://xihe.mindspore.cn/my/clouddev 启动&#xff01;&#xff01; 进入环境之后&#xff0c;即可开始运行notebook&#xff0c; Transformer 模型与实现 Transformer 是一种由 Vaswani 等人在 2017 年提出的神经网络结构&#xff08;论文…

‌关于人工智能(AI)的发展现状和未来趋势的详细分析!

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日将继续分享关于‌人工智能&#xff08;AI&#x…

提高排名的有效策略与实践指南

内容概要 在现代数字化时代&#xff0c;提高排名不仅是企业营销的关键&#xff0c;更是提升品牌知名度和客户粘性的有效途径。为了更好地理解这一主题&#xff0c;我们从多个方面进行详细分析。首先&#xff0c;明确"排名"的基本概念是非常重要的&#xff0c;它通常…

【Linux】动静态库

目录 1、制作静态库 2、站在使用者角度使用库 3、制作动态库 4、动态库是怎么被加载的 1、制作静态库 之前对动静态库的认识&#xff1a; libXXX.a-----静态库 静态链接&#xff1a;将库当中的代码拷贝到最终的可执行程序里&#xff0c;也就是&#xff0c;自己的源代码会变成…

AI绘画到底怎么画,才能出好图!一文详解

前言 在当今数字化的时代&#xff0c;AI 绘画以其强大的创造力和便捷性&#xff0c;成为了众多艺术爱好者和创作者的新宠。无论是专业画家想要拓展创作思路&#xff0c;还是业余爱好者渴望展现自己的创意&#xff0c;AI 绘画都提供了无限的可能。那么&#xff0c;究竟如何才能…

【React】深入理解 JSX语法

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 深入理解 JSX语法1. JSX 简介2. JSX 的基本语法2.1 基本结构2.2 与普通 JavaScr…

Kafka-Eagle 监控 搭建

Kafka-Eagle 框架可以监控 Kafka 集群的整体运行情况&#xff0c;在生产环境中经常使用。 在生产过程中&#xff0c;想创建topic、查看所有topic、想查看某个topic 想查看分区等&#xff0c;都需要写命令&#xff0c;能不能有一个图形化的界面&#xff0c;让我们操作呢&#x…

5位机械工程师如何共享一台工作站的算力?

在现代化的工程领域中&#xff0c;算力已成为推动创新与技术进步的关键因素之一。对于机械工程师而言&#xff0c;强大的计算资源意味着能够更快地进行复杂设计、模拟分析以及优化工作&#xff0c;从而明显提升工作效率与项目质量。然而&#xff0c;资源总是有限的&#xff0c;…

显示器接口种类 | 附图片

显示器接口类型主要包括VGA、DVI、HDMI、DP和USB Type-C等。 VGA、DVI、HDMI、DP和USB Type-C 1. 观察 VGA接口:15针 DP接口&#xff1a;在DP接口旁&#xff0c;都有一个“D”型的标志。 电脑主机&#xff1a;DP(D) 显示器&#xff1a;VGA(15针) Ref https://cloud.tenc…

C++常见概念问题(3)

C常见概念问题&#xff08;3&#xff09; 1. 构造函数的初始化顺序 基类构造函数&#xff1a;在派生类的构造函数中&#xff0c;基类的构造函数在派生类构造函数体执行之前调用。 成员变量初始化&#xff1a;类中的成员变量会按照其在类中声明的顺序进行初始化&#xff0c;而…