C++STL之vector(超详细)

news2024/12/26 19:41:38

C++STL之vector

  • 1.vector基本介绍
  • 2.vector重要接口
    • 2.1.构造函数
    • 2.2.迭代器
    • 2.3.空间
      • 2.3.1.resize
      • 2.3.2.capacity
    • 2.4.增删查找
  • 3.迭代器失效
  • 4.迭代器分类

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【C++的学习】
📝📝本篇内容:vector基本介绍;vector重要接口:构造函数;迭代器;空间;增删查改;迭代器失效;迭代器分类
⬆⬆⬆⬆上一篇:C++IO流
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.vector基本介绍

① 在我们C语言中有数组,C++中因此也设计了array来代替C语言中的数组,但是它有一个缺点,就是array是静态空间,一旦配置了空间就无法改变,这就让使用者非常麻烦。但是vector就不一样,它是动态空间,一旦底层空间不足,就会自动扩容,压根不需要担心空间不足而造成问题。
②vector扩容:当新元素插入进来时,发现内存不足,这个时候就会先扩容开辟新的空间(并不是原地扩容,因为可能后面没有内存),然后把原来的数据拷贝过去,再进行插入,然后把旧的空间给释放掉。并且我们扩容的空间基本上是以倍数来增长的,保证后续再有元素插入时,不需要扩容,导致效率低下。
③vector支持随机访问,即像数组一样[ ]来访问,非常高效,同时在末尾删除和末尾插入元素非常高效,这得益于它的结构。但是其他的位置进行操作效率就会比较低下,没有list好

2.vector重要接口

☞vector参考文档

2.1.构造函数

对于任何容器,首先看的肯定是构造函数
在这里插入图片描述
讲一下其中比较重要的

vector构造函数
vector()默认构造函数
vector(const vector&)拷贝构造
vector(size_type n,const value_type&val)构造n个val
vector(InputIterator first, InputIterator last)使用迭代器构造
#include <vector>
#include <iostream>
using namespace std;
int main()
{
	vector<int> v1;//默认构造

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	vector<int> v2(arr, arr + 10);//使用迭代器来构造
	for (auto& e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<int> v3(2, 10);//构造2个为10的元素
	for (auto& e : v3)
	{
		cout << e <<" ";
	}

	vector<int> v4(v3);//拷贝构造
	return 0;
}

在这里插入图片描述

2.2.迭代器

我们来看一下迭代器,迭代器就是迭代器是一种访问容器内元素的对象,它提供了一种方法来顺序访问容器中的各个元素,而不需要了解容器的内部工作原理。

vector迭代器iterator/reverse_iterator
begin+end正向迭代器
rbegin+rend反向迭代器
#include <iostream>
#include <vector>
using namespace std;
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	vector<int> v(arr, arr + 10);
	//使用迭代器
	//正向迭代器
	vector<int>::iterator it = v.begin();
	while (it != v.end())//end是最后一个元素的下一个位置
	{
		cout << *it <<' ';//像指针一样使用
		it++;
	}
	
	cout << endl;
	//反向迭代器
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << ' ';//像指针一样使用
		rit++;//这里是++,而不是--,因为已经说明是反向迭代器,就该使用++
	}

	return 0;
}

在这里插入图片描述

2.3.空间

vector空间
size_type size() const;获取数据个数
size_type capacity() const;获取vector的容量大小
bool empty() const;vector是否为空
void reserve (size_type n);提前开辟空间,只会改变vector的capacity,能够缓解vector增容代价问题
void resize (size_type n, value_type val = value_type());改变vector的size,可以变大也可以变小
#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	//empty
	if (v.empty())
	{
		cout << "empty" << endl;
	}
	cout << "------------------" << endl;
	//size
	v.push_back(10);
	v.push_back(10);
	v.push_back(10);
	cout << v.size() << endl;
	cout << "------------------" << endl;
	//capacity
	cout << v.capacity() << endl;

	cout << "------------------" << endl;
	//reverse
	v.reserve(10);//提前扩容到能存放10个元素的空间
	cout << v.size() << endl;//3,不会改变
	cout << v.capacity() << endl;//10
	
	cout << "------------------" << endl;
	//resize
	v.resize(20);
	cout << v.size() << endl;//20,改变size,元素为默认值
	cout << v.capacity() << endl;//20
	return 0;
}

