【C++干货铺】解密vector底层逻辑

news2024/11/15 2:02:11

=========================================================================

个人主页点击直达:小白不是程序媛

C++系列专栏:C++干货铺

代码仓库:Gitee

=========================================================================

目录

vector介绍

vector的使用

vector的定义和使用

vector的空间增长问题

vector的增删查改

解密vector及模拟实现

成员变量

成员函数

构造函数

拷贝构造函数

operator = 

析构函数

reserve

push_back

erase

 resize

insert

operator [ ] 

迭代器

size

capacity


vector介绍

1. vector是表示可变大小数组的序列容器
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。 


vector的使用

vector的使用和string差不多,有很多的接口供我们函数调用。这些函数的功能包括增、删、查、改等。

vector的定义和使用

接口函数名称    

接口函数功能

vector()无参构造
vector(size_type n,const value_type&val=value_type())构造并初始化n个val
vector(const vector& x)拷贝构造
vector (InputIterator first, InputIterator last)使用迭代器进行初始化构造
begin+end使用迭代器对实例化的对象进行读取
operator[ ]使用数组的方式对实例化的对象读取
范围for使用范围for的方式对实例化的对象读取
	vector <int> v1;
	vector <int> v2(10, 1);
	for (size_t i = 0; i < v2.size(); i++)
	{
		cout << v2[i] << " ";
	}
	cout << endl;
	vector<int>::iterator it = v2.begin();
	while (it != v2.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	string s1("hello world");
	vector<char> v3(s1.begin(), s1.end());
	for (auto ch : v3)
	{
		cout << ch << " ";
	}
	cout << endl;
	vector<char> v4(v3);
	for (auto ch : v4)
	{
		cout << ch << " ";
	}
	cout << endl;

vector的空间增长问题

接口函数接口函数的作用
size获取有效数据的个数
capacity获取容量大小
resize改变vector的size
reserve改变vector的capacity
	vector<int> v1;
	
	size_t sz = v1.capacity();
	cout << "初始容量:" << sz << endl;
	for (size_t i = 0; i < 100; i++)
	{
		v1.push_back(i);
		if (sz != v1.capacity())
		{
			sz = v1.capacity();
			cout << "容量: " << sz << endl;
		}
	}

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size。

vector的增删查改

接口函数接口函数的作用
push_back尾插
pop_back尾删
insert在pos位置插入val
erase删除pos位置的数据
	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 i : v)
	{
		cout << i << " ";
	}
	cout << endl;
	v.pop_back();
	for (auto i : v)
	{
		cout << i << " ";
	}
	cout << endl;
	vector<int>::iterator pos = v.begin();
	v.insert(pos, 1);
	for (auto i : v)
	{
		cout << i << " ";
	}
	cout << endl;
	v.erase(v.begin());
	for (auto i : v)
	{
		cout << i << " ";
	}
	cout << endl;


解密vector及模拟实现

 

成员变量

	//指向动态开辟空间的开始
	iterator _start;
	//指向最后一个有效数据的结尾
	iterator _finish;
	//指向动态开辟空间的结尾
	iterator _end;

vector是通过三个指针实现的,并不像我们在数据结构初阶那样使用一个指向动态开辟的指针和两个变量来实现。三个指针都指向的是动态开辟的空间,只不过各司其职;第一个指针直接指向动态开辟空间,第二个指针指向动态开辟空间中有效数据的个数的下一位,第三个指针指向的是动态开辟空间的结尾。

成员函数

构造函数

	//传入数据类型的指针
	typedef T* iterator;
	//静态
	typedef const T* const_iterator;
	//构造函数
	Vector()
		:_start(nullptr)
		, _finish(nullptr)
		, _end(nullptr)
	{}

使用初始化列表对成员变量进行初始化为空指针

拷贝构造函数

	Vector(const Vector<T>& x)
		:_start(nullptr)
		, _finish(nullptr)
		, _end(nullptr)
	{
		reserve(x.capacity());
		for (auto e : x)
		{
			push_back(e);
		}
	}

