C++【STL】之vector的使用

news2024/11/25 14:29:41

文章目录:

  • vector介绍
  • vector使用
    • 1. 默认成员函数
      • 1.1 默认构造
      • 1.2 拷贝构造
      • 1.3 析构函数
      • 1.4 赋值重载
    • 2. 迭代器
      • 2.1 正向迭代器
      • 2.2 反向迭代器
    • 3. 容量操作
      • 3.1 获取空间数据
      • 3.2 空间扩容
      • 3.3 大小调整
      • 3.4 空间缩容
    • 4. 数据访问
      • 4.1 下标随机访问
      • 4.2 获取首尾元素
    • 5. 数据修改
      • 5.1 尾插尾删
      • 5.2 任意位置插入删除
      • 5.3 交换和清理

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的部分常用接口,大佬们想了解更多关于vector类的细节,一定要请前往官方文档(点击跳转)查阅学习

1. 默认成员函数

vector的成员变量就是三个指针

template<class T>
class vecotr
{
private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

其中,_start指向空间起始位置,_finish指向最后一个有效元素的下一个位置,_end_of_storage指向已开辟空间的终止位置

1.1 默认构造

vector支持三种默认构造

  1. 默认构造大小为0的对象
  2. 构造n个值为value的对象
  3. 通过迭代器区间构造自定义元素类型
int main()
{
	vector<int> v1; //构造值为int的对象
	vector<char> v2(12, 'c'); //构造12个值为c的对象
	string s = "happ";
	vector<char> v3(s.begin(), s.end()); //构造s区间内的元素对象
	vector<int> v4 = { 4,1,2 }; //调用了拷贝构造
	return 0;
}

1.2 拷贝构造

拷贝构造:通过拷贝原有对象,来创建新的相同值的对象

int main()
{
	vector<int> v1 = { 1,2,3 };
	vector<int> v2(v1);
	return 0;
}

1.3 析构函数

析构函数:释放动态开辟的的空间,由于vector对象的空间是连续的,释放时直接delete[] _start即可

内部主要代码:

delete[] _start;
_start = _finish = _end_of_storage = nullptr;

析构函数会在对象生命周期结束时自动调用,平时使用vector时无需担心

1.4 赋值重载

赋值重载:对原有对象的值进行重写

int main()
{
	vector<int> v1 = { 1,2,3 };
	vector<int> v2; //空对象
	v2 = v1; //将v1的值赋给v2
	return 0;
}

赋值重载函数有返回值,也适用于连续赋值

vector<int> v1;
vector<int> v2;
vector<int> v3 = { 4,1,2 };
v2 = v1 = v3; //将v3赋值给v1,v2

2. 迭代器

迭代器的出现使得各种各样的容器都能以同一种方式访问数据,vectorstring迭代器本质上就是原生指针,与之相比其他容器的迭代器就比较复杂了,后面都会一一介绍

迭代器分为三类:

  • 单向迭代器:只支持单向操作
  • 双向迭代器:支持双向移动
  • 随机迭代器:支持双向移动,还能指定移动长度

stringvector的迭代器就是随机迭代器,可以随意指定移动

2.1 正向迭代器

正向迭代器用于从前往后遍历容器中的数据

开始位置:

结束位置:

这里begin()是第一个有效元素的地址,end()是最后一个有效元素的下一个地址

int main()
{
	const char* pa = "hello world";
	vector<char> v1(pa, pa + strlen(pa)); //迭代器构造
	vector<char>::iterator it = v1.begin(); //创建迭代器
	while (it != v1.end())
	{
		cout << *it;
		it++;
	}
	return 0;
}

注意:

使用迭代器遍历数据时,结束条件要写 it != v.end(),而不能写成 it < v.end(),因为对于有些容器的空间不是连续的,如list,这时判断小于就是错误的!

vector是随机迭代器,所以支持随机访问遍历

auto it = v.begin() + 6; //auto自动推导类型,随机位置开始遍历

2.2 反向迭代器

反向迭代器用于从后往前遍历容器中的数据

开始位置:

结束位置:

这里rbegin()是对象中最后一个有效元素的地址,rend()是对象中

int main()
{
	const char* ps = "happy new year";
	vector<char> v1(ps, ps + strlen(ps));
	vector<char>::reverse_iterator rit = v1.rbegin();
	while (rit != v1.rend())
	{
		cout << *rit;
		rit++;
	}
	return 0;
}

3. 容量操作

3.1 获取空间数据