在这里插入图片描述

2.3.1.resize

其中我们的resize还有其他的功能,可以缩小空间以及设定自己想要的元素,resize在开空间的同时还会进行初始化,影响size
具体演示见下面

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(11);
	v.push_back(12);
	v.push_back(13);
	v.push_back(14);
	cout <<"size:"<<v.size() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "---------------------" << endl;
	v.resize(3);
	cout << "size:" << v.size() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "---------------------" << endl;
	v.resize(10,33);//把7个元素都设置为33
	cout << "size:" << v.size() << endl;
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "---------------------" << endl;

	return 0;
}

在这里插入图片描述

2.3.2.capacity

我们这边再详细讨论一下capacity这个成员函数,我们想一下,我们的vector的扩容是按照什么来的呢?需要空间就扩容?一个个扩容?我们来看一下下面的一段代码,分别在windows和Linux下展示

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	size_t cap = v.capacity();
	cout <<"initital capacity:"<< cap << endl;
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (cap != v.capacity())
		{
			cap = v.capacity();
			cout << "capacity changed:" << cap << endl;
		}
	} 
	return 0;
}

windows:
在这里插入图片描述

Linux:
在这里插入图片描述

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

我们还要注意下它的效率问题,如果我们提前知道需要多少空间,就可以提前扩容来保证一次的开辟空间,来避免多次扩容的效率低下

#include <iostream>
#include  <vector>
using namespace std;
int main()
{
	vector<int> v;
	v.reserve(100);//提前开辟好空间,防止一遍遍的扩容造成效率低下
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
	}
	cout << v.capacity() << endl;//100
	return 0;
}

2.4.增删查找

vector增删查改
void push_back (const value_type& val);尾插
void pop_back();尾删
InputIterator find (InputIterator first, InputIterator last, const T& val);这是算法中的,查找
iterator insert (iterator position, const value_type& val);在position前插入元素
void insert (iterator position, size_type n, const value_type& val);在position位置前插入n个val
iterator erase (iterator position);删除position位置的元素
iterator erase (const_iterator first, const_iterator last);删除迭代器范围的元素
void swap (vector& x);交换两个vector
reference operator[] (size_type n);像数组一样的访问
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
	vector<int> v(2, 10);
	v.push_back(11);//尾插
	v.push_back(12);
	v.push_back(13);
	v.push_back(14);
	cout << "initiatl element:";
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "------------------------------------------------------" << endl;

	
	v.pop_back();//尾删
	cout << "after pop_back():";
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "------------------------------------------------------" << endl;


	
	v.insert(v.begin(), 9);//在第一个元素前的位置插入一个9
	cout << "after insert(v.begin(),9):";
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "------------------------------------------------------" << endl;



	//find && erase
	vector<int>::iterator it=find(v.begin(), v.end(),12);//查找元素为12的位置
	if (it != v.end())
	{
		//如果返回的迭代器为end()说明没找到
		v.erase(it);
	}
	cout << "after erase(it):";
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "------------------------------------------------------" << endl;



	//operator[]
	for (int i = 0; i < v.size(); i++)
	{
		v[i] = i;//operator[]的返回值是引用
	}
	cout << "after change element through operator[]:";
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "------------------------------------------------------" << endl;


	return 0;
}

在这里插入图片描述

3.迭代器失效

接下来我们要谈谈迭代器失效的问题了,这个如果不了解底层的话就会很容易掉进坑里面
首先我们先讲insert导致的迭代器失效
在这里插入图片描述
在我们使用insert的时候,如果空间不足了,那我们的vector就会自动帮我们扩容,但是扩容常常需要经历三个步骤:开辟新的空间;移动元素到新空间;释放原来的空间。我们vector底层的实现的迭代器本身就是指针,只不过是typedef了一下。仔细想想,问题就来了,当我们插入后,有概率空间不足,扩容后,原本的迭代器(指针)就是野指针了,没有指向新空间

