【C++】理解vector的底层原理并模拟实现(手撕vector)

目录

01.成员变量

02.构造与析构

 03.管理内存

1.reserve函数

2.reszie函数

04.访问元素

05.修改元素


之前的一篇博客讲到了vector的介绍及其运用:vector的介绍及使用说明 但是我们不仅要会用,还要理解它的底层原理,今天我们通过手撕一个自己的vector,来进一步加深对vector容器的理解。

01.成员变量


namespace my_std
{template<typename T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endOfStorage; // 指向存储容量的尾};
}

vector是一个通过类模版实现的容器,通过类定义,可以使其更为灵活,泛用性更强。

在我们定义的vector类中,我们把iterator定义为原生指针T*,虽然vector迭代器严格意义上并不等同于指针,但是其在很多方面与原生指针行为类似,比如可以通过‘++’来递增,通过‘*’来解引用等等。因此这里我们将其定义为原生指针也是比较贴近标准库中的vector的实现的。

  1. iterator _start;:指向 vector 内部数据块的开始位置的迭代器。

  2. iterator _finish;:指向 vector 内部有效数据的尾部。有效数据是指在 vector 中存储的实际元素,而 _finish 指向的位置是有效数据的下一个位置

  3. iterator _endOfStorage;:指向 vector 内部存储容量的尾部的下一个位置。存储容量是指 vector 内部实际分配的内存空间的大小,而 _endOfStorage 指向的位置是分配的内存空间的末尾的下一个位置。这个位置之后的内存空间是 vector 可以进行扩展的空间。

迭代器声明为私有的成员类型可以确保用户不能直接访问和操作迭代器,这样可以保护vector内部的状态和数据。但是用户必须通过迭代器才能访问并操作vector内的元素,所以需要有获取迭代器的成员函数,分为读写和只读访问两个版本:

        iterator begin() {return _start;}iterator end() {return _finish;}const_iterator cbegin() {return _start;}const_iterator cend() const {return _finish;}

02.构造与析构

1.无参构造:

        vector():_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){}

都初始化为空指针

2.带参构造:

        vector(int n, const T& value = T()) {_start = new T[n];for (int i = 0; i < n; i++) {_start[i] = value;}_finish = _endOfStorage =  _start + n;}

_start迭代器是T*类型的原生指针,实际上是一个数组,因此可以使用new关键字为其动态分配内存,元素添加后要记得将_finish移到正确的偏移位置。

3.迭代器构造:

        template<class InputIterator>vector(InputIterator first, InputIterator last) {int size = last - first;_start = new T[size];int i = 0;for (auto it = first; it != last; it++) {_start[i] = *it;i++;}_finish = _endOfStorage =  _start + size;}

vector还支持迭代器进行构造,通过迭代器遍历源数组,将数据拷贝到目标容器中。迭代器相减可以得到构造的元素数量,再用new关键字对_start进行空间分配,迭代器遍历实现数据拷贝。

4.拷贝构造:

        vector(const vector<T>& v) {_start = new T[v.size()];for (size_t i = 0; i < v.size(); i++) {_start[i] = v[i];}_finish = _endOfStorage = _start + v.size();}

拷贝构造其实与迭代器构造类似,不过拷贝构造只支持vector容器类型之间的拷贝,而迭代器构造还支持数组的拷贝以及片段的拷贝,这两者是有区别的。这里的size()函数用于获取元素个数,后面会有关于函数的声明及定义。

5.析构函数:

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

进行测试:

 03.管理内存

首先我们需要两个函数分别获取vector的数据个数以及容量大小:

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

将其申明为const,确保不会修改成员变量的值。

1.reserve函数

vector的reserve函数用于预留一定的容量,只开辟空间,不进行赋值,空间足够就不进行操作:

        void reserve(size_t n) {if (n > capacity()) {size_t old_size = size();T* tmp = new T[n];if (_start != nullptr) {for (int i = 0; i < size(); i++) {tmp[i] = _start[i];}}delete[] _start;_start = tmp;_finish = _start + old_size;_endOfStorage = _start + n;}}

