vector深度剖析及模拟实现

目录

  • 前言
  • vector核心框架模拟实现
    • 1. 前期准备
    • 2. 构造和销毁
      • 补充: 隐式类型转换和多参数构造的区别
    • 3. 迭代器相关
    • 4. 容器相关
      • 补充: memcpy拷贝问题
    • 5. 元素访问
    • 6. vector的修改
      • 测试代码
  • 总结

前言

本文重点模拟实现vector的核心接口, 帮助我们更好的理解底层逻辑, 以及对vector的深度剖析.

博客主页: 酷酷学!!! 期待关注~


正文开始
在这里插入图片描述

在这里插入图片描述

vector核心框架模拟实现

1. 前期准备

首先, 可以查看到STL源码, 底层vector的实现并不是我们通常顺序表那样定义成员变量, 而是通过迭代器也就是指针进行实现, 那么我们也按照STL来进行模拟实现.

T* _a;
size_t _size;
size_t _capacity;

在这里插入图片描述


#pragma once#include<iostream>
#include<assert.h>
using namespace std;namespace my
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//核心接口的实现private:iterator _start; //指向数据块开始iterator _finish; //指向有效数据的尾iterator _endOfStorage; //指向存储容量的尾};
}

2. 构造和销毁

在这里插入图片描述

  • 默认无参构造
		//默认无参构造vector():_start(nullptr), _finish(nullptr), _endOfStorage(nullptr){}

默认的无参构造, 因为在声明的时候我们并没有给缺省值, 所以我们也可以直接在初始化列表进行初始化.

  • 迭代器区间初始化
		//迭代器区间初始化//若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)//只能是vector的迭代器//重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}
  • 初始化n个相同的值
		//n个相同的值,使用默认的构造函数进行初始化, //对于内置类型,C++对这方面也进行了支持//vector(size_ n, const T& value = 0)//这里缺省值不能给0,如果对于自定义类型就有问题了vector(size_t n, const T& value = T())//匿名对象//相当于缺省值给了一个临时的匿名对象:_start(nullptr), _finish(nullptr), _endOfStorage(nullptr){reserve(n);while (n--){push_back(value);}}/** 理论上讲, 提供了vector(size_t n,const T& value = T())之后,* vector(int n,const T& value = T())就不需要提供了,但是对于:* vector<int> v(10,5);* 编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型* 就不会走vector(size_t n,const T& value = T())这个构造方法* 最终选择的是:vector(InputIterator first, InputIterator last)* 因为编译器觉得区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int* 但是10和5根本不是一个区间,编译时就报错了* 故需要增加该构造方法*/vector(int n, const T& value = T()): _start(new T[n]), _finish(_start + n), _endOfStorage(_finish){for (int i = 0; i < n; ++i){_start[i] = value;}}

这里可能不太理解为什么 const T& value = T() 这个缺省值要给T(), 要给默认构造函数, C++对于内置类型也进行了升级, 内置类型也可以使用构造初始化, 所以这个值, 不管自定义类型还是内置类型都可以适用

		// C++内置类型进行了升级,也有构造int i = 0;int j(1);int k = int();int x = int(2)

在这里插入图片描述
有时候, 我们会发现有些代码使用vector是这样写的

vector<int> v{ 1, 2, 3, 4 };

查看C++文档, 可以发现这是C++11的新语法让vector用起来更加方便, 使用<initializer_list>这个类进行初始化.
在这里插入图片描述
initializer_list中有两个成员变量. begin指针和end指针, 记录了列表的开始位置和结尾位置, 记录列表这段区间, 进行初始化.
在这里插入图片描述

  • initializer_list进行初始化
		vector(initializer_list<T> il):_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){reserve(il.size());for (auto e : il){push_back(e);}}

首先扩容, 然后遍历il, 将里面的值尾插到vector.


  • 拷贝构造
		//拷贝构造vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){reserve(v.capacity());iterator it = begin();const_iterator vit = v.cbegin();while (vit != v.cend()){*it++ = *vit++;}_finish = it;}//vector(const vector<T>& v)//	:_start(nullptr)//	,_finish(nullptr)//	,_endOfStorage(nullptr)//{//	reverse(v.capacity());//	for (auto e : v)//	{//		push_back(e);//	}//}
  • 赋值运算符重载
		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}//赋值运算符重载// v1 = v3vector<T>& operator=(vector<T> v){swap(v);return *this;}
  • 析构函数
		//析构函数~vector(){if (_start){delete[] _start;_start = _finish = _endOfStorage = nullptr;}}

