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

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

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

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.mfbz.cn/a/151346.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分类预测 | Matlab实现PSO-LSTM-Attention粒子群算法优化长短期记忆神经网络融合注意力机制多特征分类预测

分类预测 | Matlab实现PSO-LSTM-Attention粒子群算法优化长短期记忆神经网络融合注意力机制多特征分类预测 目录 分类预测 | Matlab实现PSO-LSTM-Attention粒子群算法优化长短期记忆神经网络融合注意力机制多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1…

飞书开发学习笔记(七)-添加机器人及发送webhook消息

飞书开发学习笔记(七)-添加机器人及发送webhook消息 一.添加飞书机器人 1.1 添加飞书机器人过程 在群的右上角点击折叠按键…选择 设置 群机器人中选择 添加机器人 选择自定义机器人&#xff0c;通过webhook发送消息 弹出的信息中有webhook地址&#xff0c;选择复制。 安…

【Linux专题】SFTP 用户配置 ChrootDirectory

【赠送】IT技术视频教程&#xff0c;白拿不谢&#xff01;思科、华为、红帽、数据库、云计算等等https://xmws-it.blog.csdn.net/article/details/117297837?spm1001.2014.3001.5502 红帽认证 认证课程介绍&#xff1a;红帽RHCE9.0学什么内容&#xff0c;新版有什么变化-CSDN…

任正非说:10%的特殊场景就像牛在路上,谁也不知道它会在哪拉屎

你好&#xff01;这是华研荟【任正非说】系列的第40篇文章&#xff0c;让我们聆听任正非先生的真知灼见&#xff0c;学习华为的管理思想和管理理念。 一、我们要建立核心生产能力&#xff0c;否则我们对供应链理解不深&#xff0c;供应链不能打通。我们之所以管道系统做得好&am…

修改树莓派4b密码

修改树莓派4b密码&#xff0c;vnc viewer远程连接树莓派时忘记了密码&#xff0c;修改为新密码进行远程连接 sudo passwd pi 其中pi为所要修改密码的用户

Java 设计模式——中介者模式

目录 1.概述2.结构3.案例实现3.1.抽象中介类3.2.抽象同事类3.3.具体同事类3.4.具体中介类3.5.测试 4.优缺点5.使用场景 1.概述 &#xff08;1&#xff09;一般来说&#xff0c;同事类之间的关系是比较复杂的&#xff0c;多个同事类之间互相关联时&#xff0c;他们之间的关系会…

IDEA这样配置Maven:让你一遍就能学会!

一、安装Maven环境 1.1 下载并安装Maven Maven官网&#xff1a;http://maven.apache.org/download.cgi 建议放在非系统盘目录下&#xff0c;可在根目录新建&#xff08;D:/maven&#xff09;目录用于存放Maven&#xff0c;或者如图&#xff0c;路径中不要有中文。 1.2 配置M…

AIGC实战——变分自编码器(Variational Autoencoder, VAE)

AIGC实战——变分自编码器 0. 前言1. 变分自编码器1.1 基本原理1.2 编码器 2. 构建VAE编码器2.1 Sampling 层2.2 编码器2.3 损失函数2.4 训练变分自编码器 3. 变分自编码器分析小结系列链接 0. 前言 我们已经学习了如何实现自编码器&#xff0c;并了解了自编码器无法在潜空间中…

轻量封装WebGPU渲染系统示例<33>- 单精度浮点纹理(源码)

在WebGPU中创建纹理使用纹理很方便&#xff0c;只是js中只有Float32Array而默认不支持Float16Array&#xff0c;所以略微费点事。不过网上的大神多的是&#xff0c;摇摇小手就能获得解决方案。 废话多了容易挨胖揍&#xff0c;看代码。 js中float16单精度float数值转换: // …

[C/C++] 数据结构 链表OJ题:相交链表(寻找两个链表的相交起始结点)

题目描述: 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返…

JSP 购物商城系统eclipse定制开发mysql数据库BS模式java编程servlet

一、源码特点 java 购物商城系统是一套完善的web设计系统 系统采用serlvetdaobean 模式开发&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模 式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数…

CentOS停更在即,国内厂商该如何应对?KeyarchOS X2Keyarch 迁移体验

一、CentOS 停更危机二、关于浪潮信息KeyarchOS三、浪潮信息 KeyarchOS License 应用迁移实践第一步&#xff1a;迁移前验证第二步&#xff1a;迁移第三步&#xff1a;迁移后验证 四、写在最后 一、CentOS 停更危机 自 1993 年开始&#xff0c;红帽 Linux 已经陪伴开发者们走过…

荧光量子效率积分球检测薄膜需要注意什么

荧光量子效率积分球是一种特殊的积分球&#xff0c;它可以用于测量荧光材料在特定波长下的荧光量子效率。它由一个具有高朗伯特性的漫反射材料制成&#xff0c;具有高达99%的反射率和朗伯特性。荧光量子效率积分球的使用方法包括将样品放置在积分球的样品口中&#xff0c;调整激…

【C++高阶(二)】熟悉STL中的map和set --了解KV模型和pair结构

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; map和set 1. 前言2. map和set介绍3. pair结构介…

JWT登录认证(1登录)

JwtUtil package com.lin.springboot01.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import java.util.Date; import java.util.Map;public class JwtUtil {private static final String KEY "liner2332";//接受业务数据&#xf…

Python in Visual Studio Code 2023年11月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布 Visual Studio Code 的 Python 和 Jupyter 扩展将于 2023 年 11 月发布&#xff01; 此版本包括以下公告&#xff1a; 改进了使用 Shift Enter 在终端中运行当前行弃用内置 linting 和格式设置功能对 Python linting 扩展的改进重…

linux安装nginx并配置服务的详细步骤

文章目录 依赖安装安装gcc环境安装 pcre安装zlib安装openssl 安装Nginx在nginx官网下载安装包将安装包上传linux解压文件手动创建用户和用户组编译目录编译源码并安装启动查看进程 设置nginx服务并开机自启 依赖安装 nginx安装前需要一些依赖&#xff0c;如果已经安装了则忽略…

大数据Doris(二十三):取消导入与其他导入案例参考

文章目录 取消导入与其他导入案例参考 一、取消导入

Django(七、模型层)

文章目录 模型层模型层前期准备使用django ORM要注意 代码演示&#xff1a;切换MySQL数据库如何查看django ORM 底层原理&#xff1f; 单表操作模型层之ORM常见关键字基础的增删改查常用的关键字 常见的十几种查询基于双下滑线的查询 模型层 模型层前期准备 使用django ORM要…

【Qt之QWizardPage】使用

介绍 QWizardPage类是向导页面的基类。 QWizard表示一个向导。每个页面都是一个QWizardPage。当创建自己的向导时&#xff0c;可以直接使用QWizardPage&#xff0c;也可以子类化它以获得更多控制。 页面具有以下属性&#xff0c;由QWizard呈现&#xff1a;a title&#xff0c;…
最新文章