使用reserve函数后,会在堆上重新申请一块空间,原来的空间就失效了,需要进行空间释放,这里使用delete操作符,因为_start是通过new操作符进行空间开辟的。_finish与_endOfStorage实际上表示的是相对于_start的偏移量,因此我们需要记录这个偏移量,使得_start指针修改之后_finish与_endOfStorage可以来到正确的位置。

2.reszie函数

resize函数用于改变vector的大小,调整vector中元素的数量,如果调整后的大小大于当前大小,则会在末尾添加新的元素并进行赋值(默认构造值),如果调整后的大小小于当前大小,则会删除末尾的元素。

        void resize(size_t n, const T& value = T()) {T* tmp = new T[n];if (n <= size()) {for (size_t i = 0; i < n; i++) {tmp[i] = _start[i];}}else {for (size_t i = 0; i < size(); i++) {tmp[i] = _start[i];}for (size_t i = size(); i < n; i++) {tmp[i] = value;}}delete[] _start;_start = tmp;_finish = _endOfStorage =  _start + n;}

同样的,需要开辟一块新的空间并将数据进行迁移,释放原空间内存,由于空间可能扩大也可能缩小,所以要分情况讨论。

运行测试:

04.访问元素

1.‘[]’运算符的重载

为了方便对容器内元素的访问,我们需要重载‘[]’运算符,模拟数组的下标访问:

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

由于迭代器_start实际上就是数组的头指针,因此可以直接调用

2.front、back函数

vector的front函数可以直接访问容器的首元素,back函数可以直接访问容器的尾元素:

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

05.修改元素

1.尾插尾删

在尾部插入元素时,首先要考虑内存是否充足,内存不足的情况下,我们考虑二倍扩容。

        void push_back(const T& x) {if (_finish = _endOfStorage){int newcapacity = capacity() == 0 ? 2 : 2 * capacity();}*_finish = x;++_finish;}void pop_back() {assert(size() > 0);--_finish;}

修改元素后要记得实时更新迭代器。

2.swap交换

vector支持不同类型元素的交换,其原理是通过交换两个容器内部的指针和大小,而并不修改实际的内存块,所以不受元素类型的影响

        void swap(vector<T>& v) {vector<T> tmp(v);delete[] v._start;v._start = _start;v._finish = _finish;v._endOfStorage = _endOfStorage;_start = tmp._start;_finish = tmp._finish;_endOfStorage = tmp._endOfStorage;}

C++标准库也提供了‘std::swap’函数,因此可以直接用‘std::swap’函数实现vector的swap:

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

3.随机插入

