【C++】STL---vector
STL---vector
- 一、vector 的介绍
- 二、vector 的模拟实现
- 1. 容量相关的接口
- (1)size
- (2)capacity
- (3)reserve
- (4)resize
- (5)empty
- 2. [] 重载
- 3. 迭代器
- 4. 修改数据相关的接口
- (1)push_back
- (2)pop_back
- (3)insert
- (4)erase
- (5)swap
- (6)clear
- 5. 构造函数
- 6. 拷贝构造函数
- 7. 赋值运算符重载
- 8. 析构函数
一、vector 的介绍
- vector 是表示可变大小数组的序列容器。
- 就像数组一样,vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自
动处理。 - 本质讲,vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小。为了增加存储空间,其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector 并不会每次都重新分配大小。
- vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。
- 因此,vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
二、vector 的模拟实现
vector 学习时一定要学会查看文档:vector文档介绍,vector 在实际中非常的重要,在实际中我们熟悉常见的接口就可以,下面我们直接开始模拟实现,在模拟实现中我们实现的是常见的接口,并且会在实现中讲解它们的使用以及注意事项。
首先我们将 vector 放到我们自己的命名空间 namespace Young
中;其次我们需要知道,在 vs2019 中,vector 的实现是用三个迭代器实现的,这三个迭代器分别指向的是:数据块的开始、有效数据的尾、存储容量的尾,它跟 string 的实现方式差不多,就是换了一种表达形式,本质上还是一样的,声明如下:
namespace Young{// 使用模板,泛型编程template <class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:// 给缺省值iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾iterator _endofstorage = nullptr; // 指向存储容量的尾};}
1. 容量相关的接口
(1)size
我们要获取到有效数据的长度,只需要用两个迭代器相减,用指向有效数据尾部的减去指向数据块开头的即可,实现如下:
// 获取有效数据长度size_t size() const{return _finish - _start;}
(2)capacity
获取容量的接口与上面的相似,如下:
// 获取容量size_t capacity() const{return _endofstorage - _start;}
(3)reserve
reserve 我们在 string 中也实现过,vector 的 reserve 与 string 的相似,当 n(需要调整的空间大小) 大于 capacity() 才进行扩容,否则不会缩容;并且它只改变 capacity 不改变 size;其实现如下:
// 申请空间void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t sz = size();if (_start){// 不能使用 memcpy 拷贝数据 --- 浅拷贝问题// memcpy(tmp, _start, sizeof(T) * sz);for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n; }}
注意,我们在进行拷贝数据的时候,不能使用 memcpy 进行拷贝,因为这会导致深层度的浅拷贝问题,例如实例化以下的对象:
我们在进行尾插的时候,由于 v 对象没有空间,所以需要开空间,我们默认是开4个空间,当我们需要插入第5个数据的时候,需要再次扩容,此时问题就体现出来了,如下图:
因为 _start 维护的数据块中,里面是 string 自定义类型,所以它里面的 _str 指针应该指向一段字符串,在我们需要扩容的时候,memcpy 会按照逐字节的方式进行拷贝,拷贝到 _tmp 中,其中 _tmp 中的 string 的 _str 也是指向原来的空间中,当我们 delete[] _start;
的时候,_start 的空间被释放掉了,而 _tmp 中还有指针指向那段被释放的空间,所以这是造成了野指针问题,所以不能用 memcpy 进行拷贝数据。
所以我们应该采用赋值的方式进行拷贝,如下,如果是像上面的自定义类型 string,它会调用它自己的赋值重载,是深拷贝,所以不会造成上面的问题;
for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}
还需要注意的是,我们在拷贝数据前,需要记录原来的长度,在拷贝完数据后,将 _tmp 重新赋给 _start 后,需要更新 _finish 和 _endofstorage,就需要用到原来的长度和容量相加了。
(4)resize
// 调整空间为 n,并初始化空间void resize(size_t n,const T& value = T()){// 如果 n 小于原来的长度,调整 _finish 的位置if (n <= size()){_finish = _start + n;}// 否则,重新开空间,并在数据的尾部插入需要初始化的值else{reserve(n);while (_finish < _start + n){*_finish = value;_finish++;}}}
(5)empty
// 判断是否空bool empty(){return _start == _finish;}
2. [] 重载
在 vector 中我们也可以实现下标的随机访问,所以我们可以重载 [] ,支持随机访问,实现如下:
非 const 对象:
// [] 重载T& operator[](size_t pos){assert(pos < size());return _start[pos];}
const 对象:
const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}
3. 迭代器
非 const 对象:
// 迭代器iterator begin(){return _start;}iterator end(){return _finish;}
const 对象:
const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}
4. 修改数据相关的接口
(1)push_back
尾插需要注意先要判断空间是否满了,满了就要扩容,如果一开始为空,我们默认开 4 个空间,实现如下:
// 尾插void push_back(const T& x){if (size() == capacity())//if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;_finish++;}
(2)pop_back
尾删只需要将 _finish 减减即可;实现如下:
// 尾删void pop_back(){assert(size() > 0);_finish--;}
(3)insert
在 pos 位置插入数据,实现如下:
// 在 pos 位置插入数据void insert(iterator pos, const T& value){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){// 记录 pos 的长度,防止开空间后迭代器失效size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 开好空间后 _start 重新加上 len ,使 pos 回到原来相对 _start 的位置pos = _start + len;}// 挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}// 插入数据*pos = value;_finish++;}
在实现 insert 的时候需要注意迭代器失效的问题,在上面实现的代码中,如果不记录 pos 的长度,然后空间不够需要扩容的时候, pos 还留在原来的空间中,而原来的空间已经被释放了,所以会导致野指针问题;所以我们为了避免这种问题,在扩容之前需要记录 pos 的长度,开好空间后更新 pos 的位置,让它回到新的空间相对原来空间的位置中。这是第一种迭代器失效的问题。
insert 的使用如下图:
(4)erase
删除在 pos 位置的数据,实现如下:
// 删除 pos 位置的数据iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);// 挪动数据iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;end++;}// 有效数据减一_finish--;return pos;}
erase 的使用和实现会面临迭代器失效的第二种情况,例如我们就以以上的数据为例,假设我们需要删除数据中的偶数,例如下图:
结果没有把偶数完全删除,这是为什么呢?这是因为删除了第一个 2 后,it 指向原来的第二个 2,再 ++ 后,就错过了第二个 2;后面的 6 也同理,例如下图:
找到偶数:
删除第一个 2 后:
it ++ 后:
这种迭代器失效的情况还可能面临程序崩溃的问题,如果上面的数据中最后只有一个 6 ,it 就会因为与 v1.end() 错过一个位置导致程序崩溃,例如下图:
找到 6 后:
删除后:
it ++ 后:
如上图,这种情况 it 永远都不会等于 v1.end() 所以程序会死循环。
解决方案是什么呢?解决方案就是我们在实现 erase 的时候,需要返回被删除后当前 pos 的位置,例如上面的实现中;而在使用的时候,我们在 erase 之后用 it 接收这个位置,并且不再访问这个位置,例如下图:
这段代码中,我们 erase 后,没有访问当前位置,而是在没有 erase 的时候去访问 it;
if (*it % 2 == 0){it = v1.erase(it);}else{it++;}
结论: insert 和 erase 以后迭代器都失效了,不能再访问。
(5)swap
我们利用标准库的 swap 函数实现我们 vector 中的 swap 函数,实现如下:
// 交换void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}
(6)clear
清空数据,只需要将 _finish 变成 _start 即可,实现如下:
// 清空数据void clear(){_finish = _start;}
5. 构造函数
因为我们在声明处给了缺省值,所以无参的构造函数写成以下形式即可,因为缺省值最终也会走初始化列表:
// 构造函数vector(){}
我们再重载其它形式的构造函数,例如 vector<int> v(10,0)
,开 10 个空间并将它们初始化为 0,实现如下:
// vector<int> v(10,0);vector(int n, const T& value = T()){reserve(n);for (int i = 0; i < n; i++){push_back(value);}}
我们在缺省值中给了一个匿名对象,因为我们不知道 T 的类型是什么,所以我们在缺省值中需要给一个匿名对象;如果是内置类型,它会初始化为 nullptr 或 0,我们以前了解到的是编译器不会对内置类型进行处理,但是匿名对象会对它进行处理;注意在类型前要加 const 因为匿名对象具有常性。如果是自定义类型,它会去调自己的构造函数给缺省值。
使用如下图:
我们还需要重载一个构造的形式,用迭代器区间进行构造,实现如下:
// vector<string> v(str.begin(),str.end());template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}
我们在类模板内再使用了函数模板,这个函数模板的生命周期只在这个构造函数内;使用如下:
6. 拷贝构造函数
拷贝构造函数只需要申请和形参对象一样大的空间,然后插入数据即可,实现如下:
// 拷贝构造函数 -- v2(v1);vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}
7. 赋值运算符重载
赋值运算符重载我们利用传参拷贝 tmp,然后将 *this 的数据和 tmp 进行交换即可,最后返回 *this,tmp 出了作用域会自动调用析构函数;实现如下:
// 赋值运算符重载 -- v2 = v1;vector<T>& operator=(vector<T> tmp){swap(tmp);return *this;}
8. 析构函数
析构函数只需要释放 _start 的空间即可,因为 _start, _finish, _endofstorage 实际上都是同一块空间,只是它们所在的位置不一样,实现如下:
// 析构函数~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}
相关文章:

