STL之vecor的使用(超详解)

news/2024/11/16 20:33:19 标签: c++, 数据结构, 开发语言

 

目录

 1. C/C++中的数组

1.1. C语言中的数组

1.2. C++中的数组 

 2. vector的接口

2.1. vector的迭代器 

2.2. vector的初始化与销毁 

2.3. vector的容量操作 

2.4. vector的访问操作 

2.5. vector的修改操作 


💓 博客主页:C-SDN花园GGbond

⏩ 文章专栏:玩转c++

 1. C/C++中的数组

1.1. C语言中的数组

在 C 语言中,数组是一组相同类型元素的有序集合。与字符串类似,它的大小在编译时就已经确定,不可更改。 

//大小为5的整型数组
int arr1[5] = { 1,2,3,4,5 };
//大小为5的浮点型数组
double arr2[5] = {0.0};

1.2. C++中的数组 

同样与string类似,C++为了更加方便就引入了一个支持可动态大小数组的序列容器vecotr。其特点如下:

 

  1. vector是可变大小的序列容器,采用连续存储空间存储元素,可通过下标高效访问。
  2. 与数组不同,vector大小可动态改变,由容器自动处理。
  3. vector本质上用动态分配数组存储元素,插入新元素时可能重新分配空间,即分配新数组并移动全部元素,此操作时间代价高,但不是每次插入都重新分配。
  4. vector会分配额外空间适应增长,不同库策略不同,但重新分配通常是对数增长间隔,使末尾插入元素能在常数时间完成。
  5. dequelistforward_list相比,vector访问元素及末尾添加和删除元素更高效,非末尾的删除和插入操作效率低,且统一的迭代器和引用更好。
//整型数组
vector<int> v1;
//浮点型数组
vector<double> v2;

使用vector都需要包含头文件#include<vector>。并且vector是一个模版类,所以在使用时需要显示实例化。 

 

 2. vector的接口

介绍一些vector的常见接口,因为很多接口的作用都与string的接口非常类似,所以很多就不在详细说明,具体也可以参考vector文档。

4cf179dc487747cbb791b073911b00f3.png

  下面我们开始研究他的使用,为了能够更好的测试,我们先实现一个打印容器元素的函数,vector底层是数组,所以有三种访问方式:下标访问、迭代器访问、范围for(本质也是迭代器)

void Printf(vector<int>& v)
{
	//下表遍历
	cout << "下表遍历:";
	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i];
	}
	cout << endl;
	//迭代器遍历
	cout << "迭代器遍历:";
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;
	cout << "范围for遍历:";
	for (auto e : v)
	{
		cout << e;
	}
	cout << endl;
}
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,9 };

	Printf(v);
	//Test01();
	return 0;
}

 57eac87fa68d443db7ed5129175398f1.png

 ​​​​​​

2.1. vector的迭代器 

同样的vector中也存在迭代器iterator,因为定义在vector类中,所以其需要通过域作用限定符访问——vector<类型>::iterator

下面将介绍的begin(),end(),rbeign(),rend()的使用访问方法与string中的几乎一摸一样,我们直接上实例演示:

#include<iostream>
#include<vector>

using namespace std;
void Test01()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,9 };
	cout << "顺序遍历:";
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;
	cout << "逆序遍历:";
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit;
		rit++;
	}
	cout << endl;

}
int main()
{
	Test01();
	return 0;
}

093772b6c25f4fd2a8634c094427e8ec.png

 

当然vector也支持const_iterator,用法也类似,这里就不在赘述。 

2.2. vector的初始化与销毁 

60befd5fa70d4c28b67fc30196b9e7c5.png

(1) 空容器构造函数(默认构造函数)
构造一个没有元素的空容器。
(2) 填充构造函数
构造一个包含n个元素的容器。每个元素都是val的副本。
(3) 范围构造器
构造一个包含与范围[first,last)一样多的元素的容器,每个元素都按照相同的顺序从该范围内的相应元素构造而成。
(4) 复制构造函数
构造一个容器,其中包含x中每个元素的副本,顺序相同。
 