operator = 

	void swap(Vector<T>& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_end, v._end);
	}
	Vector<T>& operator=( Vector<T>& tmp)
	{
		swap(tmp);
		return *this;
	}

析构函数

	~Vector()
	{
		delete[] _start;
		_start = _finish = _end = nullptr;
	}

reserve

	void reserve(size_t n)
	{
		
		if (n > capacity())
		{
			T* tmp = new T[n];
			//记录开始和有效数据结尾中间的有效数据个数防止,迭代器失效
			size_t sz = size();
			//判断原指针是否为空指针,第一次开辟空间是空指针
			if (_start)
			{
				//将有效数据的字节数拷贝到新空间中
                //memcpy函数只会进行的是浅拷贝,对于自定以类型无法完成深拷贝
				//memcpy(tmp, _start, sizeof(T) * sz);
				for (size_t i = 0; i < sz; i++)
				{
                    //赋值重载会完成深拷贝
					tmp[i] = _start[i];
				}
				//释放旧空间
				delete[] _start;
			}

			_start = tmp;
			//重置指针,解决迭代器失效问题
			_finish = _start + sz;
			_end= _start + n;
		}
	}

这里我们要进行空间的扩容,先将size记录下来;开辟好新的空间后根据size和第一个指针重置第二个指针;根据第一个指针和开辟好空间的容量重置第三个指针。

push_back

	void push_back(const T& x)
	{
		//需不需要扩容
		if (_finish == _end)
		{
			//第一次容量为空
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}
		//尾插数据,移动指针
		*_finish = x;
		_finish++;
	}

尾插根据第二个和第三个指针判断容量的大小,是否需要扩容;注意:第一次扩容时使用三目操作符进行判断;

erase

	void /*iterator*/ erase(iterator pos)
	{
		assert(pos < _finish);
		iterator begin = pos + 1;
		//移动数据
		while (begin < _finish)
		{
			*(begin - 1) = *(begin);
			begin++;
		}
		//修改指针;
		_finish--;
		//return pos;
	}

 resize

	void resize(size_t n , const T& val=T())
	{
		if (n < size())
		{
			//重置的大小小于当前大小
			//修改指针即可
			_finish = _start + n;
		}
		else
		{
			//两种情况
			//重置的大小小于大于当前容量  需要扩容
			//重置的大小小于小于当前容量  不用扩容直 接放数据
			if (n > capacity())
			{
				reserve(n);
			}
			while (_finish < _start + n)
			{
				*_finish = val;
				_finish++;
			}
		}
	}

insert

	void insert(iterator pos, const T& x)
	{
		//判断容量
		if (_finish == _end)
		{
			//记录旧空间的pos位置
			size_t sz = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			//在新空间中找到pos位置
			pos = _start + sz;
		}
		auto end = _finish - 1;
		while (end >= pos)
		{
			*(end + 1) == *end;
			end--;
		}
		*pos = x;
		_finish++;
	}

这里扩容后改指向旧空间pos是野指针,程序会崩溃;要在扩容前配合第一个指针记录此时的位置,扩容后进行重置。

operator [ ] 

	//可读可写
    T& operator[](size_t pos)
	{
		return _start[pos];
	}
    //只读
	const T& operator[](size_t pos) const
	{
		return _start[pos];
	}

迭代器

    //可读可写
    iterator begin()
	{
		return _start;
	}
	iterator end()
	{
		return _finish;
	}
	//只读
    const_iterator begin()const
	{
		return _start;
	}
	const_iterator end()const
	{
		return _finish;
	}

size

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

capacity

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

今天对vector的介绍和底层模拟实现的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法。您三连的支持就是我前进的动力,感谢大家的支持!!! 

 

 

 

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

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

相关文章

23000 个恶意流量代理的 IPStorm 僵尸网络被拆除

