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
相关文章:
C++初阶 | [八] (下) vector 模拟实现
摘要:vector 模拟实现讲解(附代码示例),隐藏的浅拷贝,迭代器失效 在进行 vector 的模拟实现之前,我们先粗略浏览一下 stl_vector.h 文件中的源码来确定模拟实现的大体框架。 这里提供一些粗略浏览源码的技巧…...
信息安全计划
任何管理人员或人力资源专业人士都知道,除非彻底记录标准和实践,否则永远无法真正实施和执行标准和实践。正如您可能想象的那样,在保护您的网络、技术和数据系统免受网络威胁以及在发生这些事件时规划最及时、高效和有效的响应时,…...
【更新完毕】2024牛客寒假算法基础集训营6 题解 | JorbanS
文章目录 [A - 宇宙的终结](https://ac.nowcoder.com/acm/contest/67746/A)[B - 爱恨的纠葛](https://ac.nowcoder.com/acm/contest/67746/B)[C - 心绪的解剖](https://ac.nowcoder.com/acm/contest/67746/C)[D - 友谊的套路](https://ac.nowcoder.com/acm/contest/67746/D)[E …...
FL Studio All Plugins Edition2024中文完整版Win/Mac
FL Studio All Plugins Edition,常被誉为数字音频工作站(DAW)的佼佼者,是音乐制作人和声音工程师钟爱的工具。它集音频录制、编辑、混音以及MIDI制作为一体,为用户提供了从创作到最终作品输出的完整工作流程。这个版本…...
神经网络系列---归一化
文章目录 归一化批量归一化预测阶段 测试阶段γ和β(注意)举例 层归一化前向传播反向传播 归一化 批量归一化 (Batch Normalization)在训练过程中的数学公式可以概括如下: 给定一个小批量数据 B { x 1 , x 2 , … …...
2023 龙蜥操作系统大会演讲实录:《兼容龙蜥的云原生大模型数据计算系统——πDataCS》
本文主要分三部分内容:第一部分介绍拓数派公司,第二部分介绍 πDataCS 产品,最后介绍 πDataCS 与龙蜥在生态上的合作。 杭州拓数派科技发展有限公司(简称“拓数派”,英文名称“OpenPie”)是国内基础数据计…...
【Vue渗透】Vue站点渗透思路
原文地址 极核GetShell 前言 本文经验适用于前端用Webpack打包的Vue站点,阅读完本文,可以识别出Webpack打包的Vue站点,同时可以发现该Vue站点的路由。 成果而言:可能可以发现未授权访问。 识别Vue 识别出Webpack打包的Vue站…...
主数据管理是数字化转型成功的基石——江淮汽车案例分享
汽车行业数字化转型的背景 在新冠疫情导火索的影响下,经济全球化政治基础逐渐动摇。作为全球最大的汽车市场,我国的汽车市场逐渐由增量转为存量市场。 在数字化改革大背景下,随着工业4.0时代的到来,江淮汽车集团力争实现十四五数…...
【Spring连载】使用Spring Data访问 MongoDB(十一)----加密Encryption (CSFLE)
[TOC](【Spring连载】使用Spring Data访问 MongoDB(十一)----加密Encryption (CSFLE)) 一级目录 二级目录 三级目录...
【postgresql】数据表id自增与python sqlachemy结合实例
需求: postgresql实现一个建表语句,表名:student,字段id,name,age, 要求:每次添加一个数据id会自动增加1 在PostgreSQL中,您可以使用SERIAL或BIGSERIAL数据类型来自动生成主键ID。以下是一个创建名为stude…...
什么是索引?在 MySQL 中有哪些类型的索引?它们各自的优势和劣势是什么?
什么是索引?在 MySQL 中有哪些类型的索引?它们各自的优势和劣势是什么? 索引是数据库中用于帮助快速查询数据的一种数据结构。在 MySQL 中,索引可以显著提高查询性能,因为它允许数据库系统不必扫描整个表来找到相关数据…...
Docker安装与基础知识
目录 -----------------Docker 概述--------------------------- 容器化越来越受欢迎,因为容器是: Docker与虚拟机的区别: Docker核心概念: ●镜像 ●容器 ●仓库 -----------------安装 Docker--------------------------…...
搭建Facebook直播网络对IP有要求吗?
在当今数字化时代,Facebook直播已经成为了一种极具吸引力的社交形式,为个人和企业提供了与观众直接互动的机会,成为推广产品、分享经验、建立品牌形象的重要途径。然而,对于许多人来说,搭建一个稳定、高质量的Facebook…...
Qt开发:MAC安装qt、qtcreate(配置桌面应用开发环境)
安装qt-creator brew install qt-creator安装qt brew install qt查看qt安装路径 brew info qtzhbbindembp ~ % brew info qt > qt: stable 6.6.1 (bottled), HEAD Cross-platform application and UI framework https://www.qt.io/ /opt/homebrew/Cellar/qt/6…...
python学习网站
Python系列干货之——Python与设计模式 - 知乎 Python之23种设计模式_23种设计模式 python-CSDN博客 用python实现设计模式 — python-golang-web-guide 0.1 文档 python设计模式_Python六大原则,23种设计模式 - 掘金 Python 常用设计模式 Python入门 类class提…...
编程笔记 Golang基础 033 反射的类型与种类
编程笔记 Golang基础 033 反射的类型与种类 一、反射的类型和种类二、切片与反射三、集合与反射四、结构体与反射五、指针与反射六、函数与反射小结 反射机制的作用范围涵盖了几乎所有的类型和值的操作层面,它极大地增强了Go语言在运行时对于自身类型系统的探索和操…...
MySQL进阶篇2-索引的创建和使用以及SQL的性能优化
索引 mkdir mysql tar -xvf mysqlxxxxx.tar -c myql cd mysql rpm -ivh .....rpm yum install openssl-devel systemctl start mysqld gerp temporary password /var/log/mysqld.log mysql -u root -p mysql> show variables like validate_password.% set glob…...
基于SVM的功率分类,基于支持向量机SVM的功率分类识别,Libsvm工具箱详解
目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 完整代码和数据下载链接:基于SVM的功率分类,基于支持向量机SVM的功率分类识别资源-CSDN文库 https://download.csdn.net/download/abc991835105/88862836 SVM应用实例, 基于…...
【IO流】FileWrite字符输出流
FileWrite字符输出流 1. 概述2. 作用3. 方法4. 细节5. 代码示例6. 注意事项 1. 概述 java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。 FileWriter 是用于写入字符数据到文件的字符输出流。 2. 作用 写入字符数据:…...
WPF 【十月的寒流】学习笔记(1):DataGrid过滤
文章目录 相关链接代码仓库前言环境DataGrid 数据筛选项目配置使用原理主要代码(详细代码可以看我的GitHub仓库)Models.PersonDataGirdViewDataGridViewModel 实现效果 DataGrid直接绑定CollectionViewxamlViewModel 总结 相关链接 十月的寒流 在 WPF 中…...
台达伺服PR模式调试避坑指南:从参数配置到故障排查(AL.013/AL.30报警解决)
台达伺服PR模式实战调试手册:参数配置与故障排查全解析 在工业自动化现场调试中,台达B3系列伺服驱动器的PR模式因其灵活的定位控制特性,成为许多设备制造商的首选方案。但实际应用中,工程师们常被电子齿轮比设置、软极限配置、报警…...
保姆级避坑指南:在ROS Noetic上搞定aruco_ros编译与单目相机定位(解决CV_FILLED报错)
ROS Noetic实战:从CV_FILLED报错到单目ARUCO定位全流程解析 刚接触ROS的开发者经常会遇到一个尴尬场景:按照网上教程一步步操作,却在编译阶段卡在某个看似简单的报错上。最近在Noetic环境下配置aruco_ros时,我就被CV_FILLED这个错…...
大模型微调面试100问,非常详细收藏我这一篇就好了!
本文系统梳理了LoRA权重更新梯度反向传播公式推导,解释了固定只训练和能显著减少显存占用的原因。深入解析了QLoRA中NF4量化原理及其利用高斯分布优化量化区间的机制。详细阐述了Double Quantization在QLoRA中的实现步骤及其显存节省效果。推导了DPO损失函数从RLHF目…...
别再只会AT指令了!用STM32CubeMX+正点原子LoRa模块,5分钟搞定透明传输
STM32CubeMX与正点原子LoRa模块的高效开发指南 在嵌入式开发领域,LoRa技术因其长距离、低功耗的特性广受欢迎,但传统的AT指令配置方式往往让开发者陷入繁琐的底层调试中。本文将带你体验STM32CubeMX图形化工具与正点原子LoRa模块的完美结合,告…...
告别鬼影!用PyTorch复现动态场景HDR融合论文,手把手教你搞定多曝光图像对齐与融合
动态场景HDR融合实战:PyTorch实现多曝光图像对齐与去鬼影技术 在数字摄影领域,高动态范围(HDR)成像技术一直是突破相机硬件限制的重要手段。当面对阳光直射的窗户与昏暗室内共存的场景时,单张照片往往难以同时保留亮部和暗部细节。传统解决方…...
C/C++面试八股文精讲:从指针到网络编程的实战要点
1. 指针:C/C的灵魂操作 指针是C/C最核心也最让初学者头疼的概念。我当年第一次接触指针时,完全不明白这个"地址"到底有什么用。直到后来做图像处理项目时,需要直接操作内存中的像素数据,才真正体会到指针的强大。 指针变…...
ELK全家桶HTTPS安全通信保姆级配置:从单机到集群的证书管理与避坑指南
ELK全栈HTTPS安全通信实战:从证书签发到集群化管理的完整解决方案 在分布式日志分析领域,ELK(Elasticsearch Logstash Kibana)技术栈已成为事实上的行业标准。随着企业安全合规要求的不断提高,为ELK全组件配置HTTPS加…...
RLS vs. LMS:自适应滤波两大算法到底怎么选?从原理到应用场景全解析
RLS vs. LMS:自适应滤波两大算法深度对比与工程选型指南 在语音降噪耳机消除环境杂音时,在视频会议系统抑制回声干扰时,在移动通信对抗多径效应时——这些场景背后都活跃着自适应滤波算法的身影。作为数字信号处理的基石技术,递归…...
用Python搞定电力系统潮流计算?手把手教你用PyPower跑通第一个OPF算例
从零开始用Python实现电力系统潮流计算:PyPower实战指南 电力系统潮流计算是电力工程师的必备技能之一。想象一下,你刚接手一个电网优化项目,领导要求你快速评估不同运行方案下的电压分布和功率流动。传统方法可能需要依赖商业软件ÿ…...
避开这些坑!用STC15单片机做超声波测距时,定时器溢出和温度补偿到底该怎么处理?
STC15单片机超声波测距实战:定时器溢出与温度补偿的深度优化方案 当超声波测距模块在STC15F2K60S2单片机上运行时,你是否遇到过这些情况:测量数据偶尔出现异常跳变,远距离测量结果不稳定,甚至程序莫名其妙进入死循环&a…...