//!!!!error code
#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	v.push_back(11);
	v.push_back(12);
	v.push_back(13);
	v.push_back(14);
	cout << v.capacity() << endl;
	//接下来再插入就会扩容	
	vector<int>::iterator it = v.begin();
	v.insert(it, 10);
	cout << v.capacity() << endl;
	cout << *it << endl;

	return 0;
}

上述代码就演示了这个情况,使用一块已经被释放的空间,造成的后果是程序崩溃,我们仅仅演示了一下insert造成的结果,其实其他会造成扩容的函数如resize,reverse,push_back都会造成迭代器失效,同时从另一个角度来说,我们所传入的迭代器position所指向的内容已经不是我们想要的了,这也是迭代器失效的一种情况,因此insert以后我们认为迭代器已经失效,不能再使用

下一种情况是erase,思考一下?erase会扩容吗?答案是并不会,只是删除元素,所以说就没问题了?不不不,仔细思考一下,我的迭代器此时指向最后一个元素,如果我erase了一个元素呢,我们底层的_finish指针就会–,那此时的迭代器就相等于是指向无效空间

//!!!!!error code
#include <iostream>
using namespace std;
#include <vector>
int main()
{
	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; // 此处会导致非法访问
	return 0;
}

在这里插入图片描述

可以运行一下上面的代码分别在Linux和Windows下,在Windows下会崩溃,但是在Linux可以运行,这不得不说VS的检查非常严,erase后的迭代器是不允许使用的,但是g++的就会宽很多,当删除3后,元素4会往前移动,pos的位置还是有效的
接下来就看一下下面这个代码

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v{1,2,3,4,5};
	//删除偶数
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if ((*it) % 2 == 0)
		{
			v.erase(it);
		}
		it++;
	}
	
	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

首先对于环境而言是一样的,在VS下会崩溃,而Linux下能正常运行
在这里插入图片描述
在这里插入图片描述
我们来分析一下这个代码
在这里插入图片描述
可以发现,我们的it很巧合的把偶数元素删除了,同时也正好落到了_finish使用空间的尾end(),也判断循环结束了,我们接下里对代码稍作修改如下

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v{ 1,2,3,4};
	//删除偶数
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if ((*it) % 2 == 0)
		{
			v.erase(it);
		}
		it++;
	}

	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

我们的元素的数量变为只有四个,接下来再看一下它的执行逻辑
在这里插入图片描述
通过上面的图可以发现,我们的it直接和_finish(end())直接错过了,这样即使是Linux也无能为力了
在这里插入图片描述
直接发生段错误

