Vector底层实现详解

news2024/11/25 6:10:28

一、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 

2.1 创建一个vector对象

1.无参构造 vector()(重点)

eg:  vector<int> first;

2.vector(size_type n, const value_type& val = value_type()) 构造并初始化n个val

eg:  vector<int> second(4, 100);

3.vector (const vector& x); 拷贝构造

vector<int> fourth(third);

4.vector (InputIterator first, InputIterator last) 使用迭代器进行初始化构造

vector<int> third(second.begin(), second.end());

2.2 vector迭代器的使用

1.begin 和 end 的使用

begin是获取第一个数据位置的iterator/const_iterator, end获取最后一个数据的下一个位置 的iterator/const_iterator

vector<int>::iterator begin = v1.begin();
vector<int>::iterator end = v1.begin();

这里注意:vector不是类类型了,是一个模板类,vector<数据类型> 才是

 2.3 vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了 封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的 空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)

对于vector可能会导致其迭代器失效的操作有:

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back.......( 扩容)
  2. 指定位置元素的删除操作--erase
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));

	// 使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);

	// 删除pos位置的数据,导致pos迭代器失效。
	v.erase(pos);
	cout << *pos << endl; // 此处会导致非法访问

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代 器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了

 迭代器失效解决办法:在使用前,对迭代器重新赋值即可

三、vector深度剖析及模拟实现

1. 构造以及析构函数

template<class T>
class vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;
    // 强制编译器生成默认的构造函数
    vector() = default;

	// 函数模板 -- 目的支持任意容器的迭代器区间初始化
	template <class InputIterator>
	vector(InputIterator first, InputIterator last)
	{
		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}
  
	{
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}
	vector(initializer_list<T> il)
	{
		reserve(il.size());
		for (auto e : il)
		{
			push_back(e);
		}
	}

	vector(int n, const T& val = T())
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}
	~vector()
	{
		if (_start)
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
	}

private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

vector(int n, const T& val = T())

这里使用到了匿名对象,实际上c++11以后,对内置类型进行了升级,是内置类型也有构造


    int k = int();
    int j(1);
    int x = int(2)
 


2.对迭代器的使用

template<class T>
class vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;
//访问只读迭代器
    const_iterator begin() const
    {
    	return _start;
    }

    const_iterator end() const
    {
	    return _finish;
    }

    const T& operator[](size_t i)const
    {
	    assert(i < size());

	    return _start[i];
    }

    //访问可读可写迭代器
    iterator begin()
    {
	    return _start;
    }

    iterator end()
    {
	    return _finish;
    }
    T& operator[](size_t i)
    {
    	assert(i < size());

    	return _start[i];
    }


  
private:
	iterator _start = nullptr; //指向第一个数据
	iterator _finish = nullptr;//指向最后一个数据的下一个
	iterator _end_of_storage = nullptr;//指向数据元素空间末尾的下一个位置
};

3. 扩容

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldsize = size();
		T* tmp = new T[n];

		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * old_size);
			for (size_t i = 0; i < oldsize; i++)
			{
				tmp[i] = _start[i];
			}

			delete[] _start;
		}

		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

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

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

n是需要的空间大小,当n大于capacity时开辟n个大小新空间

这里注意迭代器失效,原因在于_start指向新开的空间tmp,但是我们并未改变 _finish  与 _end_of_storage 指向,如果我们使用_finish = size() + _start就错了,因为size()返回 _finish - _start,  _start指向新空间,_finish 还是指向旧空间,这时二者相减得到的结果并不正确,也就是_finish位置迭代器失效

解决办法:记录原来的数据个数,直接用新 _start + oldsize 即可得到新的_finish

4. 插入 、删除


		void push_back(const T& x)
		{
			/*if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}

			*_finish = x;
			++_finish;*/

			insert(end(), x);
		}

		void pop_back()
		{
			assert(size() > 0);

			--_finish;
		}

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;

				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);

				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = x;
			++_finish;

			return pos;
		}

		// 迭代器

		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				++it;
			}

			--_finish;
		}

push_back :  当空间大小不足时,若是没有新空间(_end_of_storage - _start == 0),用newcapacity开4块大小为T的空间,若是有空间但满了,则扩大至两倍

pop_back:删除不会导致空间变小,直接--finish即可

insert:注意记录扩容前要移动size大小,扩容后pos变成野指针,迭代器失效,需要更新