【C++】STL---vector
STL---vector 一、vector 的介绍二、vector 的模拟实现1. 容量相关的接口(1)size(2)capacity(3)reserve(4)resize(5)empty 2. [] 重载3. 迭代器4. 修改数据相…...

机器学习:基本介绍
机器学习介绍 Hnad-crafted rules Hand-crafted rules,叫做人设定的规则。那假设今天要设计一个机器人,可以帮忙打开或关掉音乐,那做法可能是这样: 设立一条规则,就是写一段程序。如果输入的句子里面看到**“turn of…...
基于长短期神经网络LSTM的碳排量预测,基于LSTM的碳排放量预测
目录 背影 摘要 LSTM的基本定义 LSTM实现的步骤 基于长短期神经网络LSTM的碳排放量预测 完整代码: 基于长短期神经网络LSTM的碳排放量预测,基于LSTM的碳排放量预测资源-CSDN文库 https://download.csdn.net/download/abc991835105/88184632 效果图 结果分析 展望 参考论文 背…...

日常BUG——SpringBoot关于父子工程依赖问题
😜作 者:是江迪呀✒️本文关键词:日常BUG、BUG、问题分析☀️每日 一言 :存在错误说明你在进步! 一、问题描述 在父子工程A和B中。A依赖于B,但是A中却无法引入B中的依赖,具体出现的…...