补充: 隐式类型转换和多参数构造的区别

class A{public:A(int a1 = 0):_a1(a1), _a2(0){}A(int a1, int a2):_a1(a1),_a2(a2){}private:int _a1;int _a2;};void test()
{//单参数构造和多参数对象隐式类型转换A aa1(1); //这里是单参数构造A aa2 = 1; //这里是隐式类型转换A aa3(1,1); //这里是多参数构造A aa4 = {1,1}; //这里是多参数隐式类型转换A aa5{1,1}; //这里是多参数隐式类型转换省略=, 一般不要这种写法A aa6{1};A aa7 = {1}; //这两个是C++为了想让{}进行统一, 所以单参数也可以使用{}进行构造, 比较冗余,一般不要这样写/////自定义类型动态开辟调用构造函数A* p1 = new A;//无参构造A* p2 = new A(2); //单参数传参构造A* p3 = new A(2,3); //多参数传参构造A* p1 = newA[10]; //连续申请10个空间,无参构造A aa1(1);A aa2(2);A aa3(3);A *p2 = new A[10]{aa1,aa2,aa3};//拷贝构造,其余为0A* p3 = new A[10]{1,2,3,4,{6,7}};//也可以直接写,进行隐式类型转换///这里的隐式类型转换,跟上面不一样,这里参数个数不固定vector<int> v1({ 1,2,3,4,5,6 });vector<int> v2 = { 10, 20, 30};//()可以省略for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2}, {1}, {2,2} };//这个是首先创建一个存储A的列表然后进行构造}

3. 迭代器相关

这里分别对应静态和非静态vector

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

4. 容器相关