从上述的例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。
因此我们的insert和erase函数都会返回一个迭代器来供我们使用,我们修正一下上面的代码

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v{ 1,2,3,4};
	//删除偶数
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if ((*it) % 2 == 0)
		{
			it=v.erase(it);//接受迭代器,返回的迭代器是it传入时的位置
		}
		else
		{
			it++;
		}
	}

	for (auto& e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

我们的string其实也是一样的道理,只是对于string我们很少用迭代器
在这里插入图片描述
我们对str进行了提前扩容,造成了野指针问题,我们的string使用失效的迭代器也会崩溃

4.迭代器分类

在我们使用算法函数时,有些函数需要特定的迭代器,像我们的vector的迭代器本质就是原生指针,因此它是一个随机迭代器,我们可以看一下侯捷大佬的《STL源码剖析》里对迭代器的分类
在这里插入图片描述
在这里插入图片描述我们Forward迭代器就是单向迭代器,它是只支持重载++的迭代器,Bidrectional迭代器是双向迭代器,它支持++和–,而我们的Random是随机迭代器,它支持++和–,也支持+和-,同样支持[ ]
理论上来讲,模板语法上可以传任何类型参数,但是内部使用迭代器是有要求

🌸🌸C++STL之vector的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

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

相关文章

深度学习实验十三 卷积神经网络(4)——使用预训练resnet18实现CIFAR-10分类

目录 一、数据加载 二、数据集类构建 三、模型构建 四、模型训练 五、模型评价及预测 附完整可运行代码&#xff1a; 实验大体步骤&#xff1a; 注&#xff1a; 在自己电脑的CPU跑代码 连接远程服务器跑代码√ 本次实验由于数据量巨大&#xff0c;我的笔记本上还没有…

【Maven Helper】分析依赖冲突案例

目录 Maven Helper实际案例java文件pom.xml文件运行抛出异常分析 参考资料 《咏鹅》骆宾王 鹅&#xff0c;鹅&#xff0c;鹅&#xff0c;曲项向天歌。 白毛浮绿水&#xff0c;红掌拨清波。 骆宾王是在自己7岁的时候就写下了这首杂言 Maven Helper A must have plugin for wor…

Android 桌面窗口新功能推进,聊一聊 Android 桌面化的未来

Android 桌面化支持可以说是 Android 15 里被多次提及的 new features&#xff0c;例如在 Android 15 QPR1 Beta 2 里就提到为 Pixel 平板引入了桌面窗口支持&#xff0c;桌面窗口允许用户在自由窗口同时运行多个应用&#xff0c;同时可以像在传统 PC 平台上一样调整这些窗口的…

【深度学习】四大图像分类网络之VGGNet

2014年&#xff0c;牛津大学计算机视觉组&#xff08;Visual Geometry Group&#xff09;和Google DeepMind公司一起研发了新的卷积神经网络&#xff0c;并命名为VGGNet。VGGNet是比AlexNet更深的深度卷积神经网络&#xff0c;该模型获得了2014年ILSVRC竞赛的第二名&#xff0c…

Pytest框架学习20--conftest.py

conftest.py作用 正常情况下&#xff0c;如果多个py文件之间需要共享数据&#xff0c;如一个变量&#xff0c;或者调用一个方法 需要先在一个新文件中编写函数等&#xff0c;然后在使用的文件中导入&#xff0c;然后使用 pytest中定义个conftest.py来实现数据&#xff0c;参…

【力扣】389.找不同

问题描述 思路解析 只有小写字母&#xff0c;这种设计参数小的&#xff0c;直接桶排序我最开始的想法是使用两个不同的数组&#xff0c;分别存入他们单个字符转换后的值&#xff0c;然后比较是否相同。也确实通过了 看了题解后&#xff0c;发现可以优化&#xff0c;首先因为t相…

HarmonyOS4+NEXT星河版入门与项目实战(23)------组件转场动画

文章目录 1、控件图解2、案例实现1、代码实现2、代码解释3、实现效果4、总结1、控件图解 这里我们用一张完整的图来汇整 组件转场动画的用法格式、属性和事件,如下所示: 2、案例实现 这里我们对上一节小鱼游戏进行改造,让小鱼在游戏开始的时候增加一个转场动画,让小鱼自…

Wireshark常用功能使用说明

此处用于记录下本人所使用 wireshark 所可能用到的小技巧。Wireshark是一款强大的数据包分析工具&#xff0c;此处仅介绍常用功能。 Wireshark常用功能使用说明 1.相关介绍1.1.工具栏功能介绍1.1.1.时间戳/分组列表概况等设置 1.2.Windows抓包 2.wireshark过滤器规则2.1.wiresh…

Vue3 开源UI 框架推荐 (大全)

一 、前言 &#x1f4a5;这篇文章主要推荐了支持 Vue3 的开源 UI 框架&#xff0c;包括 web 端和移动端的多个框架&#xff0c;如 Element-Plus、Ant Design Vue 等 web 端框架&#xff0c;以及 Vant、NutUI 等移动端框架&#xff0c;并分别介绍了它们的特性和资源地址。&#…

探索Python词云库WordCloud的奥秘

文章目录 探索Python词云库WordCloud的奥秘1. 背景介绍&#xff1a;为何选择WordCloud&#xff1f;2. WordCloud库简介3. 安装WordCloud库4. 简单函数使用方法5. 应用场景示例6. 常见Bug及解决方案7. 总结 探索Python词云库WordCloud的奥秘 1. 背景介绍&#xff1a;为何选择Wo…

Kali Linux系统一键汉化中文版及基础使用详细教程

Kali Linux系统一键汉化中文版及基础使用详细教程 引言 Kali Linux是一款基于Debian的Linux发行版&#xff0c;专为渗透测试和网络安全而设计。由于其强大的功能和丰富的工具&#xff0c;Kali Linux在安全领域得到了广泛应用。然而&#xff0c;许多用户在使用Kali Linux时会遇…

网络安全(三):网路安全协议

网络安全协议设计的要求是实现协议过程中的认证性、机密性与不可否认性。网络安全协议涉及网络层、传输层与应用层。 1、网络层安全与IPSec协议、IPSec VPN 1.1、IPSec安全体系结构 IP协议本质上是不安全的额&#xff0c;伪造一个IP分组、篡改IP分组的内容、窥探传输中的IP分…

2. STM32_中断

中断 中断是什么&#xff1a; 打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断。 中断的意义&#xff1a; 中断可以高效处理紧急程序&#xff0c;不会一直占用CPU资源。如实时控制、故障处理、处理不确定…

【聚类】主成分分析 和 t-SNE 降维

1 主成分分析PCA PCA 是一种线性降维技术&#xff0c;旨在通过选择具有最大方差的特征方向&#xff08;称为主成分&#xff09;来压缩数据&#xff0c;同时尽可能减少信息损失。 1.1 原理 1.2 优缺点 from sklearn.decomposition import PCA import matplotlib.pyplot as plt…

ARM 嵌入式处理器内核与架构深度剖析:解锁底层技术逻辑

目录 一、ARM架构概述 1.1. 优势与特点 1.2. 应用领域 二、ARM内核的主要系列及特点 2.1. ARM内核与架构的关系 2.2. Cortex-A系列 2.2.1. 应用场景 2.2.2. 特点 2.3. Cortex-R系列 2.3.1. 应用场景 2.3.2. 特点 2.4. Cortex-M系列 2.4.1. 应用场景 2.4.2. 特点 …

数据结构 (21)树、森林和二叉树的关系

一、树 定义&#xff1a;树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点&#xff0c;所定义的关系称为父子关系。当集合为空时&#xff0c;是一棵空树&#xff1b;当集合非空时&#xff0c;有且仅有一个特定的称为根的结点。树中的每个结点可以有…

探索温度计的数字化设计:一个可视化温度数据的Web图表案例

随着科技的发展&#xff0c;数据可视化在各个领域中的应用越来越广泛。在温度监控和展示方面&#xff0c;传统的温度计已逐渐被数字化温度计所取代。本文将介绍一个使用Echarts库创建的温度计Web图表&#xff0c;该图表通过动态数据可视化展示了温度值&#xff0c;并通过渐变色…

计算机网络——数据链路层Mac帧详解

目录 前言 一、以太网 二、Mac帧 三、MTU——最大传输单元 四、Mac帧的传输过程 1.ARP协议 2.RARP协议 前言 在之前&#xff0c;我们学习过网络层的IP协议&#xff0c;了解到IP协议解决了从哪里来&#xff0c;到哪里去的问题&#xff0c;也就是提供了将数据从A到B的能力…

LabVIEW将TXT文本转换为CSV格式(多行多列)

在LabVIEW中&#xff0c;将TXT格式的文本文件内容转换为Excel格式&#xff08;即CSV文件&#xff09;是一项常见的数据处理任务&#xff0c;适用于将以制表符、空格或其他分隔符分隔的数据格式化为可用于电子表格分析的形式。以下是将TXT文件转换为Excel&#xff08;CSV&#x…

响应式编程一、Reactor核心

目录 一、前置知识1、Lambda表达式2、函数式接口 Function3、StreamAPI4、Reactive-Stream1&#xff09;几个实际的问题2&#xff09;Reactive-Stream是什么&#xff1f;3&#xff09;核心接口4&#xff09;处理器 Processor5&#xff09;总结 二、Reactor核心1、Reactor1&…