C++:vector底层剖析

文章目录

  • 前言
  • 成员变量
  • 成员函数
    • vector ()
    • size_t size()
    • size_t capacity()
    • iterator begin()和const_iterator begin()const
    • iterator end()和const_iterator end()const
    • ~vector()
    • void push_back(const&T val)
    • vector<T>(const vector<T>& v)
    • vector<T>& operator=(vector<T> v)
    • void swap(vector<T> &v)
    • void reserve(size_t n)
    • void resize(size_t n,const T&val=T( ))
    • T& front() 和const T& front()const
    • T& back()和const T& back() const
    • void pop_back()
    • iterator insert(iterator pos, const T& val)
    • void erase(iterator pos)
    • vector(InputIterator first, InputIterator last)
  • 完整代码
  • 总结


前言

在上篇文章中,我们介绍了vector的使用,在本篇文章中我们将会详细介绍一下vector的底层逻辑,我们会对vector有更深层次的理解

成员变量

在vector中,我们可以是任意类型,所以我们要采用灵活的方式。在C语言中,我们采用typedef的方式改变存储类型。在C++中进入了模版的概念,我们采用模版来实现。

在C语言板块,我们采用三个变量记录vector的变化,分别是int size,int capacity,int *a;
我们如果去查看vector的底层逻辑,我们就会发现,vector底层采用三个指针记录数据以及空间的变化,指针用迭代器的方式实现

	template<class T>class vector{public:typedef T* iterator;typedef const T*  const_iterator;private://内置成员变量声明给缺省值T* _start=nullptr;T* _finish=nullptr;T* _endofstorage=nullptr;};

我们来简单介绍一下这几个指针:
_start是vector中元素初始位置
_finish是vector中元素的结束位置
_endofstorage是vector中的容量大小

在这里插入图片描述
侯捷老师《STL原码剖析》这本书画图的,书上的展示图如下:
在这里插入图片描述

成员函数

vector ()

我们在三个成员定义时已经进行了初始化操作

size_t size()

这个函数是用来计算vector中数据的大小

		size_t size(){return _finish - _start;}

size_t capacity()

这个函数是用来计算vector中容量的大小

	    size_t capacity(){return _endofstorage - _start;}

iterator begin()和const_iterator begin()const

这个函数用来记录第一个元素的位置

		iterator begin(){return _start;}const_iterator begin()const{return _start;}

iterator end()和const_iterator end()const

这个函数用来记录最后一个元素的下一个位置

    	iterator end(){return _finish;}const_iterator end()const{return _finish;}

~vector()

这个函数用来对空间进行清理

		~vector(){if (_start)//判断是否有资源需要清理{delete[]_start;_start = _finish = _endofstorage = nullptr;}}

void push_back(const&T val)

这个函数是用来在尾部插入数据

void  push_back(const T& val)
{//首先判断是否有空间,进行插入数据if (_finish == _endofstorage){//空间不足,需要开空间,提前计算好需要多大的空间size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;//我们一般采用reserve函数进行开空间//因为这个函数是专门用来进行对空间的开辟的reserve(newcapacity);}*_finish = val;_finish++;
}

vector(const vector& v)

拷贝构造

		vector<T>(const vector<T>& v){//开空间+尾插reserve(v.capacity());for (auto& e : v){push_back(e);}}

vector& operator=(vector v)

赋值功能
我们可以开辟一块空间,在一个个把值拷贝过去,再释放不用的空间,下面我们采取一种灵活的方式

我们已tmp[i]=_start[i]为例
vector<T>& operator=(vector<T> v){swap(v);return *this;}

vector v这个地方不能加引用!!!
我们来看一下具体的实现
🌟 在调用之前,会发生拷贝,拷贝一份相同的内容
在这里插入图片描述

🌟 把v和tmp进行交换,指针指向发生变化
在这里插入图片描述

🌟 v是局部变量,出了作用域进行销毁,完成深拷贝工作
我们不开空间而是直接构造

void swap(vector &v)

交换两个vector

		void swap(vector<T> &v){//仅仅通过交换两个指针的位置就可以解决 //必须指定是库里的,要不然就会发生死递归,最终栈溢出std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}

void reserve(size_t n)

这个函数是用来对空间进行开辟的,开n个大小的空间

void reserve(size_t n)
{//判断是否需要开空间if (n > capacity()){//开空间T* tmp = new T[n];if (_start){//拷贝之前的数据到新空间memcpy(tmp, _start, sizeof(T) * n);//释放旧空间delete[]_start;}//更新指针_start = tmp;_finish = _start +size();_endofstorage = _start + capacity();}
}

我们来测试一下下面这段代码

void test1(){vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (auto& e : v){cout << e << " ";}cout << endl;}

我们会发现,直接报错了,经过调试我们发现了错误所在
在这里插入图片描述
我们来画图看一看
在这里插入图片描述
我们发现,再对指针_finish更新时,不能用size(),因为之前的_start已经进行了更新,计算不到之前的数据个数了。
我们需要在进行更新指针之前,对这个size()进行备份,对于_endofstorage,容量也发生来更新,不能用原来的

		void reserve(size_t n){//判断是否需要开空间if (n > capacity()){//提前记录数据个数size_t old = size();//开空间T* tmp = new T[n];//拷贝之前的数据到新空间memcpy(tmp, _start, sizeof(T) * old);//更新指针_start = tmp;_finish = _start + old;_endofstorage = _start + n;}}

我们这里还有问题需要处理,memcpy拷贝呢是一个字节一个字节的拷贝,属于浅拷贝。
我们画个图来看一下
在这里插入图片描述
浅拷贝对于没有资源的类型是没有问题的,但是对于有资源的成员变量就有问题了,开辟新空间,拷贝数据,两块空间的内容都指向同一块空间,旧空间需要被释放,指向的那块空间就被释放掉了,新空间就没法玩了。
我们在处理vector中挪动数据的问题是不能用memcpy进行浅拷贝,我们应该开一块同一样的空间,把数据插入进去。

我们这里采取一种全新的方法

	void reserve(size_t n){//判断是否需要开空间if (n > capacity()){size_t old = size();//开空间t* tmp = new t[n];if (_start){//拷贝之前的数据到新空间//memcpy(tmp, _start, sizeof(t) * n);for (int i = 0; i < size(); i++){tmp[i] = _start[i];}//释放旧空间delete[]_start;}//更新指针_start = tmp;_finish = _start + old;_endofstorage = _start + n;}

tmp[i] = _start[i];就可以完成我们的任务。

void resize(size_t n,const T&val=T( ))

这个函数用于对空间开辟+初始化
我们来看const T&val=T( )这个,这个其实就是缺省值
对于自定义类型会调用它的默认构造,那麽对于内置类型也要调用它的默认构造,这个是为了适应模板才加入的。
比如int默认为0,char默认为’/0‘,double默认为0.0等等
在这里插入图片描述
对于resize的大小,有三种情况:
🌟 size()<n<capacity,我们仅需要插入数据即可
🌟 size()>capacity(),我们先需要开空间,再进行数据的插入
🌟 size()>n,我们需要对数据进行删除
我们可以把前两种情况合并到一起,直接开空间

void resize(size_t n,const T&val=T( ))
{//需要开空间if (n > capacity()){size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);//插入数据while (_finish < _start + n){*_finish = val;*_finish++;}}else{_finish = _start + n;}}

T& front() 和const T& front()const

获取vector中第一个元素

		T& front(){return *_start;}const T& front()const{return *_start;}

T& back()和const T& back() const

获取vector中最后有一个元素

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

void pop_back()

删除最后一个元素

		void pop_back(){assert(size() > 0);_finish--;}

iterator insert(iterator pos, const T& val)

在pos位置插入数据val

	void insert(iterator pos, const T& val){//进行断言assert(pos >= _start);assert(pos <= _finish);//先判断扩容if (_endofstorage == _finish){int len = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + len;}//挪动数据,必须从后往前挪iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = val;_finish++;}

我们来关注一下这段代码的实现

在这里插入图片描述
如果不加上的话程序会产生错误,就会发生迭代器失效
在这里插入图片描述

当程序发生扩容,原来的空间会进行销毁,vector中三个元素都会发生变化,而pos所处的位置不变,pos的那块空间被销毁,以后进行插入进找不到了。
在解决这类问题时:我们仅仅需要记录一下pos的位置就可以完成操作。

			iterator insert(iterator pos, const T & val){assert(pos >= _start);assert(pos <= _finish);//先判断扩容if (_endofstorage == _finish){int len = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + len;return pos;}//挪动数据,必须从后往前挪iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = val;_finish++;}

void erase(iterator pos)

删除pos位置的元素,erase也可能会引发迭代器失效

			void erase(iterator pos){//断言检查assert(pos >= _start);assert(pos < _finish);//有、数据移动iterator it = pos + 1;while (it < _finish){*(it+1= *it;it++;}//删除数据_finish--;}

我们来看一下这段代码

		vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print(v);auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);else {++it;}}}

这段代码其实是有问题的,如果erase发生缩容,pos是一个迭代器,位置就会发生变化,
有的编译器下size()减小就会发生缩容,那么空间就会发生变化,外边pos位置的值还是指向那块旧空间,我们再继续使用的话就会发生问题
我们画个图来看一下
在这里插入图片描述
我们在使用前,对迭代器进行重新赋值就可以

iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it-1) = *it;it++;}_finish--;return pos;}

vector(InputIterator first, InputIterator last)

一段迭代器区间初始化

		template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

指向连续物理空间的指针就是天然迭代器

        int a[] = { 100, 200, 300 };vector<int> v4(a, a+3);for (auto e : v4){cout << e << " ";}cout << endl;

完整代码

模板不能声明和定义在同一个文件中

namespace peng
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//初始化列表初始化vector(){	}size_t size(){return _finish - _start;}size_t capacity(){return _endofstorage - _start;}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}~vector(){if (_start){delete[]_start;_start = _finish = _endofstorage = nullptr;}}//拷贝构造vector<T>(const vector<T>& v){//开空间+尾插reserve(capacity());for (auto& e : v){push_back(e);}}//常见错误//void reserve(size_t n)//{//	//判断是否需要开空间//	if (n > capacity())//	{//		//开空间//		T* tmp = new T[n];//		if (_start)//		{//			//拷贝之前的数据到新空间//			memcpy(tmp, _start, sizeof(T) * n);//			//释放旧空间//			delete[]_start;//		}//		//更新指针//		_start = tmp;//		_finish = _start +size();//		_endofstorage = _start + capacity();//	}//}//void reserve(size_t n)//{//	//判断是否需要开空间//	if (n > capacity())//	{//		size_t old = size();//		//开空间//		T* tmp = new T[n];//		if (_start)//		{//			//拷贝之前的数据到新空间//			memcpy(tmp, _start, sizeof(T) * n);//			//释放旧空间//			delete[]_start;//		}//		//更新指针//		_start = tmp;//		_finish = _start + old;//		_endofstorage = _start + n;//	}//}void reserve(size_t n){//判断是否需要开空间if (n > capacity()){size_t old = size();//开空间T* tmp = new T[n];if (_start){//拷贝之前的数据到新空间//memcpy(tmp, _start, sizeof(t) * n);for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}//释放旧空间delete[]_start;}//更新指针_start = tmp;_finish = _start + old;_endofstorage = _start + n;}}void  push_back(const T & val){//插入之前判断扩容if (_finish == _endofstorage){//计算开多大空间size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);}//插入数据*_finish = val;_finish++;}///*	vector<T>& operator=(vector<T> v)//	{//		swap(v);//		return *this;//	}*/T& operator[](size_t pos){return _start[pos];}const T& operator[](size_t pos)const{return _start[pos];}void resize(size_t n, const T & val = T()){//需要开空间if (n > size()){size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);//插入数据while (_finish < _start + n){*_finish = val;*_finish++;}}else{_finish = _start + n;}}T& front(){return *_start;}T& back(){return *(_finish - 1);}const T& front()const{return *_start;}const T& back() const{return *(_finish - 1);}void pop_back(){assert(size() > 0);_finish--;}iterator insert(iterator pos, const T & val){assert(pos >= _start);assert(pos <= _finish);//先判断扩容if (_endofstorage == _finish){int len = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + len;return pos;}//挪动数据,必须从后往前挪iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = val;_finish++;}vector<T>& operator=(vector<T> v){swap(v);return *this;}//vector<T>& operator=(vector<T> v)//{//	//开空间+数据插入//	reserve(capacity());//	_start = new T[v.capacity()]; //这里要开辟容量的空间大小//	_finish = _start + v.size(); //指定里面存到的数据位置//	_endofstorage = _start + v.capacity();//	memcpy(_start, v._start, v.size() * sizeof(T));  //注意这里有一个Bug//	return *this;//}void swap(vector<T> &v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//void erase(iterator pos)//{//	assert(pos >= _start);//	assert(pos < _finish);//	iterator it = pos + 1;//	while (it < _finish)//	{//		*it = *(it + 1);//		it++;//	}//	_finish--;//}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it-1) = *it;it++;}_finish--;return pos;}//template<class InputIterator>//vector(InputIterator first, InputIterator last)//{//	while (first != last)//	{//		push_back(*first);//		++first;//	}//}vector(size_t n, const T& value = T()){resize(n,value);}private:T* _start = nullptr;T* _finish = nullptr;T* _endofstorage = nullptr;};}

总结

以上就是今天要讲的内容,本文仅仅详细介绍了C++vector的模拟实现,希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

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

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

相关文章

前端解决跨域问题( 6种方法 )

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

美团2025春招第一次笔试题

第四题 题目描述 塔子哥拿到了一个大小为的数组&#xff0c;她希望删除一个区间后&#xff0c;使得剩余所有元素的乘积未尾至少有k个0。塔子哥想知道&#xff0c;一共有多少种不同的删除方案? 输入描述 第一行输入两个正整数 n,k 第二行输入n个正整数 a_i&#xff0c;代表…

ARM TrustZone技术解析:构建嵌入式系统的安全扩展基石

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-dSk2aQ85ZR0zxnyI {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

【机器学习】进阶学习:详细解析Sklearn中的MinMaxScaler---原理、应用、源码与注意事项

【机器学习】进阶学习&#xff1a;详细解析Sklearn中的MinMaxScaler—原理、应用、源码与注意事项 这篇文章的质量分达到了97分&#xff0c;虽然满分是100分&#xff0c;但已经相当接近完美了。请您耐心阅读&#xff0c;我相信您一定能从中获得不少宝贵的收获和启发~ &#x1f…

free pascal 调用 C#程序读 Freeplane.mm文件,生成测试用例.csv文件

C# 请参阅&#xff1a;C# 用 System.Xml 读 Freeplane.mm文件&#xff0c;生成测试用例.csv文件 Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#x…

hadoop报错:HADOOP_HOME and hadoop.home.dir are unset. 解决方法

参考&#xff1a;https://blog.csdn.net/weixin_45735242/article/details/120579387 解决方法 1.下载apache-hadoop-3.1.0-winutils-master 官网下载地址&#xff1a; https://github.com/s911415/apache-hadoop-3.1.0-winutils win配置系统环境&#xff1a; 然后重启idea…

一文了解原型和原型链

本文重点概念&#xff1a; 1、所有的对象都是new一个函数创建的 2、所有的函数都有一个属性prototype&#xff0c;称为函数原型 3、函数原型得到的这个对象都有一个属性constructor,指向该函数 4、所有的对象都有一个属性&#xff1a;隐式原型__proto__&#xff0c;隐式原型…

机器学习,剪刀,石头,布

计算机视觉:剪刀,石头,步 TensorFlow AI人工智能及Machine Learning训练图集的下载建立分类模型并用图像进行训练检验模型总结当前AI Machine Learning 异常火爆,希望在MCU上使用机器学习,做图像识别的工作。看到一个剪刀,石头,步的学习程序,给大家分享一下。 TensorFl…

Buran勒索病毒通过Microsoft Excel Web查询文件进行传播

Buran勒索病毒首次出现在2019年5月&#xff0c;是一款新型的基于RaaS模式进行传播的新型勒索病毒&#xff0c;在一个著名的俄罗斯论坛中进行销售&#xff0c;与其他基于RaaS勒索病毒(如GandCrab)获得30%-40%的收入不同&#xff0c;Buran勒索病毒的作者仅占感染产生的25%的收入,…

AI创造的壁纸,每一幅都是视觉盛宴!

1、方小童在线工具集 网址&#xff1a; 方小童 该网站是一款在线工具集合的网站&#xff0c;目前包含PDF文件在线转换、随机生成美女图片、精美壁纸、电子书搜索等功能&#xff0c;喜欢的可以赶紧去试试&#xff01;

本地部署推理TextDiffuser-2:释放语言模型用于文本渲染的力量

系列文章目录 文章目录 系列文章目录一、模型下载和环境配置二、模型训练&#xff08;一&#xff09;训练布局规划器&#xff08;二&#xff09;训练扩散模型 三、模型推理&#xff08;一&#xff09;准备训练好的模型checkpoint&#xff08;二&#xff09;全参数推理&#xff…

2021年江苏省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书

2021年江苏省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书 一、赛项时间&#xff1a;二、赛项信息三、竞赛内容&#xff1a;第一阶段任务书&#xff08;300分&#xff09;任务1&#xff1a;网络平台搭建&#xff08;60分&#xff09;任务2&#xff1a;网络安全设备…

AI预测福彩3D第6弹【2024年3月11日预测--新算法重新开始计算日期】

由于周末休息了两天&#xff0c;没有更新文章&#xff0c;这两天也没有对福彩3D的预测。今天继续咱们使用AI算法来预测3D吧~ 前面我说过&#xff0c;我的目标是能让百十个各推荐7个号码&#xff0c;其中必有中奖号码&#xff0c;这就是7码定位&#xff0c;只要7码定位稳定了&am…

【前端系列】CSS 常见的选择器

CSS 常见的选择器 CSS&#xff08;层叠样式表&#xff09;是一种用于描述网页样式的标记语言&#xff0c;它定义了网页中各个元素的外观和布局。在 CSS 中&#xff0c;选择器是一种用于选择要应用样式的 HTML 元素的模式。选择器允许开发人员根据元素的类型、属性、关系等来选…

JVM3_数据库连接池虚引用ConnectionFinalizerPhantomReference引起的FullGC压力问题排查

背景 XOP服务运行期间&#xff0c;查看Grafana面板&#xff0c;发现堆内存周期性堆积&#xff0c;观察FullGC的时间&#xff0c;xxx&#xff0c;需要调查下原因 目录 垃圾收集器概述 常见的垃圾收集器分区收集策略为什么CMS没成为默认收集器 查看JVM运行时环境分析快照 Pha…

基于PCtoLCD实现OLED汉字取模方法

0 工具准备 PCtoLCD2002 NodeMCU&#xff08;ESP8266&#xff09;&#xff08;验证OLED字模效果&#xff09; 0.96寸OLED显示屏 1 基于PCtoLCD实现OLED汉字取模方法 1.1 基础知识介绍 0.96存OLED显示屏包含128x64个像素点&#xff0c;x轴方向为128个像素点&#xff0c;y轴方向…

[AutoSar]BSW_Com011 CAN IF 模块配置

目录 关键词平台说明一、CanIfCtrlDrvCfgs二 、CanIfTrcvDrvCfgs三、CanIfDispatchCfg四、CanIfBufferCfgs五、CanIfHrhCfgs六、CanIfHthCfgs七、CanIfRxPduCfgs八、CanIfTxPduCfgs九、CanIfPrivateCfg十、CanIfPublicCfg 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 …

目前最强大语言模型!谷歌开源 | 开源日报 No.196

google/gemma_pytorch Stars: 3.4k License: Apache-2.0 gemma_pytorch 是 Google Gemma 模型的官方 PyTorch 实现。 提供了 Gemini 模型技术的轻量级、最新开放模型支持文本到文本、仅解码器大语言模型提供英文版本&#xff0c;包含开源权重、预训练变体和指导调整变体支持…

natfrp和FRP配置SSL的基本步骤和bug排查

获取免费/付费SSL 我直接买了一年的ssl证书 设置 主要参考&#xff1a;https://doc.natfrp.com/frpc/ssl.html 遇到的Bug root域名解析是ALIAS&#xff0c;不是CNAME不要用NATFRP &#xff08;SakuraFrp&#xff09;同步Joplin&#xff0c;会出现webdav错误导致大量笔记被…

linux上安装fastdfs及配置

一、基础环境准备 1、所需软件 名称说明libfastcommonfastdfs分离出的一些公用函数包fastdfsfastdas软件包fastdfs-nginx-modulefastdfst和nginx的关联模块nginxnginxl软件包 2、编辑环境 安装一些基础的支持环境 yum install git gccc gcc-c make automake autoconf libto…