vector也支持在指定位置进行元素插入的操作,由于vector内元素是顺序连续存储的,因此每次插入操作都要对当前位置之后的所以元素进行挪动,同样的,在插入操作前需要进行内存检查:

		iterator insert(iterator pos, const T& x){assert(pos <= _finish);// 空间不够先进行增容if (_finish == _endOfStorage){//size_t size = size();size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;reserve(newCapacity);// 如果发生了增容,需要重置pospos = _start + size();}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}

 pos迭代器指向插入的位置,其本质是在_start迭代器地址基础上的一个偏移,扩容操作会使_start指向一块新的空间,其值会发生改变,因此pos迭代器也要进行改变。

4.随机删除

删除元素也同样需要挪动数据,将pos位置之后的元素以此往前移动覆盖,就可以实现删除效果。删除元素后记得更新_finish指针:

        iterator erase(iterator pos){// 挪动数据进行删除iterator begin = pos + 1;while (begin != _finish) {*(begin - 1) = *begin;++begin;}--_finish;return pos;}

下面给出完整代码:

#pragma once
#include<iostream>
using namespace std;
#include<assert.h>namespace my_std
{template<typename T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;iterator begin() {return _start;}iterator end() {return _finish;}const_iterator cbegin() {return _start;}const_iterator cend() const {return _finish;}// 创建和销毁vector():_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){}vector(int n, const T& value = T()) {_start = new T[n];for (int i = 0; i < n; i++) {_start[i] = value;}_finish = _endOfStorage =  _start + n;}template<class InputIterator>vector(InputIterator first, InputIterator last) {int size = last - first;_start = new T[size];int i = 0;for (auto it = first; it != last; it++) {_start[i] = *it;i++;}_finish = _endOfStorage =  _start + size;}vector(const vector<T>& v) {_start = new T[v.size()];for (size_t i = 0; i < v.size(); i++) {_start[i] = v[i];}_finish = _endOfStorage = _start + v.size();}vector<T>& operator= (vector<T> v) {vector<T> new_v(v);return new_v;}~vector(){if (_start){delete[] _start;_start = _finish = _endOfStorage = nullptr;}}// 内存管理size_t size() const {return _finish - _start;}size_t capacity() const {return _endOfStorage - _start;}void reserve(size_t n) {if (n > capacity()) {size_t old_size = size();T* tmp = new T[n];if (_start != nullptr) {for (int i = 0; i < size(); i++) {tmp[i] = _start[i];}}delete[] _start;_start = tmp;_finish = _start + old_size;_endOfStorage = _start + n;}}void resize(size_t n, const T& value = T()) {T* tmp = new T[n];if (n <= size()) {for (size_t i = 0; i < n; i++) {tmp[i] = _start[i];}}else {for (size_t i = 0; i < size(); i++) {tmp[i] = _start[i];}for (size_t i = size(); i < n; i++) {tmp[i] = value;}}delete[] _start;_start = tmp;_finish = _endOfStorage =  _start + n;}// 元素访问T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}T& front(){return *_start;}const T& front()const{return *_start;}T& back(){return *(_finish - 1);}const T& back()const{return *(_finish - 1);}// 增删void push_back(const T& x) {if (_finish = _endOfStorage){int newcapacity = capacity() == 0 ? 2 : 2 * capacity();}*_finish = x;++_finish;}void pop_back() {assert(size() > 0);--_finish;}void swap(vector<T>& v) {vector<T> tmp(v);delete[] v._start;v._start = _start;v._finish = _finish;v._endOfStorage = _endOfStorage;_start = tmp._start;_finish = tmp._finish;_endOfStorage = tmp._endOfStorage;}iterator insert(iterator pos, const T& x) {assert(pos < _finish&& pos >= _start);if (_finish == _endOfStorage){size_t site = pos - _start;int newcapacity = capacity() == 0 ? 2 : 2 * (capacity());reserve(newcapacity);pos = _start + site;//pos到新空间的位置上}iterator end = _finish - 1;while (end >= pos)//开始整体向后退{*(end + 1) = *end;end--;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos < _finish&& pos >= _start);assert(size() > 0);//开始向前移动iterator start = pos + 1;while (start < _finish){*(start - 1) = *start;start++;}_finish--;return pos;//返回删除的位置}private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endOfStorage; // 指向存储容量的尾};
}

那么以上就是vector的模拟实现了,欢迎在评论区留言,觉得这篇博客对你有帮助的可以点赞关注收藏支持一波喔~😉

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

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

相关文章

wireshark解析grpc/protobuf的方法

1&#xff0c;wireshark需要安装3.20以上 下载地址&#xff1a;https://www.wireshark.org/ 2&#xff0c;如果版本不对&#xff0c;需要卸载&#xff0c;卸载方法&#xff1a; sudo rm -rf /Applications/Wireshark.app sudo rm -rf $HOME/.config/wireshark sudo rm -rf /…

设计模式-结构型-享元模式Flyweight

享元模式的特点&#xff1a; 享元模式可以共享相同的对象&#xff0c;避免创建过多的对象实例&#xff0c;从而节省内存资源 使用场景&#xff1a; 常用于需要创建大量相似的对象的情况 享元接口类 public interface Flyweight { void operate(String extrinsicState); } 享…

报错 | 2023新版IDEA/PyCharm连接远程服务器的Docker需使用密钥认证

文章目录 01 问题情景02 需求场景及工作原理03 解决步骤3.1 在本地生成密钥对3.2 将公钥保存至服务器3.3 本地连接时选择私钥文件 网上有很多文章讲怎么解决&#xff0c;但都要么写得很复杂&#xff0c;要么没有写明白原理或操作详情&#xff0c;造成我一头雾水。 01 问题情景…

一维卷积神经网络的特征可视化

随着以深度学习为代表的人工智能技术的不断发展&#xff0c;许多具有重要意义的深度学习模型和算法被开发出来&#xff0c;应用于计算机视觉、自然语言处理、语音处理、生物医疗、金融应用等众多行业领域。深度学习先进的数据挖掘、训练和分析能力来源于深度神经网络的海量模型…

vue2+element-ui 实现OSS分片上传+取消上传

遇到问题&#xff1a;项目中需要上传500MB以上的视频。一开始使用上传组件el-upload&#xff0c;调用后台接口&#xff0c;但是出现了onprogress显示百分百后接口一直pending&#xff0c;过了很多秒后接口才通&#xff0c;如果遇到大文件的话&#xff0c;接口就会报超时。 解决…

Linux网络协议栈从应用层到内核层④

文章目录 1、网卡接受数据2、网络设备层接收数据3、ip层接受数据4、tcp层接受数据5、上层应用读取数据6、数据从网卡到应用层的整体流程 1、网卡接受数据 当网卡收到数据时&#xff0c;会触发一个中断&#xff0c;然后就会调用对应的中断处理函数&#xff0c;再做进一步处理。…

掌握数据相关性新利器:基于R、Python的Copula变量相关性分析及AI大模型应用探索

在工程、水文和金融等各学科的研究中&#xff0c;总是会遇到很多变量&#xff0c;研究这些相互纠缠的变量间的相关关系是各学科的研究的重点。虽然皮尔逊相关、秩相关等相关系数提供了变量间相关关系的粗略结果&#xff0c;但这些系数都存在着无法克服的困难。例如&#xff0c;…

Nginx从安装到高可用实用教程!

一、Nginx安装 1、去官网http://nginx.org/下载对应的nginx包&#xff0c;推荐使用稳定版本 2、上传nginx到linux系统 3、安装依赖环境 (1)安装gcc环境 yum install gcc-c(2)安装PCRE库&#xff0c;用于解析正则表达式 yum install -y pcre pcre-devel(3)zlib压缩和解压缩…

洞察商机共论出海,4月12日@上海,数说故事D3智能营销论坛,灿然开启!

洞察是生意之母。 —— 彼得德鲁克 读懂消费者&#xff0c;找到好洞察&#xff0c;是生意的原点。 大数据与AI时代&#xff0c;好洞察常常蕴藏在社媒数据中。品牌上新会提前布局社交媒体&#xff0c;集中种草与收集反馈&#xff0c;产品创新灵感也很多来自评论区的夸夸与吐槽&a…

数据文件大小扩容或缩容必备技能

欢迎关注“数据库运维之道”公众号&#xff0c;一起学习数据库技术! 本期将为大家分享“数据文件大小扩容或缩容必备技能” 。 关键词&#xff1a;Resize Datafile、ORA-03297、高水位线 表空间跟数据文件是一对多的关系&#xff0c;数据文件存放到磁盘或ASM磁盘组。当磁盘空间…

Stream 流和 Lambda 组装复杂父子树形结构

在最近的开发中&#xff0c;遇到了两个类似的需求&#xff1a;都是基于 Stream 的父子树形结构操作&#xff0c;返回 List 集合对象给前端。于是在经过需求分析和探索实践后有了新的认识&#xff0c;现在拿出来和大家作分享交流。 一般来说完成这样的需求大多数人会想到递归&a…

【C+ +】第一个C+ + 项目的创建及namespace命名空间解释C++中的输入输出

目录 1.创建第一个c项目 1.1项目创建 1.2 .cpp源文件建立 1.3 第一个c程序hello world对比c语言hello world 2.命名空间 2.1 C关键字 2.2 命名空间---解决c语言中的命名冲突 2.2.1 namespace命名空间用法 2.2.2 &#xff1a;&#xff1a; 预作用限定符 2.2.3 命名空间的嵌套…