  • size()接口:获取有效数据大小
  • capacity()接口:获取空间容量大小
  • empty()接口:判空

指针 - 指针 = 两个指针间的元素个数

int main()
{
	vector<int> v1 = { 4,1,2,8,8,8 };
	cout << "size: " << v1.size() << endl;
	cout << "capacity: " << v1.capacity() << endl;
	cout << "empty: " << v1.empty() << endl;
	return 0;
}

image-20230614184352506

3.2 空间扩容

reserve()接口:vector对象空间扩容

int main()
{
	vector<int> v1;
	cout << "capacity: " << v1.capacity() << endl;
	v1.reserve(88);
	cout << "capacity: " << v1.capacity() << endl;
	return 0;
}

n < capacity时,reserve()接口不会进行任何操作

下面来看一段代码,观察vector在VS下和Linux下的扩容机制

int main()
{
	vector<int> v1;
	size_t capacity = v1.capacity();
	cout << "capacity:" << capacity << endl;
	int i = 0;
	while (i < 100)
	{
		v1.push_back(12); //尾插
		//不相等则说明发生了扩容
		if (capacity != v1.capacity())
		{
			capacity = v1.capacity();
			cout << "capacity:" << capacity << endl;
		}
		i++;
	}
	return 0;
}

观察上面的运行结果可以看出,VS下采用的是1.5倍扩容法,Linux下采用的是2倍扩容法,当所需容量较小时,VS采用的方法更浪费空间,而所需容量较大,Linux采用的方法更浪费空间。

如果知道所需内存进行提前扩容,两种版本所申请的容量就是一样的,且不会造成过多的内存碎片,达到节约空间的效果

3.3 大小调整

resize()接口:调整vector对象大小(调整_finish位置)

第二个参数val是缺省值,为对应对象的默认构造值,如int的默认构造为0

int main()
{
	vector<int> v1;
	v1.resize(12); //使用缺省值
	vector<int> v2;
	v2.resize(12, 8); //使用指定值
	return 0;
}

resizereserve都能起到扩容的效果,二者的区别在于:

  • resize扩容的同时还能起到初始化的效果,而reserve不能
  • resize会改变_finish的位置,而reserve不会
  • n < capacity时,resize会将size初始化到capacity空间

3.4 空间缩容

shrink_to_fit()接口:对vector对象的空间进行缩容

这个接口的缩容步骤是:首先开辟一个容量小于原空间的新空间,然后将原空间的数据转移到新空间,超出的部分就丢弃,最后释放原空间,完成缩容

缩容的代价和风险都是很大的,官方文档上都加了一个警告标志,不推荐使用此接口

4. 数据访问

由于vector是连续的空间,所以不仅可以通过迭代器遍历,还能通过下标随机访问

4.1 下标随机访问

下标随机访问的原理就是operator[]运算符重载

int main()
{
	const char* pb = "happy";
	vector<char> v1(pb, pb + strlen(pb)); //迭代器区间构造
	const vector<char> cv1(pb, pb + strlen(pb)); //迭代器区间构造
	size_t pos = 0; //下标
	while (pos < v1.size())
	{
		cout << v1[pos]; //访问普通对象
		//cout << v1.at(pos); //与上一条等价
		pos++;
	} 
	cout << endl;
	size_t _pos = 0; //下标
	while (_pos < cv1.size())
	{
		cout << cv1[_pos]; //访问const对象
		_pos++;
	}
	return 0;
}

这里的at方法也可以起到遍历访问的功能,它实际上就是对operator[]的封装

4.2 获取首尾元素

front()接口:获取首元素

back()接口:获取结尾元素

int main()
{
	vector<int> v1 = { 4,1,2 };
	cout << "front: " << v1.front() << endl;
	cout << "back: " << v1.back() << endl;
	return 0;
}

front() 返回的就是 *_startback() 返回的就是 *_finish

5. 数据修改

5.1 尾插尾删

push_back()尾插接口和pop_back()尾删接口,都是老朋友了,下面直接演示用法

int main()
{
	vector<int> v1 = { 4,1,2 };
	v1.push_back(6);
	vector<int>::iterator _it = v1.begin();
	while (_it != v1.end())
	{
		cout << *_it;
		_it++;
	}
	_it = v1.begin();
	cout << endl;
	v1.pop_back();
	while (_it != v1.end())
	{
		cout << *_it;
		_it++;
	}
	return 0;
}

5.2 任意位置插入删除

insert()接口:任意位置插入

erase()接口:任意位置删除

下面还是直接来演示用法

int main()
{
	int _arr[] = { 8,8,8 };
	vector<int> v1 = { 1,2 };
	//在指定位置前插入一个值(找不到默认尾插)
	v1.insert(find(v1.begin(), v1.end(), 2), 6); //1 6 2
	//在指定位置前插入n个值
	v1.insert(find(v1.begin(), v1.end(), 6), 3, 7); //1 7 7 7 6 2
	//在指定位置前插入一段迭代器区间(数据中有相同的数时默认在第一次找到的该数前插入)
	v1.insert(find(v1.begin(), v1.end(), 7), _arr, _arr + (sizeof(_arr[0]) - 1)); //1 8 8 8 7 7 7 6 2
	//删除指定位置的元素
	v1.erase(find(v1.begin(), v1.end(), 1)); //8 8 8 7 7 7 6 2
	//删除一段区间
	v1.erase(v1.begin() + 1, v1.end()); //8
	return 0;
}

这里还有一个迭代器失效的场景:

int main()
{
	vector<int> v = { 4,1,2 };
	auto it = v.end();
	for (int i = 0; i < 5; i++)
	{
		v.insert(it, 10);
		it++; //再次使用迭代器会发生失效
	}
	return 0;
}

在进行插入或删除操作后,由于没有及时更新,可能会导致迭代器的指向位置失效

具体原因和解决方案我会在下篇模拟实现中讲解

5.3 交换和清理

swap()接口:交换

clean()接口:清理

int main()
{
	vector<int> v1 = { 1,2,3 };
	vector<int> v2 = { 4,5,6 };
	vector<int> v3 = { 7,8,9 };
	v1.swap(v2); //交换v1、v2
	v3.clear();  //清理v3
	return 0;
}

这里有个问题,std 中已经提供了全局的 swap 函数,为什么vector中还要再提供一个呢?

