c++ - vector容器常用接口模拟实现

文章目录

    • 一、成员变量
    • 二、常用迭代器接口模拟实现
    • 三、一些常用接口模拟
    • 四、默认成员函数
    • 五、功能测试


一、成员变量

我们通过在堆上申请一个数组空间来进行储存数据,我们的成员变量是三个指针变量,分别指向第一个位置、最后储存有效位置的下一个位置以及数组空间的最后位置的下一个位置。

//为了与stl保持一致使用重命名
typedef T* iterator;
typedef const T* const_iterator;iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

在这里插入图片描述

二、常用迭代器接口模拟实现

直接返回数组的开始位置和有效数据的下一个位置即可。

//迭代器
//第一个位置
iterator begin()
{return  _start;
}//有效数据的下一个位置
iterator end()
{return _finish;
}//重载 const
const iterator begin() const
{return  _start;
}const iterator end() const
{return _finish;
}

三、一些常用接口模拟

(1)size
求有效数据的个数,用结束位置下一个位置的指针减去第一个位置的指针就是有效数据的个数了。

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

(2)capacity
求容器的容量,用数组空间的最后位置的下一个位置的指针减去第一个位置的指针就是容量的大小了。

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

(3)重载[]

//重载 []
T operator[](size_t i)
{//保证i位置符合assert(i < size());return  *(_start + i);
}const T operator[](size_t i) const
{//保证i位置符合assert(i < size());return  *(_start + i);
}

(4)reserve
预留空间,一般用于扩容,这里需要手动扩容,再拷贝数据,最后再改变成员变量的指向。

void reserve(size_t n)
{//先保存有效数据个数size_t _size = size();//判断是否需要扩容if (n > _size){//手动申请空间iterator tmp = new T[n];//拷贝数据到新空间for (size_t i = 0; i < _size; i++){*(tmp + i) = *(_start + i);}//释放掉原来的空间delete[] _start;//重新改变位置_start = tmp;_finish = _start + _size;_end_of_storage = _start + n;}
}

注意:
a.要先保存 size,因为在重新指向时需要_start第一个重新指向新空间,如果此时再去调用size()函数的话,返回的size是不确定的。最终导致_finish 指向错误。

b.就是不能使用memcpy函数进行拷贝,因为memcpy是浅拷贝,当出现数据是自定义类型而且也是需要申请资源的话就会出现重复释放等问题。如数据储存的是string类型。
在这里插入图片描述
所以需要使用深拷贝。
在这里插入图片描述
(5)clear
清空,将_start = _finish即可。

void clear()
{_finish = _start;
}

(6)insert
在迭代器位置前插入一个元素,并返回新的position位置。
迭起器失效问题:
插入一个位置可能会扩容,扩容就会导致position位置失效,所以如果需要扩容就先保存好 position 相对 _start的位置。

iterator insert(iterator position, const T val)
{//迭代器位置assert(position >= _start);assert(position <= _finish);//空间是否够if (_finish == _end_of_storage){	//保存相对位置size_t _size = position - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);//重新获取position位置position = _start + _size;}//往后移动一位iterator end = _finish;while (end > position){*end = *(end-1);--end;}*position = val;++_finish;return position;
}

(7)push_back
尾插入一个元素,这里直接调用insert即可。

void push_back(T x)
{insert(_finish, x);
}

(8)erase
删除position位置,返回position位置的值。
这里也可能会存在迭代器失效问题,如缩容(这里不考虑)、最后一个位置。

//删除迭代器
iterator erase(iterator position)
{//符合位置assert(position >= _start);assert(position < _finish);//覆盖掉positioniterator end = position;while (end < _finish - 1){*end = *(end + 1);++end;}--_finish;//这里返回空,因为position已经失效了if (position == _finish + 1){return nullptr;}return position;
}

(9)empty
是否空。

bool empty() const
{return size() == 0;
}

(10)pop_back
尾删,这里直接调用erase()即可。

void pop_back()
{if (empty())return;erase(_finish-1);
}

(11)resize
重新定义大小,n小于实际大小就缩小,n大于实际大小就用val来填充。