erase:注意erase后的pos位置迭代器失效了

5.拷贝构造和operator=的现代写法 

		// v2(v1)
		vector(const vector<T>& v)
		{
			/*reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}*/
			vector<T> v1(v.begin(), v.end());
			swap(v1);
		}

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

		// v1 = v3
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

拷贝构造:直接调用构造深拷贝一份v,再把v1和*this交换,除了作用域v1自动销毁,既不影响v又不用自己再写

operator=:直接在参数调用拷贝构造深拷贝一份v,直接交换,出作用域自动销毁

四 、用vector实现杨辉三角

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> v;
        //先把所有两侧为值初始化为1
        for(int i = 0;i<numRows;i++)
        {
            v.push_back(vector<int>(i+1,1));
        }
        for(int i = 2;i<numRows;i++)
        {
            for(int j = 1;j<i;j++)
            {
                v[i][j] = v[i-1][j-1] + v[i-1][j];
            }
        }
        return v;
    }
};

先进行一次遍历,把第 i-n 层都放进去 i+1 个 1  

再进行一次遍历,把中间部分 = 上一层左+右即可

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

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

相关文章

【2024 Optimal Control 16-745】【Lecture4】equality-constraints.ipynb功能分析

代码实现了一个二次优化问题的可视化解法&#xff0c;包括目标函数、约束以及优化路径。提供了两种优化方法&#xff1a;牛顿法和高斯-牛顿法&#xff0c;用于对比效果。利用了自动微分工具 ForwardDiff 来计算约束曲率。 环境初始化并导入依赖库 # 激活当前文件夹下的项目环境…

【国产MCU】-GD32F470-串行外设接口(SPI)