Zabbix监控tomcat
文章目录 一、安装部署TomcatTomcat二、安装Tomcat1.安装zabbix-agent收集监控数据(192.168.40.104)2.安装部署Zabbix-server(192.168.40.105)3.配置数据库 三、Zabbix监控Tomcat页面设置 实验环境 主机用途Centos7:192.168.40.105zabbix-server,zabbix-java-gatew…...

CentOS-6.3安装MySQL集群
安装要求 安装环境:CentOS-6.3 安装方式:源码编译安装 软件名称:mysql-cluster-gpl-7.2.6-linux2.6-x86_64.tar.gz 下载地址:http://mysql.mirror.kangaroot.net/Downloads/ 软件安装位置:/usr/local/mysql 数据存放位…...

项目管理的艺术:掌握成本效益分析
引言 在项目管理中,我们经常面临着如何有效地使用有限的资源来实现项目目标的挑战。为了解决这个问题,我们需要使用一种强大的工具——成本效益分析。通过成本效益分析,我们可以评估和比较不同的项目选项,选择最具成本效益的项目…...

护眼灯值不值得买?什么护眼灯对眼睛好
想要选好护眼台灯首先我们要知道什么是护眼台灯,大的方向来看,护眼台灯就是可以保护视力的台灯,深入些讲就是具备让灯发出接近自然光特性的光线,同时光线不会伤害人眼而出现造成眼部不适甚至是视力降低的照明设备。 从细节上看就…...