美国司法部今天宣布&#xff0c;联邦调查局取缔了名为 IPStorm 的僵尸网络代理服务的网络和基础设施。 IPStorm 使网络犯罪分子能够通过世界各地的 Windows、Linux、Mac 和 Android 设备匿名运行恶意流量。 与此案相关的俄罗斯裔摩尔多瓦籍公民谢尔盖马基宁 (Sergei Makinin)…

Java-绘图

文章目录 Java绘图Java绘图类绘图颜色与画笔属性设置颜色设置画笔 绘制文本显示图片图像处理1、放大与缩小2、图像翻转3、图像旋转4、图像倾斜 End Java绘图 Java绘图是指在Java程序中创建和显示图形的过程。Java提供了许多类和方法来支持绘图。 Java绘图类 Java中主要的绘图类…

软件测试个人求职简历该怎么写,模板在这里

1、个人资料 姓 名&#xff1a;xxx 性 别&#xff1a;x 手机号码&#xff1a;138888888xx 邮 箱&#xff1a; xxx 学 历&#xff1a;本科 专 业&#xff1a;电子商务 英 语&#xff1a;四级 当前工作&#xff1a;测试工程师 从业时间&#xff1a;4年 期望薪资&#xff1a;…

[Linux] 网络文件共享服务

一、存储类型 存储类型可分为三类&#xff1a;DAS&#xff08;直连式存储&#xff09;,NAS&#xff08;网络附加存储&#xff09;,SAN&#xff08;存储区域网络&#xff09;。 1.1 DAS 定义&#xff1a; DAS是指直连存储&#xff0c;即直连存储&#xff0c;可以理解为本地文…

Vue3使用i18n国际化

安装 npm install vue-i18nnext 创建i18n文件夹 我这个项目是中、俄语言切换 zh.ts里放中文语言下要显示的字段&#xff0c;rn.ts里放俄语要显示的字段 index.ts import { createI18n } from vue-i18n; import ZH from ./zh.js; import RN from ./rn.js; const messages {zh…

echarts官网卡?

全网echarts案例资源大总结和echarts的高效使用技巧&#xff08;细节版&#xff09; - 掘金 drawnLine() {let myChart echarts.init(document.getElementById("grade"));// 绘制图表myChart.setOption({title: {left: "center",},tooltip: {trigger: &qu…

有效数字(表示数值的字符串),剑指offer,力扣

目录 题目地址&#xff1a; 我们直接看题解吧&#xff1a; 难度分析&#xff1a; 解题方法&#xff1a; 审题目事例提示&#xff1a; 解题思路&#xff1a; 代码实现&#xff1a; 题目地址&#xff1a; LCR 138. 有效数字 - 力扣&#xff08;LeetCode&#xff09; 难度&#xf…

利用Nextcloud搭建企业私有云盘系统

利用Nextcloud搭建企业私有云盘系统 1. 场景介绍2. 环境准备3. 安装NextCloud4. 系统功能验证 1. 场景介绍 Nextcloud是一款免费开源的私有云存储系统&#xff0c;采用PHPMySQL开发&#xff0c;提供了多个同步客户端支持多种设备访问&#xff0c;使用Nextcloud可以快速便捷地搭…

二、项目的运行环境

项目的运行环境 一、概述 项目所处的环境可能对项目的开展产生有利或不利的影响&#xff1a; 事业环境因素组织过程资产 二、事业环境因素 资源可用性&#xff1a;例如包括合同和采购制约因素、获得批准的供应商和分包商以及合作协议&#xff1b; 法律限制、政府或行业标准…

远程创建分支本地VScode看不到分支

在代码存放处右击&#xff0c;点击Git Bash Here 输入git fetch–从远程仓库中获取最新的分支代码和提交历史 就OK啦&#xff0c;现在分支可以正常查看了

游戏报错找不到xinput1_3.dll如何解决呢?分享5个解决方法对比

由于找不到xinput1_3.dll,无法继续执行代码的5个解决方法与丢失原因分享。 xinput1_3.dll是一个动态链接库文件&#xff0c;它包含了一些重要的函数和数据结构&#xff0c;用于支持游戏手柄等设备的操作。当这个文件丢失或损坏时&#xff0c;就会导致程序无法正常运行。 那么…