c7001ccbe26d422da35a785a836479e1.png

同样的vector也支持多种构造函数,拷贝构造以及赋值运算符重载。 

void Test02()
{
	//无参构造
	vector<int> v1;
	Print(v1);
	//有参构造,n个位置初始化
	vector<int> v2(5, 2);
	Print(v2);
	//有参构造,n个位置调用T类型的默认构造
	vector<int> v3(5);
	Print(v3);
	//拷贝构造
	vector<int> v4(v3);
	Print(v4);
	//迭代器区间构造(传string的迭代器区间)
	string s("hello world");
	vector<int> v5(s.begin(), s.end());
	Print(v5);
	//迭代器区间构造(传vctor的迭代器区间)
	vector<int> v6(v5.begin(), v5.end());
	Print(v6);
	//赋值重载
	v1 = v6;
	cout << &v1 << "   " << &v6 << endl;//深拷贝
	Print(v1);
	//特殊的赋值方式
	vector<int> v7{ 1,2,3,4,5,6,7,8 };
	Print(v7);
}

98d091b952164f39b9eb1bc71e6ac6b2.png

2.3. vector的容量操作 

0f433b82d99d4bb9b03c7e754cc93593.png

d4c5b5393fea4b67a25b66ef3754eb11.png

vector类中,同样可以通过size()容器的有效长度;capacity()返回容器的容量大小。

void Test03()
{
	vector<int> v1;
	
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;
	vector<int> v2 = { 0,1,2,3,4,5 };
	cout << v2.size() << endl;
	cout << v2.size() << endl;
}

12b8ab8d05524637b37183d3a5064fa4.png

在初始化时,vecotr中的sizecapacity一般相同。这时我们也可以通过以下程序探究一下其扩容机制: 

void Test04()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v grow" << endl;
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed  " << v.capacity() << endl;
		}

	}
}
int main()
{
	Test04();
	return 0;
}

c34dee28ec704cb29486fb593477407d.png

在VS环境下,vector一般是以1.5倍扩容。但是在Linux环境下一般就以2倍扩容。 

58c314016a714161b67b9c59b0c61a4a.png

a2458ec7c562403b807c6a0ea95b76ba.png

要求容器减少容量以适应其大小。

该请求是非绑定的,容器实现可以自由地进行优化,使向量的容量大于其大小。

这可能会导致重新分配,但对向量大小没有影响,也不能改变其元素。

 

ae966670b2264115809eafc566891532.png

 

有效长度与容量操作 

 vector中的resize()reserve()。其实他们的用法与特点也是与string类中的相同

当n<sz时,reserve并不会发生任何改变,resize会删除有效字符到指定大小。
当sz<n<capcity时,reserve并不会发生任何改变,resize会补充有效字符(默认为0)到指定大小。
当n>capacity时,reserve会发生扩容,resize会补充有效字符(默认为0)到指定大小。