串行外设接口(SPI) 文章目录 串行外设接口(SPI)1、SPI介绍1.1 SPI特性1.2 SPI信号1.3 SPI 时序和数据帧格式1.4 NSS 功能1.5 SPI运行模式2、SPI控制器寄存器列表3、SPI控制器驱动API介绍4、SPI应用4.1 SPI初始化流程4.2 数据发送与接收串行外设接口(Serial Peripheral Int…

Docker安装ubuntu1604

首先pull镜像 sudo docker run -d -P m.daocloud.io/docker.io/library/ubuntu:16.04国内使用小技巧&#xff1a; https://github.com/DaoCloud/public-image-mirror pull完成之后查看 sudo docker images 运行docker sudo docker run -d -v /mnt/e:/mnt/e m.daocloud.io/…

2024 年:Kubernetes 包管理的新前沿

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;历代文学&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编程&#xff0c;高并发设计&#xf…

飞凌嵌入式T113-i开发板RISC-V核的实时应用方案

随着市场对嵌入式设备的功能需求越来越高&#xff0c;集成了嵌入式处理器和实时处理器的主控方案日益增多&#xff0c;以便更好地平衡性能与效率——实时核负责高实时性任务&#xff0c;A核处理复杂任务&#xff0c;两核间需实时交换数据。然而在数据传输方面&#xff0c;传统串…

VSCode 汉化教程【简洁易懂】

VSCode【下载】【安装】【汉化】【配置C环境&#xff08;超快&#xff09;】&#xff08;Windows环境&#xff09;-CSDN博客 我们安装完成后默认是英文界面。 找到插件选项卡&#xff0c;搜索“Chinese”&#xff0c;找到简体&#xff08;更具你的需要&#xff09;&#xff08;…

【Mac】VMware Fusion Pro 安装 CentOS 7

1、下载镜像 CentOS 官网阿里云镜像网易镜像搜狐镜像 Mac M1芯片无法直接使用上述地址下载的最新镜像&#xff08;7.9、9&#xff09;&#xff0c;会一直卡在安装界面&#xff08;在 install 界面按 enter 回车无效&#xff09;&#xff0c;想要使用需要经过一系列操作&#…

运维Tips:Docker或K8s集群拉取Harbor私有容器镜像仓库配置指南

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Docker与Kubernetes集群拉取Harbor私有容器镜像仓库配置 描述&#xff1a;在现在微服务、云原生的环境下&#xff0c;通常我们会在企业中部署Docker和Kubernetes集群&#xff0c;并且会在企业内部…

C语言笔记(自定义类型:结构体、枚举、联合体 )

前言 本文对自定义类型的结构体创建、使用、结构体的存储方式和对齐方式&#xff0c;枚举的定义、使用方式以及联合体的定义、使用和存储方式展开叙述&#xff0c;如有错误&#xff0c;请各位指正。 目录 前言 1 结构体 1.1 结构体的声明 1.2 结构体的自引用 1.3 结构体变…

string的实际应用 -- 大数相加 、大数相乘

前言&#xff1a;哎&#xff0c;做题好难o(╥﹏╥)o&#xff0c;有时候想不到&#xff0c;而有时候则是想到了却没办法理清思路&#xff0c;转化为代码。有必要反思了┓(;_&#xff40;)┏&#xff0c;是否是做的太少了&#xff0c;或是自己的基础欠缺。 大学总是有些迷茫~ ​​…

STM32-- keil 的option for target使用

keil版本号 1.device界面 如&#xff1a;stm32f103c8t6的工程&#xff0c;可以直接在device这里修改成stm32f103vct6&#xff0c;虽然引脚不一样&#xff0c;但是很多一样的地方&#xff0c;可以直接使用&#xff0c;有些不修改也可以下载程序。 2.target xtal的设置不起作用了…

shell脚本9完结,保姆篇---春不晚

免责声明 学习视频来自 B 站up主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 泷羽sec官网&#xff1a;http…

【数据分享】2024年我国省市县三级的住宿服务设施数量(8类住宿设施/Excel/Shp格式)

宾馆酒店、旅馆招待所等住宿服务设施的配置情况是一个城市公共基础设施完善程度的重要体现&#xff0c;一个城市住宿服务设施种类越丰富&#xff0c;数量越多&#xff0c;通常能表示这个城市的公共服务水平越高&#xff01; 本次我们为大家带来的是我国各省份、各地级市、各区…

RabbitMQ和RocketMQ相关面试题

RabbitMQ和RocketMQ面试题 RabbitMQ1.RabbitMQ各部分角色2.如何确保RabbitMQ消息的可靠性&#xff1f;3.什么样的消息会成为死信&#xff1f;4.死信交换机的使用场景是什么&#xff1f;5.TTL6.延迟队列7.消息堆积问题8.MQ集群 RocketMQ1.RocketMQ各部分角色2.RocketMQ如何保证高…

在kali用msfpc远程控制Windows

本次实验我们将使用msfpc生成windows下的被控端&#xff0c;并使用metasploit渗透工具进行远程控制。 一、实验环境 Windows主机IP&#xff1a; 192.168.167.1 虚拟机Kali IP&#xff1a; 192.168.167.100 二、实验过程 1、安装msfpc apt-get install msfpc 2、生成windows…

SDIO WIFI模组Clock EMC问题

问题&#xff1a; 某产品采用SDIO3.0的WIFI模组&#xff0c;测试3米场地辐射出现333MHz和500MHz频点超标。 分析&#xff1a; 1、一开始分析板子上没有对应333MHz,499.5MHz的频点倍频&#xff0c;因此直接拔掉产品上所有的外部接线&#xff0c;测试还是超标。表明辐射源头出…

MCU(一) 时钟详解 —— 以 GD32E103 时钟树结构为例

微控制器 (MCU) 的时钟系统是系统运行的核心&#xff0c;它提供了各模块所需的时钟信号。本文以 GD32E103 系列 MCU 为例&#xff0c;详细讲解其 时钟树结构&#xff08;Clock Tree&#xff09;。通过理解时钟源、分配与预分频器设置&#xff0c;可以灵活配置系统时钟以实现高性…

【方案库】从单张照片快速重建3D场景:Flash3D详解

一、Flash3D是什么? Flash3D 是一项革命性的AI技术,能够从单张普通照片快速重建3D场景。简单来说,你只需要提供一张照片,Flash3D 就能帮你还原出这个场景的立体效果。这项技术在房地产、建筑设计、虚拟现实等多个领域都有着广泛的应用前景。 二、主要特点 一张就够:只需…

QT QFormLayout控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例&#xff0c;包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizonta…

如何在 Ubuntu 22.04 上安装 Metabase 数据可视化分析工具

简介 Metabase 提供了一个简单易用的界面&#xff0c;让你能够轻松地对数据进行探索和分析。通过本文的指导&#xff0c;你将能够在 Ubuntu 22.04 系统上安装并配置 Metabase&#xff0c;并通过 Nginx 进行反向代理以提高安全性。本教程假设你已经拥有了一个非 root 用户&…