手机,蓝牙开发板,TTL/USB模块,电脑四者之间的通讯

一,意图 通过手机蓝牙连接WeMosD1R32开发板,开发板又通过TTL转USB与电脑连接.手机通过蓝牙控制开发板上的LED灯的开,关,闪等动作,在电脑上打开串口监视工具观察其状态.也可以通过电脑上的串口监视工具来控制开发板上LED灯的动作,而在手机蓝牙监测工具中显示灯的状态. 二,原料…

每天学习一点点之从 SonarQube Code Smell 看 Serializable

相关文章&#xff1a; 每天学习一点点之从 SonarQube Bug 看对线程中断异常的处理 昨天组内同学在进行代码合并的时候发现了一个 SonarQube 异常&#xff1a; 其实我之前也遇到过这个异常&#xff0c;但觉得“这种异常很无聊”&#xff0c;毕竟让 Spring Bean 去序列化&…

d3dcompiler_43.dll丢失了怎么办,详细解答和d3dcompiler_43.dll修复方法

以下将为您提供几种处理d3dcompiler_43.dll文件丢失的解决措施&#xff0c;这些方法实用有效&#xff0c;可以帮助我们恢复计算机运行。 一.d3dcompiler_43.dll是什么 在我们开始探讨如何修复d3dcompiler_43.dll文件丢失的问题之前&#xff0c;首先需要了解这个文件的作用。该…

如何选择正确的SSL证书?

SSL证书已经成为网站安全管理的重要部分&#xff0c;但是市场上SSL证书种类繁多&#xff0c;很多新手在初次购买时都会感到困惑。下面我们就一起来看看如何快速地选择正确的SSL证书。 第一步&#xff1a;明确SSL证书的主要分类 SSL证书主要有三种类型&#xff1a;单域名证书、…

2023selenium自动化(基础篇)

哈喽大家好&#xff0c;我是静姐&#xff0c;今天来给大家介绍在Python中使用selenium进行自动化操作 定义 Selenium是一个用于Web应用程序测试的自动化测试工具。使用Selenium可以驱动浏览器执行特定的动作,如点击、下拉等操作,还可以获取页面信息,断言页面是否如预期。在工作…

Oracle如何快速删除表中重复的数据

方法一&#xff1a; 在Oracle中&#xff0c;你可以使用DELETE语句结合ROWID和子查询来删除重复的记录。以下是一个示例&#xff1a; DELETE FROM your_table WHERE ROWID NOT IN (SELECT MAX(ROWID)FROM your_tableGROUP BY column1, column2, ... -- 列出用于判断重复的列 )…

【动态规划】买卖股票的最佳时期含冷冻期

文章目录 一、买卖股票的最佳时期含冷冻期动态规划五部曲 一、买卖股票的最佳时期含冷冻期 题目: 买卖股票的最佳时期含冷冻期 动态规划五部曲 1.确定dp的含义 由题意可知&#xff0c;这里有三种状态 1.买入状态&#xff1a;dp[i][0]:表示第i天处于买入状态时的最大利润为dp[…

C# Socket通信从入门到精通(9)——如何设置本机Ip地址

前言&#xff1a; 我们开发好Socket通信程序以后&#xff0c;上机调试的时候&#xff0c;首先要做的就是先设置好电脑的IP&#xff0c;这样才能实现不同的电脑之间的通信&#xff0c;并且电脑1的ip地址和电脑2的Ip地址要同处于一个网段&#xff0c;比如电脑1的Ip地址为192.168…

“程序员们的奔溃瞬间”——分享你最令你哭笑不得的程序员经历

文章目录 每日一句正能量前言编程趣事后记 每日一句正能量 每件事最后都会是好事。如果不是好事&#xff0c;说明还没到最后。 前言 作为程序员&#xff0c;我们时常会遇到各种奇怪的错误和挑战&#xff0c;有时候我们会崩溃&#xff0c;但更多的时候&#xff0c;我们会从中学…