【设备树笔记整理4】内核对设备树的处理
1 从源头分析_内核head.S对dtb的简单处理 1.1 bootloader向内核传递的参数 (1)bootloader启动内核时,会设置r0,r1,r2三个寄存器: r0一般设置为0;r1一般设置为machine_id (在使用设备树时该参数没有被使用…...

算法通关村第七关——递归和迭代实现二叉树前中后序遍历
1.递归 1.1 熟悉递归 所有的递归有两个基本特征: 执行时范围不断缩小,这样才能触底反弹。终止判断在调用递归的前面。 写递归的步骤: 从小到大递推。分情况讨论,明确结束条件。组合出完整方法。想验证就从大到小画图推演。 …...

Datawhale Django后端开发入门Task01 Vscode配置环境
首先呢放一张运行成功的截图纪念一下,感谢众多小伙伴的帮助呀,之前没有配置这方面的经验 ,但还是一步一步配置成功了,所以在此以一个纯小白的经验分享如何配置成功。 1.选择要建立项目的文件夹,打开文件找到目标文件夹…...
django部署到centos服务器上
具体的操作步骤 步骤一 更新系统和安装依赖, sudo yum update sudo yum install python3 python3-pip python3-devel git步骤二:创建并激活虚拟环境 在终端中执行以下命令: python3 -m venv myenv source myenv/bin/activate可以不创建虚拟…...

IOS开发-XCode14介绍与入门
IOS开发-XCode14介绍与入门 1. XCODE14的小吐槽2. XCODE的功能bar一览3. XCODE项目配置一览4. XCODE更改DEBUG/RELEASE模式5. XCODE单元测试 1. XCODE14的小吐槽 iOS开发工具一直有个毛病,就是新版本的开发工具的总会有一些奇奇怪怪的bug。比如在我的Mac-Pro&#…...
Interactive Marker Publish Pose All the Time (Interactive Marker通过topic一直发送其状态)
以下代码实现了:Interactive Marker通过topic一直发送其状态,而不只是交互时才发送。 几个要点: 通过定时器rospy.Timer实现PublishInteractiveMarkerServer feedback.pose的类型是geometry_msgs/Pose,而不是geometry_msgs/PoseS…...

前后端分离------后端创建笔记(04)前后端对接
本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论,如有侵权请联系 源码:https://gitee.com/green_vegetables/x-admin-project.git 素材:https://pan.baidu.com/s/…...

一站式自动化测试平台-Autotestplat
3.1 自动化平台开发方案 3.1.1 功能需求 3.1.3 开发时间计划 如果是刚入门、但有一点代码基础的测试人员,大概 3 个月能做出演示版(Demo)进行自动化测试,6 个月内胜任开展工作中项目的自动化测试。 如果是有自动化测试基础的测试人员,大概 …...
Ansible Service模块,使用 Ansible Service模块进行服务管理
Ansible 是一种自动化工具,它可以简化配置管理、应用程序部署和任务自动化等操作。Ansible 的 Service 模块是其中一个重要的模块,它提供了管理服务的功能,使得在远程主机上启动、停止、重启和重新加载服务变得简单和可靠。本文将介绍 Ansibl…...

共识算法初探
共识机制的背景 加密货币都是去中心化的,去中心化的基础就是P2P节点众多,那么如何吸引用户加入网络成为节点,有那些激励机制?同时,开发的重点是让多个节点维护一个数据库,那么如何决定哪个节点写入&#x…...
Oracle查询表字段名并拼接
在数据库使用中,我们常常需要,获取一张表的全部字段,那该如何查询呢? 查询表字段名 SELECT column_name FROM all_tab_columns WHERE table_name table_name; 只需将引号中的table_name,替换为自己的表名࿰…...

8 张图 | 剖析 Eureka 的首次同步注册表
注册表对于注册中心尤为重要,所有的功能都是围绕这个注册表展开。比如服务 A 要想访问服务 B,就得知道服务 B 的 IP 地址和端口号吧。如下图所示,传统的方式就是服务 A 知道了服务 B 的地址后,发送 HTTP 请求到对应的 API 地址上。…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...