void Test06()
{
	vector<int> v1 = { 1,2,3,4,5 };
	cout << "v1的有效长度为:" << v1.size() << endl;
	cout << "v1的容量大小为:" << v1.capacity() << endl;
	v1.reserve(10);
	cout << "reserve(10)后:" << endl;
	cout << "v1的有效长度为:" << v1.size() << endl;
	cout << "v1的容量大小为:" << v1.capacity() << endl;
	cout << endl;
	v1.resize(8, 10);
	for (auto& e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "v1的有效长度为:" << v1.size() << endl;
	cout << "v1的容量大小为:" << v1.capacity() << endl;
	v1.resize(3);
	for (auto& e : v1)
	{
		cout << e << " ";
	}
	cout << "v1的有效长度为:" << v1.size() << endl;
	cout << "v1的容量大小为:" << v1.capacity() << endl;
	
}

int main()
{
	Test06();
	return 0;
}

3268a39a4ef64316b184f7eed9ff4b9b.png

2.4. vector的访问操作 

 7c91343943e74656b453ac8d519baa86.png

a125abd95aca431599e5f9ec7fbf9629.png

void Test07()
{
	vector<int> v = { 1,2,3,4,5 };
	for (int i = 0; i < v.size(); i++)
	{
		cout << v.at(i) << " ";
	}
	cout << endl;
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
	cout << "front:" << v.front() << endl;
	cout << "back:" << v.back() << endl;
}

int main()
{
	Test07();
	return 0;
}

f8c3d76fef5347e3a9bc1c070ea4ad0c.png

2.5. vector的修改操作 

0fac8b321376493ca30627b92c9e9080.png

d8105e101d1a4b98bb8fcf4b2f9cfc9c.png

首先先介绍最简单的四个函数push_back()pop_back()assign()swap()

void Test7()
{
	vector<int> v = { 1,2,3,4,5,6 };
	cout << "back:" << v.back() << endl;
    //尾插
	v.push_back(7);
    //尾删
	cout << "back:" << v.back() << endl;
	v.pop_back();
	cout << "back:" << v.back() << endl;
	vector<int> vv = { 6,5,4,3,2,1 };
    //n个val赋值给原数组
	vv.assign(3, 2);
	for (int i = 0; i < vv.size(); i++)
	{
		cout << vv[i] << " ";
	}
	cout << endl;
	vv.swap(v);
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < vv.size(); i++)
	{
		cout << vv[i] << " ";
	}
}

1a2941ef5d104f62b9d3f0903f4be33a.png

介绍insert()与·earse()的用法,这两个函数的用法就与string中的有所不同。首先是insert()函数: 

5995e0b2240946769db167b728c1232f.png

指定位置插入,要注意的是这里不再像string一样,用的size_t 的pos,vector虽然也可以用下标访问,但是为了承接后面STL其他不支持下标访问的容器,所以这边的pos用的是迭代器类型 

void Test8()
{
	vector<int> myvector(3, 100);
	vector<int>::iterator it = myvector.begin();
	//1.向指定位置插入一个元素
	it = myvector.insert(it, 200);
	cout << "myvector contains:";
	for (it = myvector.begin(); it < myvector.end(); it++)
		cout << ' ' << *it;
	cout << endl;
	//2.向指定位置插入n个元素
	myvector.insert(it, 2, 300);
	cout << "myvector contains:";
	for (it = myvector.begin(); it < myvector.end(); it++)
		cout << ' ' << *it;
	cout << endl;
	//3.向指定位置插入一段迭代器区间
	it = myvector.begin();
	vector<int> anothervector(2, 400);
	cout << "myvector contains:";
	for (it = myvector.begin(); it < myvector.end(); it++)
		cout << ' ' << *it;
	cout << endl;
	it = myvector.begin();
	myvector.insert(it + 2, anothervector.begin(), anothervector.end());
	//4.向指定位置插入一段迭代器区间
	int myarray[] = { 501,502,503 };
	myvector.insert(myvector.begin(), myarray, myarray + 3);
	cout << "myvector contains:";
	for (it = myvector.begin(); it < myvector.end(); it++)
		cout << ' ' << *it;
	cout << endl;
}

e8bde24df4f24e38a0ffb45c8fae61e7.png

 

erase()

 

删除单个元素: 当你需要删除向量中的某个特定元素时,可以使用 erase 函数,并传递一个指向该元素的迭代器。这将删除该元素,并使后续元素向前移动一个位置。

删除多个元素: erase 函数还可以接受两个迭代器参数,表示要删除的元素范围(左闭右开区间)。这允许你一次性删除多个连续的元素。

无论是哪种形式,erase 的返回值都是一个迭代器,指向被删除元素之后的位置(即第一个未被删除的元素)。如果删除的是容器中的最后一个元素或所有元素,则返回 vector::end() 迭代器。

void Test9()
{
	//1.删除迭代器所指元素
	vector<int> myvector;
	for (int i = 1; i <= 10; i++) 
		myvector.push_back(i);
	vector<int>::iterator it = myvector.erase(myvector.begin() + 5);
	it = myvector.erase(it);
	//2.删除一段迭代器区间
	it = myvector.erase(myvector.begin(), myvector.begin() + 3);
	cout << "myvector contains:";
	for (int i = 0; i < myvector.size(); ++i)
		cout << ' ' << myvector[i];
	cout << endl;
}