//重新定义大小void resize(size_t n, T val = T())
{if (n > capacity()){reserve(n);}size_t _size = size();if (n > _size){for (int i = _size; i < n; i++){//直接尾插push_back(val);}}else{_finish = _start + n;}}

(12)swap
交换两个容器。

void swap(vector<T>& x)
{//利用std库中的将指针位置交换即可std::swap(_start, x._start);std::swap(_finish, x._finish);std::swap(_end_of_storage, x._end_of_storage);
}

四、默认成员函数

(1)默认构造
无参构造,给了有默认值,这里不用初始化。

vector() {};

用n个val来初始化

vector(size_t n, T val = T())
{//先预留空间,这样就不用扩容了reserve(n);int i = 0;while (i < n){//尾插push_back(val);++i;}
};

拷贝构造
这里要用深拷贝

vector(vector<T> &x) 
{//先预留空间,这样就不用扩容了reserve(x.capacity());//直接将获取的数据插入即可,不用memcpy这种直接拷贝vector<T>::iterator it = x.begin();while (it != x.end()){push_back(*it);++it;}
};

用迭代器区间构造,因为可以用其他的容器来初始化,所以这里再用一个模板。

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{//先预留空间,这样就不用扩容了reserve(last - first);while (first < last){push_back(*first);++first;}
}

因为迭代器区间的构造函数与用n个val来初始化构造会冲突。
在这里插入图片描述

为了解决这个问题,使用一个具体一点的n个val来初始化的构造函数

vector(int n, T val = T())
{reserve(n);int i = 0;while (i < n){push_back(val);++i;}
};

(2)析构函数

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

(3)重载 =

vector<T>& operator=(vector<T> x)
{//利用交换函数,x出了该函数作用域就会销毁,所以不用我们手动销毁原来空间了swap(x);return *this;
}

五、功能测试

(1)内置类型

void test01()
{//n个valxu::vector<int> v1(2,1);for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;//区间xu::vector<int> v2(v1.begin(), v1.end());for (int i = 0; i < v1.size(); i++){cout << v2[i] << " ";}cout << endl;//赋值xu::vector<int> v3;v3 = v1;for (int i = 0; i < v1.size(); i++){cout << v3[i] << " ";}cout << endl;v1.push_back(2);v1.insert(v1.begin(), 0);for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;v1.pop_back();v1.erase(v1.begin());for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;v1.resize(10, 5);for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;}

在这里插入图片描述

(2)自定义类型

void test02()
{//n个valxu::vector<string> v1(2, "aa");for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;//区间xu::vector<string> v2(v1.begin(), v1.end());for (int i = 0; i < v1.size(); i++){cout << v2[i] << " ";}cout << endl;//赋值xu::vector<string> v3;v3 = v1;for (int i = 0; i < v1.size(); i++){cout << v3[i] << " ";}cout << endl;v1.push_back("bb");v1.insert(v1.begin(), "cc");for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;v1.pop_back();v1.erase(v1.begin());for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;v1.resize(10, "ee");for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;}

在这里插入图片描述

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

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

相关文章

vue中父组件与子组件的数据传递

例如在子组件中调用了&#xff1a; this.$emit(changePanel, panel) 这句代码是 Vue.js 框架中的一个方法调用&#xff0c;用于触发一个自定义事件。在 Vue.js 中&#xff0c;组件可以通过 $emit 方法来触发自定义事件&#xff0c;并传递数据给父组件。 下面是对这句代码的详…

特殊矩阵的压缩矩阵

目录 前提条件&#xff1a; 类型&#xff1a;对称矩阵&#xff0c;三角矩阵、三对角矩阵、稀疏矩阵 1&#xff1a;对称矩阵&#xff1a; 定义&#xff1a;n阶矩阵A 中任意一元素都有ai,jaj,i(1<i,j<n) 图像&#xff1a; 表达式&#xff1a; 计算过程&#xff1a; …

stream-并行流

定义 常规的流都是串行的流并行流就是并发的处理数据&#xff0c;一般要求被处理的数据互相不影响优点&#xff1a;数据多的时候速度更快&#xff0c;缺点&#xff1a;浪费系统资源&#xff0c;数据少的时候开启线程更耗费时间 模版 Stream<Integer> stream1 Stream.of…

【YOLO 系列】基于YOLO V8的学生上课行为检测系统【python源码+Pyqt5界面+数据集+训练代码】

前言 在现代教育环境中&#xff0c;学生上课行为的监测对于提升教学质量和学生学习效率具有重要意义。然而&#xff0c;传统的人工观察方法不仅效率低下&#xff0c;而且难以保证客观性和准确性。为了解决这一问题&#xff0c;我们启动了这个项目&#xff0c;目的是利用YOLOV8…

15.3 js数组塌陷和数组去重

数组塌陷 使用splice或pop会影响数组长度,形成循环次数的问题 pop 例子:pop从后删除了数据,那么影响了数组长度,i的值缩小 var arr = [1, 2, 3, 4, 5, 6, 7];for (var i = 0; i < arr.length; i++) {arr.pop(arr[i]);console.log(arr, i);}console.log("塌陷…

AI数学知识

AI数学知识 1、线性代数相关&#xff08;矩阵&#xff09;1、什么是秩2、奇异值分解3、特征值分解和奇异值分解4、低秩分解 回归分类知识点2、概率论相关1、先验概率和后验概率2、条件概率、全概率公式、贝叶斯公式、联合概率3、最大似然估计4、贝叶斯公式和最大似然估计5、伯努…

深入理解Kubernetes的调度核心思想

一、引言 Kubernetes&#xff08;简称K8s&#xff09;是一个开源的容器编排系统&#xff0c;用于自动化部署、扩展和管理容器化应用程序。在Kubernetes集群中&#xff0c;调度器是一个核心组件&#xff0c;它负责将Pod&#xff08;Kubernetes中的最小部署单元&#xff09;分配…

python读写二进制文件

需求&#xff1a;将Test文件夹下所有bin文件中凡是出现128的统一替换成129。 import os root rD:\TXB\Y2022\PROJ\S2106\INNER\内部研究\语音信号处理\智能语音处理\test\pattern_0513 for file in os.listdir(root):if file.endswith(.bin):src_path os.path.join(root, fi…

Java学习16

目录 一.StringBuffer类&#xff1a; 1.基本介绍&#xff1a; 2.StringBuffer的构造器&#xff1a; 3.String与StringBuffer的相互转换&#xff1a; &#xff08;1&#xff09;String->StringBuffer &#xff08;2&#xff09;StringBuffer->String 4.StringBuffer…

AutoDL中Notebook中无法打开“checkpoints”文件夹

checkpoints是Notebook的关键字&#xff0c;若用户创建文件夹命名为checkpoints&#xff0c;则在JupyterLab上无法打开、重命名和删除。此时可以在Terminal里使用命令行打开checkpoints&#xff0c;或者新建文件夹将checkpoints里的数据移动到新的文件夹下。 操作步骤&#xf…

Linux设置Huggingface镜像代理

打开终端&#xff08;Terminal&#xff09;。使用 vim 编辑 .bashrc 文件&#xff1a;vim ~/.bashrc在 vim 中&#xff0c;按 G 键移动到文件的末尾&#xff0c;然后按 o 键插入新的一行。输入以下内容&#xff1a;export HF_ENDPOINThttps://hf-mirror.com按 Esc 键退出插入模…

Redis篇 数据的编码方式和单线程模型

编码方式和单线程模型 一.redis中的数据类型二. Redis中查询编码方式命令三. 单线程模型四. 经典面试题,redis为何这么快?什么是IO多路复用? 一.redis中的数据类型 在redis中,数据类型大致分为5种 1.字符串类型 2.哈希 3.列表 4.集合 5.有序集合 redis底层在实现这些数据结构…

黑马点评-短信登录

Override public Result sendCode(String phone) { // 1.检验手机号 if (RegexUtils.isPhoneInvalid(phone)) { // 这里抛出异常和return fail有什么区别吗&#xff1f;———> 有区别&#xff0c;抛出异常会被全局异常处理器捕获&#xff0c;返回fail不会 throw ne…

防火墙技术基础篇:NAT转发之——NAPT(同时转换地址和端口)

NAT转发之——NAPT&#xff08;同时转换地址和端口&#xff09; 网络地址端口转换NAPT 网络地址端口转换NAPT&#xff08;Network Address Port Translation&#xff09;是人们比较熟悉的一种转换方式。NAPT普遍应用于接入设备中&#xff0c;它可以将中小型的网络隐藏在一个合…

Vue2和Vue3生命周期的对比

Vue2和Vue3生命周期的对比 Vue2 和 Vue3 生命周期对照表Vue2 和 Vue3 生命周期图示 Vue2 和 Vue3 生命周期对照表 触发时机Vue2.xVue3.x组件创建时运行beforeCreate setup createdsetup 挂载在DOM时运行beforeMountonBeforeMountmountedonMounted响应数据修改时运行beforeUpdat…

线程安全-4 AQSLockReentrantLock

一.什么是AQS&#xff1f; 1.AQS&#xff0c;AbstractQueuedSynchronizer&#xff0c;抽象队列同步器&#xff0c;是一个用于构建锁和同步器的框架。 2.基于AQS实现的锁或同步器有&#xff1a;可重入锁ReentrantLock、计时锁CountDownLatch、信号量Semaphore等 3.AQS三大核心…

x264 码率控制原理:x264_ratecontrol_end 函数

x264_ratecontrol_end 函数 原理 函数功能:编码完一帧数据后,保存状态并更新 ratecontrol 状态。函数参数:x264_t *h:编码器上下文结构体int bits:编码该帧所用的比特数int *filler:用于返回一个填充比特数函数调用关系: 函数内部执行流程:初始化x264_ratecontrol_t结…

关于DDos防御...别在听别人瞎扯了.....

前言 无意间刷文章的时候看到一篇文章&#xff0c;写的是遇到ddos&#xff0c;怎么用iptables封IP....... 然后我就百度搜了一下&#xff0c;好多都是这么说的&#xff0c;但是我发现&#xff0c;大多数人只要遭受过长期Ddos的&#xff0c;就不会再信网上的文章 文笔不太好&…

微信小程序中页面显示时触发子组件的重新渲染

使用生命周期函数&#xff1a; 在页面的 onShow 或 onLoad 生命周期函数中&#xff0c;调用子组件的方法或者改变子组件的数据&#xff0c;从而触发子组件的重新渲染。 例如&#xff0c;在页面的 js 文件中&#xff1a; Page({onShow: function() {// 调用子组件的方法this.sel…

Python内置函数ord()详解

在Python编程中&#xff0c;ord()函数是一个非常实用的内置函数&#xff0c;它用于返回一个字符的Unicode码点&#xff0c;即该字符在Unicode编码表中的数值。这个功能在处理文本数据和国际化应用程序时尤其重要。 函数功能 ord()函数的主要功能是将单个字符转换为其对应的Un…