  • size和capacity
		size_t size() const{return _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}
  • 修改空间容量, 默认增容
		void reserve(size_t n){if (n > capacity()){size_t oldSize = size();//1.开辟新的空间T* tmp = new T[n];//2.拷贝元素/*if (_start){memcpy(tmp, _start, sizeof(T) * size);}*///不可以用memcpy拷贝if (_start){for (size_t i = 0; i < oldSize; ++i){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + oldSize;_endOfStorage = _start + n;}}

补充: memcpy拷贝问题

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中.
  2. 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。(比如拷贝int类型既高效又不会出错, 那如果vector里面存储的都是一个个的指针类型呢? 比如string类)

比如下面这段代码

		vector<string> v1;v1.push_back("111111111111111111");v1.push_back("111111111111111111");v1.push_back("111111111111111111");v1.push_back("111111111111111111");v1.push_back("111111111111111111");for (auto e : v1){cout << e << " ";}cout << endl;

当插入第五个值的时候, vector需要进行扩容, memcpy会继续拷贝, 但这是浅拷贝, 将_start里面的值拷贝到tmp中, 此时tmp中成员_str也指向了原来的那段空间, 当_start释放后, 区间就会被销毁, 此时tmp里面的内容就指向了野指针.

在这里插入图片描述

而这里使用赋值拷贝, 会调用我们的赋值拷贝函数, 就不会出现浅拷贝的问题了.

在这里插入图片描述

结论: 如果对象中涉及到资源管理时, 千万不能使用memcpy进行对象之间的拷贝, 因为memcpy是浅拷贝, 否则可能会引起内存泄漏甚至程序崩溃

  • rsize()函数
		void resize(size_t n, const T& value = T()){//1.如果n小于当前的size,则数据缩小到nif (n <= size()){_finish = _start + n;return;}//2.空间不够则增容if (n > capacity())reserve(n);//3.将size扩大到n,并使用value填充后面的值iterator it = _finish;_finish = _start + n;while (it != _finish){*it = value;++it;}}

5. 元素访问

		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);}

6. vector的修改

		void push_back(const T& x){insert(end(), x);}void pop_back(){erase(end() - 1);}iterator insert(iterator pos, const T& x){assert(pos <= _finish);assert(pos >= _start);//空间不够先进性增容if (_finish == _endOfStorage){size_t newcapacity = (capacity() == 0) ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + size();}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}//返回删除数据的下一个数据,//方便解决迭代器失效的问题iterator erase(iterator pos){iterator begin = pos + 1;while (begin != _finish){*(begin - 1) = *begin;++begin;}--_finish;return pos;}

测试代码

void TestVector1()
{my::vector<int> v1;my::vector<int> v2(10, 5);int array[] = { 1,2,3,4,5 };my::vector<int> v3(array, array + sizeof(array) / sizeof(array[0]));my::vector<int> v4(v3);for (size_t i = 0; i < v2.size(); i++){cout << v2[i] << " ";}cout << endl;
}void TestVector2()
{my::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << v.size() << endl;cout << v.capacity() << endl;cout << v.front() << endl;cout << v.back() << endl;cout << v[0] << endl;for (auto e : v){cout << e << " ";}cout << endl;v.pop_back();v.pop_back();for (auto e : v){cout << e << " ";}cout << endl;v.insert(v.begin(), 0);for (auto e : v){cout << e<<" ";}cout << endl;v.erase(v.begin() + 1);for (auto e : v){cout << e << " ";}cout << endl;
}

在这里插入图片描述
在这里插入图片描述

总结

本篇对vector的核心接口进行了模拟实现和探究, C++这门语言本身就比较偏向底层, 希望能够帮助大家进一步理解底层逻辑以及实现思路, 感谢三连!!!


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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

科学又省力 宠物浮毛怎么去掉便捷高效?除毛秘籍养宠空气净化器

上次和朋友逛完街去她家&#xff0c;她家的猫哈基米一开门就飞奔过来&#xff0c;朋友直接抱起它狂亲。结果&#xff0c;猫毛和汗水粘得到处都是&#xff0c;手臂上、脸上都是&#xff0c;看得我这鼻炎星人直起鸡皮疙瘩。很多养宠物的朋友都说&#xff0c;天天给猫狗梳毛&#…

Android Studio导入源码

在有源码并且编译环境可用的情况下&#xff1a; 1.生成导入AS所需的配置文件 在源码的根目录执行以下命令&#xff1a; source build/ensetup.sh lunch 要编译的项目 make idegen //这一步会生成out/host/linux-x86/framework/idegen.jar development/tools/idegen/idegen.sh…

利用OSMnx求路网最短路径并可视化(二)

书接上回&#xff0c;为了增加多路径的可视化效果和坐标匹配最近点来实现最短路可视化&#xff0c;我们使用图形化工具matplotlib结合OSMnx的绘图功能来展示整个路网图&#xff0c;并特别高亮显示计算出的最短路径。 多起终点最短路路径并计算距离和时间 完整代码#运行环境 P…

《昇思25天学习打卡营第24天|基于MindSpore通过GPT实现情感分类》

基于MindSpore通过GPT实现情感分类 %%capture captured_output # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspore版本&#xff0c;可更改下面mindspore的版本号 !pip uninstall mindspore -y !pip install -i https://pypi.mirrors.ustc.edu.cn/simple mind…

自动化测试 pytest 中 scope 限制 fixture使用范围!

导读 fixture 是 pytest 中一个非常重要的模块&#xff0c;可以让代码更加简洁。 fixture 的 autouse 为 True 可以自动化加载 fixture。 如果不想每条用例执行前都运行初始化方法(可能多个fixture)怎么办&#xff1f;可不可以只运行一次初始化方法&#xff1f; 答&#xf…

C语言进阶 11.结构体

C语言进阶 11.结构体 文章目录 C语言进阶 11.结构体11.1. 枚举11.2. 结构类型11.3. 结构与函数11.4. 结构中的结构11.5. 类型定义11.6. 联合11.7. PAT11-0. 平面向量加法(10)11-1. 通讯录的录入与显示(10) 11.1. 枚举 常量符号化: 用符号而不是具体的数字表示程序中的数字 cons…

【C++深度探索】AVL树与红黑树的原理与特性

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;C从入门至进阶 这里将会不定期更新有关C/C的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 前言 前…

渣土车与搅拌车安全问题解析及智能监控解决方案

一、背景分析 近年来&#xff0c;渣土车在货物运输中由于超载超速、违规驾驶、车辆盲区过大等问题导致的事故频发&#xff0c;严重影响了人们的生命财产安全。而搅拌车作为一种特殊的运输车辆&#xff0c;在混凝土输送过程中也存在类似的隐患。针对这些问题&#xff0c;对搅拌…

多维矩阵乘积运算和对应的广播机制

神经网络中的多维矩阵乘积运算&#xff1a; 遵循的原则是&#xff1a; 两张量前两维度应该是相同的&#xff0c;如果不同则其中一张量维度为1。 如果有论文中有遇到矩阵乘积的两项维度不一致&#xff0c;那就考虑它计算时是使用了广播机制&#xff08;如YOLACT&#xff09;。…

谁说只有车载HMI界面?现在工业类的HMI界面UI也崛起了

谁说只有车载HMI界面&#xff1f;现在工业类的HMI界面UI也崛起了 引言 艾斯视觉作为行业ui设计和前端开发领域的从业者&#xff0c;其观点始终认为&#xff1a;工业自动化和智能化水平不断提高&#xff0c;人机界面&#xff08;Human-Machine Interface&#xff0c;简称HMI&a…

Lombok的认识

Lombok的作用 Lombok是一个Java库&#xff0c;它可以通过简单的注解形式来帮助开发人员简化Java代码的编写&#xff0c;特别是减少模板代码的书写。具体来说&#xff0c;Lombok的主要作用包括&#xff1a; 减少模板代码&#xff1a;Lombok可以通过注解自动生成getter、setter、…

QT opencv常用代码备忘

最近在了解qt opencv的一些用法&#xff0c;把常用的代码记下来方便需要时复制使用 在默认.pro文件加入opencv包含路径和库文件 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c17# You can make your code fail to compile if it uses deprecate…

网络钓鱼抓肉鸡实验

实验背景 网络钓鱼&#xff0c;攻击一台服务器或普通主机时&#xff0c;很可能会将这台服务器或主机变成“傀儡机”&#xff0c;帮助它攻击其它的主机&#xff0c;以达到窃取更多信息、组建僵尸网络、DDOS攻击等目的&#xff0c;危害性极大 其中僵尸网络&#xff08;Botnet&a…

运维锅总详解NFS

NFS是什么&#xff1f;如何对NFS进行部署及优化&#xff1f;NFS工作流程是什么&#xff1f;NFS的性能及优缺点是什么&#xff1f;NFS发展历史又是怎样的&#xff1f;希望本文能帮您解答这些疑惑&#xff01; 一、NFS简介 NFS (Network File System) 是由 Sun Microsystems 在…

rem实现屏幕适配(jQuery)

一、rem换算 1.根据视口宽度动态计算字体大小&#xff0c;如果宽度大于750px&#xff0c;则将字体大小设置为100px&#xff0c;否则按比例缩小。 tips:使用时记得引入jQuery.js // 在文档加载完成后执行函数&#xff0c;确保DOM已经准备就绪$(function () {// 定义一个自执行…

二叉树详解-第四篇 二叉树链式结构的实现

目录 1.二叉树的遍历 1.1前序遍历&#xff1a; 1.2 中序遍历&#xff1a; 1.3 后序遍历&#xff1a; 2.二叉树链式结构的实现 2.1 Tree.h 2.2 Tree.cpp 2.2.1 前序遍历 void PreOrder(TNode* Root) 2.2.2 中序遍历 void InOrder(TNode* Root) 2.2.3 后序遍历 void Bac…

【Python实战因果推断】58_因果推理概论8

目录 Identifying the Treatment Effect The Independence Assumption Identification with Randomization Identifying the Treatment Effect 现在你已经理解了问题所在&#xff0c;接下来该看看解决方案&#xff08;至少是一个解决方案&#xff09;了。识别&#xff08;i…

聊一聊知识图谱结合RAG

因为最近在做一些关于提高公司内部使用的聊天机器人的回答准确率&#xff0c;并且最近微软官方也是开源了一下graphrag的源码&#xff0c;所以想聊一聊这个知识图谱结合rag。 rag在利用私有数据增强大模型回答的领域是一种比较典型的技术&#xff0c;也就是我们提出问题的时候&…

网站漏洞扫描软件Burp suite和Xray安装应用及联合使用

目录 1、网站漏洞扫描软件应用-Burp suite 01 burp 扫描工具使用介绍&#xff1a; 02 burp 扫描工具安装过程&#xff1a; 1&#xff09;获取扫描工具程序包 2&#xff09;安装部署扫描工具 3&#xff09;bp安装完毕的基础设置&#xff1a; 3.1&#xff09;抓取浏览器访…

免费使用正版的Typora教程

1.来到Typora官网下载安装。 Typora官网: https://typoraio.cn/ 2.激活主程序 编辑修改Typora安装目录下文件 下面展示文件目录路径 &#xff1a; D:\SoftWare\Typora1.9.5\resources\page-dist\static\js\LicenseIndex.180dd4c7.4da8909c.chunk.js查找&#xff1a;e.hasAc…