C++必修:STL之vector的模拟实现
✨✨ 欢迎大家来到贝蒂大讲堂✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:C++学习
贝蒂的主页:Betty’s blog
为了让我们更加深入理解vector,接下来我们将模拟实现一个·简易版的vector。而为了和STL库中的vecotr以示区分,我们将使用命名空间namespace对其封装。
1. vector的成员变量
vector的底层其实就是我们之前在数据结构学习的顺序表,但是与顺序表不同的是vector的成员变量是三个迭代器,也可以说是三个指针。
下面是vector的成员变量:
namespace betty
{template<class T>class vector {public://...private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
其中start指向起始位置,_finish指向有效数据末尾的后一个位置,最后_end_of_storage指向容量大小末尾的后一个位置。

2. vector的成员函数
在知道vector的成员变量之后,接下来我们将探究vector的成员函数,而常见成员函数的用法我们早在之前就已经介绍过了 ,下面我们将来具体实现一下:
2.1. vector的迭代器
首先我们来模拟实现一下迭代器iterator,而在vector中迭代器iterator与string中的迭代器类似就是一个指针。所以我们直接使用typedef实现
typedef char* iterator;//普通迭代器
typedef const char* const_iterator;//const迭代器
接下来我们来实现begin()与end(),其中begin()指向的是数组的起始位置即_start,而end指向有效长度最后的下一位即_finish的位置。
iterator begin()
{return _start;
}iterator end()
{return _finish;
}
实现完普通迭代器之后,我们可以顺便重载一个const_iterator的版本。
const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}
我们知道在vector中还有一个反向迭代器,这个我们在之后会统一实现。
2.2. vector的初始化与销毁
2.2.1. 构造函数与拷贝构造
我们之前在学习vector时知道其初始化方式有很多,可以通过默认构造函数给其初始化,n个val初始化,也可以通过迭代器初始化。
首先我们写一个默认构造函数,将其所有变量都设为空。
vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{;
}
接下来我们来实现迭代器初始化,而因为我们可以通过其他容器的迭代器对其初始化,所以要通过模版来实现。
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
最后我们来实现n个val初始化。
vector(size_t n, const T& val = T())
{resize(n, val);
}
vector(int n, const T& val = T())
{resize(n, val);
}
至于为什么要同时重载int与size_t两种不同类型,那是为了防止在传两个int类型的参数时被编译器交给模版InputIterator识别,然后报错。
拷贝构造也十分简单,直接拷贝就行,但是也有一些注意事项。
vector(const vector<T>& v)
{_start = new T[v.capacity()];//开辟capacity的空间for (size_t i = 0; i < v.size(); ++i){_start[i] = v._start[i];//进行深拷贝}_finish = _start + v.size();//更新_finish_end_of_storage = _start + v.capacity();//更新_end_of_storage
}
这里注意不能利用memcpy()等库函数进行拷贝,因为这些函数都是进行的浅拷贝。如果模版参数T是string,vector等自定义类型,当程序结束回收内存时就会发生内存错误。

当然我们也可以通过一个取巧的方式来实现拷贝构造。
vector(vector<int>& v)
{// 根据v的capacity()去开出对应的空间reserve(v.capacity());//进行深拷贝for (size_t i = 0; i < v.size(); i++){push_back(v[i]);}
}
首先通过构造出一个与数组相同的数组v,然后让this所指向的数组与其交换,这样出了作用域之后销毁的就是原this所指向的数组。当然我们必须先将this所指向的数组先初始化扩容。
2.2.2. 赋值重载与析构函数
赋值运算符重载与拷贝构造的实现就非常类似了,直接实现即可。
vector<T> operator = (vector<T> v)
{swap(v);return *this;
}
最后我们实现析构函数,只需要清理资源即可
~vector()
{delete[]_start;_start = _finish = _end_of_storage = nullptr;
}
2.3. vector的容量操作
2.3.1. 有效长度与容量大小
首先我们先实现返回数组有效长度的size() 与容量大小的capacity()。并且为了适配const对象,最后用const修饰this指针。
size_t size() const
{return _finish - _start;
}
size_t capacity() const
{return _end_of_storage - _start;
}
2.3.2. 容量操作
接下来我们来实现扩容函数reserve()与·resize(),其中reserve()最简单,只要新容量大于旧容量就发生扩容,其中注意需要提前记录size大小,防止数组异地扩容原数组释放之后找不到原数组大小。
void reserve(size_t n)
{//提前原本记录长度size_t sz = size();if (n > capacity()){T* tmp = new T[n];if (_start){//深拷贝for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];//赋值重载}delete[]_start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}
而resize()的逻辑就比较复杂,需要分三种情况讨论。设字符串原来有效长度为size,容量为capacity,新容量为n
- 当
n<size时,resize会删除有效字符到指定大小。- 当
size<n<capcity时,resize会补充有效字符(默认为0)到指定大小。- 当
n>capacity时,resize会补充有效字符(默认为0)到指定大小。
void resize(size_t n,const T&val=T())
{if (n < size()){//更新数组大小_finish = _start + n;}else{//扩容reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}
}
2.4. vector的访问操作
为了符合我们C语言访问数组的习惯,我们可以先重载operator[]。当然我们也要提供两种不同的接口:可读可写与可读不可写。并且使用引用返回,减少不必要的拷贝。
// 可读可写
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}
// 可读不可写
T& operator[](size_t pos)const
{assert(pos < size());return _start[pos];
}
同理我们也可以实现front()与back()函数。
// 可读可写
char& front()
{return _start[0];
}
char& back()
{return _start[_size() - 1];
}
// 可读不可写
const char& front()const
{return _start[0];
}
const char& back()const
{return _start[_size() - 1];
}
2.5. vector的修改操作
2.5.1. 常见的修改操作
首先我们将实现两个常用的修改函数:push_back()与pop_back()。
void push_back(const T& x)
{//判断是否扩容if (_finish == _end_of_storage){size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newCapacity);}*_finish = x;++_finish;
}
void pop_back()
{--_finish;
}
随后我们来实现数组的交换swap()函数,我们知道vector的交换其实就是指针_start,_finish,_end_of_storage的交换。
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}

2.5.2. 迭代器失效
接下来我们实现insert()与erase()两个函数。其中insert()在插入时可能扩容,这时就需要记录起始长度,方便更新迭代器返回。
iterator insert(iterator pos, const T& x)
{assert(pos <= _finish && pos >= _start);//检查是否扩容if (_finish == _end_of_storage){//先记录长度size_t len = pos - _start;size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);//更新迭代器指向新空间pos = _start + len;}//往后覆盖iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}*pos = x;++_finish;return pos;
}
同样的为了防止迭代器失效,需要返回新的迭代器。
iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator end = pos + 1;while (end != _finish){*(end - 1) = *end;++end;}--_finish;return pos;
}
3. 源码
#pragma once
namespace betty
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){;}vector(size_t n, const T& val = T()){resize(n, val);}vector(int n, const T& val = T()){resize(n, val);}template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}//vector(const vector<T>& v)//{// _start = new T[v.capacity()];//开辟capacity的空间// for (size_t i = 0; i < v.size(); ++i)// {// _start[i] = v._start[i];//循环拷贝// }// _finish = _start + v.size();//更新_finish// _end_of_storage = _start + v.capacity();//更新_end_of_storage//}vector(vector<int>& v){// 根据v的capacity()去开出对应的空间reserve(v.capacity());//进行深拷贝for (size_t i = 0; i < v.size(); i++){push_back(v[i]);}}vector<T> operator=(vector<T> v){swap(v);return *this;}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void reserve(size_t n){//提前原本记录长度size_t sz = size();if (n > capacity()){T* tmp = new T[n];if (_start){//深拷贝for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];//赋值重载}delete[]_start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}void push_back(const T& x){//判断是否扩容if (_finish == _end_of_storage){size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newCapacity);}*_finish = x;++_finish;}void resize(size_t n,const T&val=T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}T& operator[](size_t pos){assert(pos < size());return _start[pos];}T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}iterator insert(iterator pos, const T& x){assert(pos <= _finish && pos >= _start);//检查是否扩容if (_finish == _end_of_storage){//先记录长度size_t len = pos - _start;size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);//更新迭代器指向新空间pos = _start + len;}//往后覆盖iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator end = pos + 1;while (end != _finish){*(end - 1) = *end;++end;}--_finish;return pos;}void pop_back(){--_finish;}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(){delete[]_start;_start = _finish = _end_of_storage = nullptr;}private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
ase(iterator pos){assert(pos >= _start && pos < _finish);iterator end = pos + 1;while (end != _finish){*(end - 1) = *end;++end;}--_finish;return pos;}void pop_back(){--_finish;}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(){delete[]_start;_start = _finish = _end_of_storage = nullptr;}private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
相关文章:
C++必修:STL之vector的模拟实现
✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C学习 贝蒂的主页:Betty’s blog 为了让我们更加深入理解vector,接下来我们将模拟实现一个简易版的vect…...
Unity Camera
课程目标 1. 了解摄像机(camera)不同视角的设计与实现;2. 感受在不同摄像机视角下观察虚拟场景。 喜欢玩游戏或者看3D动漫的朋友可以回忆在虚拟场景中摄像头的运动变化带来的视觉感受,例如:摄像头给场景中的主角来个…...
CSS雷达光波效果(前端雷达光波效果)
前言 CSS雷达光波效果是一种视觉动画效果,常用于模仿雷达扫描或检测的视觉反馈。这种效果通常涉及到动态的圆形或弧形图案,它们从一个中心点向外扩散,类似于水面上的涟漪或雷达扫描线。以下是创建CSS雷达光波效果的一些关键技术和步骤&#…...
【C语言】【数据结构】冒泡排序及优化
一、算法思想 冒泡排序是一种简单的排序算法。一次从前往后地走访待排序的元素序列被称为一趟,每一趟都会把相邻的两个元素的错误顺序交换,将当前趟次中最大或者最小的元素像“冒泡泡”一样冒到最后面,反复地走访元素序列,直到所有…...
3种 Ajax 方式:原生、jQuery、axios
毋庸多言,Ajax 技术在网页中是划时代的进步。学会它,可以说掌握了一招半式,不再是门外汉了。 这里将 3 种 Ajax 方式一并呈上。 感谢 https://run.uv.cc/ 平台,以及 /api 接口 https://andi.cn/page/621639.html https://andi…...
Node.js 根据表结构动态生成目标代码
文章目录 前言项目背景使用的技术栈步骤一:设置 Node.js 项目步骤二:连接 SQL Server 数据库步骤三:查询数据库表结构步骤四:生成模板代码步骤五:整合所有功能总结 前言 在现代的前端开发中,使用 Vue3 搭配…...
渗透测试实战—云渗透(AK/SK泄露)
免责声明:文章来源于真实渗透测试,已获得授权,且关键信息已经打码处理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本…...
【机器学习】机器学习与医疗健康在疾病预测中的融合应用与性能优化新探索
文章目录 引言第一章:机器学习在医疗健康中的应用1.1 数据预处理1.1.1 数据清洗1.1.2 数据归一化1.1.3 特征工程 1.2 模型选择1.2.1 逻辑回归1.2.2 决策树1.2.3 随机森林1.2.4 支持向量机1.2.5 神经网络 1.3 模型训练1.3.1 梯度下降1.3.2 随机梯度下降1.3.3 Adam优化…...
MySQL(8.0)数据库安装和初始化以及管理
1.MySQL下载安装和初始化 1.下载安装包 下载地址:https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 2.解压…...
C# Web控件与数据感应之 TreeView 类
目录 关于 TreeView 一些区别 准备数据源 范例运行环境 一些实用方法 获取数据进行呈现 根据ID设置节点 获取所有结点的索引 小结 关于 TreeView 数据感应也即数据捆绑,是一种动态的,Web控件与数据源之间的交互,本文将继续介绍与…...
java使用责任链模式进行优化代码
1.什么是责任链 责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许多个对象有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。每个收到请求的对象要么处理该请求,要么将它传递给链中…...
【人工智能】边缘计算与 AI:实时智能的未来
💎 我的主页:2的n次方_ 💎1. 引言 随着物联网设备数量的爆炸性增长和对实时处理需求的增加,边缘计算与人工智能(Edge AI)成为一个热门话题。Edge AI 通过在本地设备上运行 AI 算法,减少对云计…...
Day12--Servlet实现前后端交互(案例:学生信息管理系统登录页面)
(在一个完整的项目架构中,servlet的角色和位置) Servlet、GenericServlet和HttpServlet三者之间的关系是Java Web开发中的一个重要概念,它们共同构成了基于Java的服务器端程序的基础。以下是具体分析: 1. Servlet接口…...
Android 安装应用-准备阶段
安装应用的准备阶段是在PackageManagerService类中的preparePackageLI(InstallArgs args, PackageInstalledInfo res),代码有些长,分段阅读。 分段一 分段一: GuardedBy("mInstallLock")private PrepareResult preparePackageLI(I…...
【JKI SMO】框架讲解(九)
本节内容将演示如何向SMO框架添加启动画面。 1.打开LabVIEW新建一个空白项目,并保存。 2.找到工具,打开SMO Editor。 3.新建一个SMO,选择SMO.UI.Splash。 4. 打开LabVIEW项目,可以看到项目里多了一个SystemSplash类。 打开Process…...
Linux通过Docker安装Microsoft Office+RDP远程控制
之前根据B站教程《在linux上安装微软office》:在linux上安装微软office_哔哩哔哩_bilibili 写过一篇使用KVM虚拟机安装Microsoft OfficeRDP远程控制的文章,根据B站的教程安装后,发现有远程控制延迟的问题,比如拖动Office窗口时会…...
利用Qt实现调用文字大模型的API,文心一言、通义千问、豆包、GPT、Gemini、Claude。
利用Qt实现调用文字大模型的API,文心一言、通义千问、豆包、GPT、Gemini、Claude。 下载地址: AI.xyz 1 Qt实现语言大模型API调用 视频——Qt实现语言大模型API调用 嘿,大家好!分享一个最近做的小项目 “AI.xyz” 基于Qt实现调用各家大模型…...
借助医疗保健专用的 LLM提高诊断支持与准确性
概述 最近的研究表明,大规模语言模型在医疗人工智能应用中非常有效。它们在诊断和临床支持系统中的有效性尤为明显,在这些系统中,它们已被证明能为各种医疗询问提供高度准确的答案(例如,医生在诊断过程中需要用到语言…...
微前端(qiankun)
微前端 特点:独立开发、独立部署,独立运行,增量升级 解决的问题:日常开发过程中,可能有很多老项目需要迭代,但是可能新的一些可能需要使用的依赖或者新的一些框架,老项目已经不满足,…...
速通c++(周二)
前言 Hello,大家好啊,我是文宇,不是文字,是文宇哦。 今天是速通c第二期。 运算符 c里的运算符种类有很多,因为这个教程是入门教程,所以只介绍其中我们会用到的几种。 算数运算 c中的算数运算有九个&a…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