  • std::swap在交换时,需要调用多次拷贝构造和赋值重载函数,是深拷贝,用于vector中效率是很低的
  • vector::swap 在交换时,是交换三个成员变量,由于都是指针,只需要三次浅拷贝,就能很高效的完成任务

C++【STL】之vector的使用,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

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

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

相关文章

chatgpt赋能python:Python怎么横向键盘输入?

Python怎么横向键盘输入&#xff1f; 如果你是一位使用Python进行编程的工程师&#xff0c;你肯定明白快速而准确地输入代码的重要性。现在&#xff0c;许多程序员都找到了一个方法来更快地输入代码-横向键盘输入。 什么是横向键盘输入&#xff1f; 横向键盘输入是一种方法&…

基于Java校园驿站管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

力扣题库刷题笔记5--最长回文子串

1、题目如下&#xff1a; 2、个人Python代码实现&#xff1a; 首先想到的是通过类似冒泡排序的方式进行切片&#xff0c;然后判断切片的子字符串是否为回文字符串&#xff0c;然后记录出最长的回文字符串&#xff0c;代码如下&#xff1a; 可以看到&#xff0c;通过切片的方式&…

合宙Air724UG Cat.1模块硬件设计指南--电源供电

电源供电 简介 在模块应用设计中&#xff0c;电源设计是很重要的一部分&#xff0c;供电部分的电路设计不当会造成模块出现工作异常、指标恶化等现象&#xff0c;而良好的电源设计方案能够给模块提供稳定的工作状态。 特性 模块主供电VBAT&#xff1a;3.3V~4.3V&#xff0c;推…

设计模式—模板方法模式

模板方法模式&#xff1a; 定义一个操作的流程框架&#xff0c;而将流程中一些步骤延迟到子类中实现。使得子类在不改变流程结构的情况下&#xff0c;重新定义流程中的特定步骤。 主要角色&#xff1a; 抽象类: 负责给出操作流程的轮廓或框架&#xff0c;由模板方法和若干基…

用户模块的增删改查接口设计

MongoDB 数据库常用操作 MongoDB数据库中常用的操作包括&#xff1a; 插入数据&#xff1a;使用insertOne()或insertMany()方法向集合中插入数据。查询数据&#xff1a;使用find()方法查询满足条件的数据。更新数据&#xff1a;使用updateOne()或updateMany()方法更新满足条件…

【力扣刷题 | 第九天】150 逆波兰 239滑动窗口最大值

目录 前言&#xff1a; 150. 逆波兰表达式求值 - 力扣&#xff08;LeetCode&#xff09; 239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 总结&#xff1a; 前言&#xff1a; 本片仍然是利用栈与队列的思想来解决实际问题&#xff0c;希望各位小伙伴可以和我…

chatgpt赋能python:Python断言之等于两个值其中一个

Python断言之等于两个值其中一个 在Python编程中&#xff0c;我们经常需要对程序进行断言&#xff0c;以判断程序是否正确地运行。其中一种常见的断言方式是判断某个方法的结果是否等于两个值中的其中一个。本文将介绍如何在Python中实现这种断言&#xff0c;并探讨其在实际应…

⑨电子产品拆解分析-触摸化妆镜

⑨电子产品拆解分析-触摸化妆镜 一、功能介绍二、电路分析以及器件作用1、电源部分2、触摸部分3、灯光控制部分三、数据手册以及其它资料1、注意点2、数据手册汇总一、功能介绍 ①短按白光、暖光、冷光三档色温切换;②长按支持无极调光;③三档调亮度关机记忆当前亮度功能;二…

chatgpt赋能python:Python模块更新技巧详解

Python模块更新技巧详解 为什么需要更新Python模块&#xff1f; Python语言自问世以来一直在得到广泛的应用&#xff0c;其中最大的原因在于它的灵活性和可扩展性。Python拥有丰富的模块库&#xff0c;覆盖了各种不同的应用场景。然而&#xff0c;由于软件环境不断发展&#…

服务器配置远程vscode

1 使用sftp同步远程代码 打开vscode&#xff0c;在扩展种搜索sftp&#xff0c;点击安装。   按住快捷键shiftctrlp&#xff0c;可以打开界面顶部的命令行&#xff0c;输入sftp&#xff0c;点击如下图的config选项&#xff1a;   会自动在.vscode目录下创建一个名为sftp.j…

photoscan(metashape)跑GPS辅助的无人机影像SfM(空三)教程

刚打开的photoscan界面如下图所示&#xff1a;   然后&#xff0c;点击工作区左上角的添加堆块选项&#xff1a;   可以看到新增了一个名为“Chunk 1”的堆块&#xff0c;然后&#xff0c;右击“Chunk 1”&#xff0c;依次选择add、添加照片&#xff1a;   即可弹出照…

踩坑系列 Spring websocket并发发送消息异常

文章目录 示例代码WebSocketConfig配置代码握手拦截器代码业务处理器代码 问题复现原因分析解决方案方案一 加锁同步发送方案二 使用ConcurrentWebSocketSessionDecorator方案三 自研事件驱动队列&#xff08;借鉴 Tomcat&#xff09; 总结 今天刚刚经历了一个坑&#xff0c;非…

云原生之使用Docker部署wordpress网站

云原生之使用Docker部署wordpress网站 一、wordpress介绍二、检查本地docker环境1.检查docker状态2.检查docker版本 三、下载wordpress镜像四、创建数据库1.创建数据目录2.创建mysql数据库容器3.查看mysql容器状态4.远程客户端测试连接数据库 五、部署wordpress1.创建wordpress…

java开发——程序性能的优化方法

java开发——程序性能的优化方法 1、算法逻辑优化2、redis缓存优化3、异步编排4、MQ削峰填谷5、预加载6、sql调优7、jvm调优8、集群搭建 后端开发必须学习的优化思想&#xff01; 1、算法逻辑优化 (1) 什么算法优化&#xff1f; 算法优化的思想就是&#xff0c;对于同一个问题…

ll 内容详解

linux的数据存储是以block&#xff08;块&#xff09;为单位的 &#xff1a; 1个block 4 KB 4096 字节 1KB 1024 字节[rootCTF-RHCSA-2 ~]# ll -sh total 76K &#xff08;列表中 所有文本文件 总共占用磁盘空间的KB大小 &#xff09;&#xff08;root用户家目录中…

cortex A7核按键中断实验

cortex A7核按键中断实验 一、分析电路图 实验目的&#xff1a;完成板子三个按键操作 1.1 电路图IO口 KEY1------>PF9 KEY2------>PF7 KEY3------>PF8 1.2 工作原理 KEY1 ------> PF9 ------> 按键触发方式&#xff1a;下降沿触发 KEY2 ------> PF7 …

ElasticSearch安装与介绍

目录 ElasticSearch安装与介绍Elastic Stack简介ElasticsearchLogstashKibanaBeats ElasticSearch快速入门简介下载单机版安装启动ElasticSearch 错误分析错误情况1错误情况2错误情况3 ElasticSearch-Head可视化工具通过Docker方式安装通过Chrome插件安装 ElasticSearch中的基本…

docker部署prometheus+grafana+alertmanager+dingtalk实现钉钉告警

目录 docker安装准备工作镜像拉取容器启动启动node-exporter启动prometheus启动grafana启动webhook-prometheus-dingtalk启动alertmanager所有容器启动成功如下 将prometheus和alertmanager进行关联在prometheus目录下新建一个rules.yml文件的告警规则修改prometheus.yml文件&a…

NLP学习笔记十二-skip-gram模型求解

NLP学习笔记十一-skip-gram模型求解 上一篇文章&#xff0c;我们见到了skip-gram模型的原理&#xff0c;这里我们在陈述一下skip-gram模型其实是基于分布相似性原理来设计的&#xff0c;在skip-gram模型中&#xff0c;他认为一个词的内涵可以由他的上下文文本信息来概括&#…