71f02f457ced4129891a9cc7506e745f.png

虽然看起来vector的insert()和erase()与string的没有什么区别,但是仔细观察就可以发现我们每次使用完迭代器之后都会更新,这是为什么呢?

主要还是因为我们每次插入数组都可能发生扩容,而扩容分为就地扩容与异地扩容。如果发生的异地扩容,这时的迭代器就不在指向原来的空间,而就指向一块释放的内存,我们一旦继续访问就会报错,这种现象我们称为迭代器失效。为了避免出现这种情况,所以我们在使用完迭代器之后需要更新。

f7c1c8f0879e47fca0352212fa0faecf.png

 

 

 

 

 

 

 

 

 

 

 

 


http://www.niftyadmin.cn/n/5754590.html

相关文章

卷积神经网络 (CNN)

代码功能 网络结构&#xff1a; 卷积层&#xff1a; 两个卷积层&#xff0c;每个卷积层后接 ReLU 激活函数。 最大池化层用于降低维度。 全连接层&#xff1a; 使用一个隐藏层&#xff08;128 个神经元&#xff09;和一个输出层&#xff08;10 类分类任务&#xff09;。 数据集…

学习用 Javascript、HTML、CSS 以及 Node.js 开发一个 uTools 插件,学习计划及其周期

希望学习 Javascript、HTML、CSS 以及 Node.js 开发一个 uTools 插件&#xff0c;学习时间取决于你的目标深度和现有的编程基础。以下是一个学习计划和时间估算&#xff1a; 1. 学习目标 HTML&#xff1a;理解网页的基本结构&#xff08;标签、属性、布局&#xff09;。CSS&am…

SpringBoot+Vue3开发会议管理系统

1 项目介绍 会议管理系统&#xff0c;简化公司内会议方面的流程&#xff0c;提供便捷。实现对会议室的管理、会议的管理、会议预约的管理&#xff0c;三大主流程模块。 系统分为三种角色&#xff0c;分别是员工、管理员和超级管理员。 员工角色功能&#xff1a;查看会议室占…

Pytest从入门到精通

一、pytest单元测试框架 (1)什么是单元测试框架 单元测试是指在软件开发当中,针对软件的最小单位(函数,方法)进行正确性的检查测试。 (2)单元测试框架 java : junit和testng python : unittest和pytest (3)单元测试框架主要做什么? 1.测试发现:从多个文件里面去找到我们测试…

第二十一课 Vue组件实用示例

Vue组件实用示例 本课主要介绍组件的一些小练习&#xff0c;通过这些小练习巩固下之前课程中的学习 1&#xff09;组件中值的双向绑定 <div id"app"><test></test> </div> <script>Vue.component(test, {template: <div><…

ThriveX 博客管理系统前后端项目部署教程

前端 前端项目地址&#xff1a;https://github.com/LiuYuYang01/ThriveX-Blog 控制端项目地址&#xff1a;https://github.com/LiuYuYang01/ThriveX-Admin Vercel 首先以 Vercel 进行部署&#xff0c;两种方式部署都是一样的&#xff0c;我们以前端项目进行演示 首先我们先…

How to install rust in Ubuntu 24.04

How to install rust in Ubuntu 24.04 Install Install 可以采用如下命令安装rust curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh具体如下&#xff1a; lwkqwfys:~$ curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh info: downloading instal…

SQL 审核在 CloudQuery 的四大场景应用

数据库作为数据的核心载体&#xff0c;其安全性和稳定性对业务的影响至关重要。而在我们日常业务中&#xff0c;SQL 编写不当是引起数据库故障的一个重要原因&#xff0c;轻则影响数据库性能&#xff0c;重则可能直接导致「雪崩」。因此&#xff0c;SQL 审核作为 SQL 代码投入生…