【C++】STL学习之vector的使用

在这里插入图片描述
🔥博客主页 小羊失眠啦.
🎥系列专栏《C语言》 《数据结构》 《C++》 《Linux》 《Cpolar》
❤️感谢大家点赞👍收藏⭐评论✍️


在这里插入图片描述

文章目录

  • 前言
  • 一、默认成员函数
    • 1.1 默认构造
    • 1.2 拷贝构造
    • 1.3 析构函数
    • 1.4 赋值重载
  • 二、迭代器
    • 2.1 正向迭代器
    • 2.2 反向迭代器
  • 三、容量相关
    • 3.1 大小、容量、判空
    • 3.2 空间扩容
    • 3.3 大小调整
    • 3.4 缩容
  • 四、数据访问相关
    • 4.1 下标随机访问
    • 4.2 首尾元素
  • 五、数据修改相关
    • 5.1 尾插尾删
    • 5.2 任意位置插入删除
    • 5.3 交换、清理

前言

vector 是表示可变大小数组的序列 容器,其使用的是一块 连续 的空间,因为是动态增长的数组,所以 vector 在空间不够时会扩容;vector 优点之一是支持 下标的随机访问,缺点也很明显,头插或中部插入效率很低,这和我们之前学过的 顺序表 性质很像,不过在结构设计上,两者是截然不同的

在这里插入图片描述


一、默认成员函数

vector 的成员变量如上图所示,就是三个指针,分别指向:

  • _start 指向空间起始位置,即 begin()
  • _finish 指向最后一个有效元素的下一个位置,相当于 end()
  • _end_of_storage 指向已开辟空间的终止位置

在这里插入图片描述

1.1 默认构造

vector 支持三种默认构造方式

  1. 默认构造大小为 0 的对象
  2. 构造 n 个元素值为 val 的对象
  3. 通过迭代器区间构造,此时元素为自定义类型,如 stringvectorDate

在这里插入图片描述

int main()
{
	vector<int> v1;   //构造元素值为 int 的对象

	vector<char> v2(10, 'x');   //构造 10 个值为 'x' 的对象

	string s = "abcdefg";
	vector<char> v3(s.begin(), s.end());   //构造 s 区间内的元素对象

	return 0;
}

在这里插入图片描述

注:也可以直接通过 vector<int> v4 = {1, 2, 3} 的方式构造对象,不过此时调用了 拷贝构造 函数

vector<int> v4 = {1, 2, 3};    //这种构造方式比较常用,有点像数组赋初始值

1.2 拷贝构造

拷贝构造将对象 x 拷贝、构造出新对象 v拷贝构造 函数的使用方法很简单,利用一个已经存在的 vector 对象,创建出一个值相同的对象

在这里插入图片描述

vector<int> x = { 1, 2, 3, 4, 5 };

vector<int> v(x);   //利用对象 x 构造出 v

在这里插入图片描述

可以看到,对象 v 和对象 x 的值是一样的(copy)

注意: 调用拷贝构造时,两个对象类型需匹配,且被复制对象需存在

拷贝构造赋值重载深度拷贝 的讲究,在模拟实现 vector 时演示

1.3 析构函数

析构函数,释放动态开辟的空间,因为 vector 使用的空间是连续的,所以释放时直接通过 delete[] _start 释放即可

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

在这里插入图片描述

// ~vector 函数内部
delete[] _start;
_start = _finish = _end_of_storage = nullptr;

1.4 赋值重载

拷贝构造 的目的是创建一个新对象,赋值重载 则是对一个老对象的值进行 改写

在这里插入图片描述

int arr[] = { 6, 6, 8 };
vector<int> v1(arr, arr + (sizeof(arr) / sizeof(arr[0])));

vector<int> v2;  //创建一个空对象
v2 = v1;         //将 v1 的值赋给老对象 v2

在这里插入图片描述

注意: v1 对象赋值给 v2 对象后,v1 本身并不受任何影响,改变的只是 v2

赋值重载 函数有返回值,适用于多次赋值的情况

vector<int> v3 = { 9,9,9 };
v2 = v1 = v3;	//这样也是合法的,最终 v1、v2 都会受到影响

二、迭代器

迭代器 是一个天才设计,它的出现使得各种各样的容器都能以同一种方式进行 访问遍历 数据

vector 支持下标随机访问, 所以大多数情况下访问数据都是使用下标,但 迭代器 相关接口它还是有的

在这里插入图片描述

vectorstring迭代器 本质上就是原生指针,比较简单,但后续容器的 迭代器 就比较复杂了

复杂归复杂,但每种 容器 的迭代器使用方法都差不多,这就是 迭代器 设计的绝妙之处

注:stringvector 的迭代器都是 随机迭代器(RandomAccessIterator),可以随意走动,支持全局排序函数 sort

在这里插入图片描述

2.1 正向迭代器

正向迭代器即 从前往后 遍历的 迭代器

在这里插入图片描述

const char* ps = "Hello Iterator!";
vector<char> v(ps, ps + strlen(ps));   //构造vector

vector<char>::iterator it = v.begin();  //创建迭代器

while (it != v.end())
{
	cout << *it;
	++it;
}
cout << endl;

在这里插入图片描述

注意:

  • 迭代器在创建时,一定要先写出对应的类型,如 vector<int>,嫌麻烦可以直接用 auto 推导

  • 在使用迭代器遍历时,结束条件为 it != v.end() 不能写成 <,因为对于后续容器来说,它们的空间不是连续的,判断小于无意义

  • begin() 为第一个有效元素地址,end() 为最后一个有效元素的下一个地址

vector随机迭代器,也支持这样玩

//auto 根据后面的类型,自动推导迭代器类型
auto it = v.begin() + 3;	//这是随机的含义

2.2 反向迭代器

反向迭代器常用来 反向遍历(从后往前)容器

在这里插入图片描述

反向遍历 vector 对象

const char* ps = "Hello Iterator!";
vector<char> v(ps, ps + strlen(ps));

vector<char>::reverse_iterator rit = v.rbegin();

while (rit != v.rend())
{
	cout << *rit;
	++rit;
}
cout << endl;

在这里插入图片描述

反向迭代器的注意点与正向迭代器一致,值得注意的是 rbegin()rend()

在这里插入图片描述

begin()end() 适用于 正向迭代器

  • begin() 为对象中的首个有效元素地址
  • end() 为对象中最后一个有效元素的下一个地址

rbegin()rend() 适用于 反向迭代器

  • rbegin() 为对象中最后一个有效元素地址
  • rend() 为对象中首个有效元素的上一个地址

注意: begin() 不能和 rend() 混用

上述 迭代器 都是用于正常 可修改 的对象,对于 const 对象,还有 cbegin()cend()crbegin()crend(),当然这些都是 C++11 中新增的语法

在这里插入图片描述

注:对于 const 对象,存在重载版本,如 begin() const,也就是说,const 修饰的对象也能正常使用 begin()end()rbegin()rend()C++11 中的这个新语法完全没必要,可以不用,但不能看不懂


三、容量相关

下面来看看 vector 容量相关函数和扩容机制

3.1 大小、容量、判空

大小 size()
容量 capacity()
判空 empty()

这些函数对于我们太熟悉了,和 顺序表 的一模一样

在这里插入图片描述

直接拿来用一用

vector<int> v = { 1, 2, 3, 4, 5 };
cout << "size:" << v.size() << endl;
cout << "capacity:" << v.capacity() << endl;
cout << "empty:" << v.empty() << endl;

在这里插入图片描述

这几个函数都是直接拿来用的,没什么值得注意的地方

3.2 空间扩容

连续空间可扩容,像 string 一样,vector 也有一个提前扩容的函数:reserve()
输入指定容量即可扩容,常用来 提前扩容,避免因频繁扩容而导致的内存碎片

下面来通过一个小程序先来简单看看 PJ 版 和 SGI 版的 默认扩容机制

vector<int> v;
size_t capacity = v.capacity();
cout << "Default capacity:" << capacity << endl;

int i = 0;
while (i < 100)
{
	v.push_back(i);  // 尾插元素 i

    // 如果不相等, 证明出现扩容
	if (capacity != v.capacity())
	{
		capacity = v.capacity();
		cout << "New capacity:" << capacity << endl;
	}
	i++;
}

在这里插入图片描述

可以看出,PJ 版采用的是 1.5 倍扩容法,而 SGI 版直接采用 2 倍扩容法,待扩容量较小时,PJ 版会扩容更多次,浪费更多空间;但待扩容量越大时,变成 SGI 版浪费更多空间,总的来说,两种扩容方式各有各的优点

如果我们提前知道待扩容空间大小 n,可以直接使用 reserve(n) 的方式进行 提前扩容,这样一来,无论是哪种版本,最终容量大小都是一致的,且不会造成空间浪费

v.reserve(100);  //提前开辟空间

在这里插入图片描述

此时是非常节约空间的,而且不会造成很多的内存碎片

注意: n 小于等于 capacity() 时,reserve 函数不会进行操作

3.3 大小调整

与提前扩容相似的大小调整,主要调整的是 _finish

在扩容的同时对新空间进行初始化,参数2 val 为缺省值,缺省为对应对象的默认构造值

  • 自定义类型也有默认构造函数,如 int(),构造后为 0
  • 这种构造方法称为 匿名构造,后续会经常简单(很方便)

在这里插入图片描述

vector<int> v1;
v1.reserve(10);  //使用缺省值

vector<int> v2;
v2.resize(10, 6);  //使用指定值

在这里插入图片描述

区别在于:是否指定初始化值

resizereserve

  • 两者的共同点是都能起到扩容的效果

  • resize 扩容的同时还能进行初始化,reserve 则不能

  • resize 会改变 _finish,而 reserve 不会

  • 对于两者来说,当 n 小于等于 capacity() 时,都不进行扩容操作

    • resize 此时会初始化 size()capacity() 这段空间

3.4 缩容

vector 中还提供一个了缩容函数,将原有容量缩小,但这完全没必要,以下是缩容步骤:

  • 开辟新空间(比原空间更小的空间)
  • 用原空间中的数据将新空间填满,超出部分丢弃
  • 释放原空间,完成缩容

为了一个缩容而导致的是代价是很大的,因此 不推荐缩容,想要改变 size() 时,可以使用 resize 函数

在这里插入图片描述


四、数据访问相关

连续空间数据访问时,可以通过 迭代器,也可以通过 下标,这里还是更推荐使用 下标,因为很方便;作为 “顺序表”,当然也支持访问首尾元素

4.1 下标随机访问

下标访问是通过 operator[] 运算符重载实现的

在这里插入图片描述

库中提供了两个重载版本,用以匹配普通对象和 const 对象

const char* ps = "Hello";
vector<char> v(ps, ps + strlen(ps));
const vector<char> cv(ps, ps + strlen(ps));

size_t pos = 0;
while (pos < v.size())
{
    cout << v[pos];
    cout << cv[pos];
    pos++;
}
cout << endl;

在这里插入图片描述

除了 operator[] 以外,库中还提供了一个 at 函数,实际就是对 operator[] 的封装

v.at(0);
v[0]     //两者等价

注意: 因为是下标随机访问,所以要小心,不要出现 越界 行为

4.2 首尾元素

front() 获取首元素,back() 获取尾元素

vector<int> v = { 1, 1, 1, 0, 0, 0 };
cout << "Front:" << v.front() << endl;
cout << "Back:" << v.back() << endl;

在这里插入图片描述

实际上,front() 就是返回 \*_startback() 则是返回 \*_finish


五、数据修改相关

vector 也可以随意修改其中的数据,比如尾部操作,也支持任意位置操作,除此之外,还能交换两个对象,亦或是清除对象中的有效元素

5.1 尾插尾删

push_back()pop_back() 算是老相识了,两个都是直接在 _finish 上进行操作

在这里插入图片描述
在这里插入图片描述

这两个函数操作都很简单,不再演示

注意: 如果对象为空,是不能尾删数据的

对于已有对象数据的修改,除了赋值重载外,还有一个函数 assign(),可以重写指定对象中的内容,使用方法很像默认构造函数,但其本质又和赋值重载一样

在这里插入图片描述

第一种方式是通过迭代器区间赋值,第二种是指定元素数和元素值赋值

5.2 任意位置插入删除

任意位置插入删除是使用 vector 的重点,因为这里会涉及一个问题:迭代器失效,这个问题很经典,具体什么原因和如何解决,将在模拟实现 vector 中解答

在这里插入图片描述
在这里插入图片描述

int arr[] = { 6,6,6 };
vector<int> v = { 1,0 };

//在指定位置插入一个值
v.insert(find(v.begin(), v.end(), 1), 10);	//10,1,0

//在指定位置插入 n 个值
v.insert(find(v.begin(), v.end(), 0), 2, 8);	//10,1,8,8,0

//在指定位置插入一段迭代器区间
v.insert(find(v.begin(), v.end(), 8), arr, arr + (sizeof(arr) / sizeof(arr[0])));	//10,1,6,6,6,8,8,0

//删除指定位置的元素
v.erase(find(v.begin(), v.end(), 10));	//1,6,6,6,8,8,0

//删除一段区间
v.erase(v.begin() + 1, v.end());	//1

先浅浅演示一下 迭代器失效的场景

vector<int> v = { 1,2,3 };
auto it = v.end();	//利用迭代器模拟尾插

for (int i = 0; i < 5; i++)
{
	v.insert(it, 10);
	it++;	//再次使用迭代器
}

在这里插入图片描述

不止 insert 的迭代器会失效,erase 的迭代器也会失效

简单来说:插入或删除后,可能导致迭代器指向位置失效,此时没有及时更新,再次使用视为非法行为

因此我们认为 vector 在插入或删除后,迭代器失效,不能再使用,尤其是 PJ 版本,对迭代器失效的检查十分严格

5.3 交换、清理

在这里插入图片描述
在这里插入图片描述

vector<int> v1 = { 1,2,3 };
vector<int> v2 = { 4,5,6 };

v1.swap(v2);	//交换两个对象

v1.clear();
v2.clear();	//清理

std 中已经提供了全局的 swap 函数,为何还要再提供一个呢?

  • 这个函数实现原理不同 std::swapstd::swap 实际在交换时,需要调用多次拷贝构造和赋值重载函数,对于深拷贝来说,效率是很低的

  • vector::swap 在交换时,交换是三个成员变量,因为都是指针,所以只需要三次浅拷贝交换,就能完美完成任务

  • 实际在 vector::swap 内部,还是调用了 std::swap,不过此时是高效的浅拷贝

至于 clear 函数,实现很简单:

  • _finish 等于 _start,就完成了清理,不需要进行缩容,这样做是低效的

在这里插入图片描述

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

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

相关文章

【STK】手把手教你利用STK进行导弹和反导仿真04 - STK/MMT模块04 导弹飞行工具应用案例

【STK】手把手教你利用STK进行导弹和反导仿真04 - STK/MMT模块04 导弹飞行工具应用案例 这个案例将把范围轮廓导出到STK,以此来分析如何放置发射点和落点位置,然后使用它们来定义MFT中的飞行导弹。最后将生成的轨迹导出到STK进行显示和分析。 (1)第一步启动STK并创建新场景。…

LeetCode-705. 设计哈希集合【设计 数组 哈希表 链表 哈希函数】

LeetCode-705. 设计哈希集合【设计 数组 哈希表 链表 哈希函数】 题目描述&#xff1a;解题思路一&#xff1a;超大数组解题思路二&#xff1a;拉链法解题思路三&#xff1a;定长拉链数组 题目描述&#xff1a; 不使用任何内建的哈希表库设计一个哈希集合&#xff08;HashSet&…

算法设计与分析实验报告c++实现(最近点对问题、循环赛日程安排问题、排序问题、棋盘覆盖问题)

一、实验目的 1&#xff0e;加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握&#xff1b; 2&#xff0e;提高学生利用课堂所学知识解决实际问题的能力&#xff1b; 3&#xff0e;提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 1、 最…

leetCode刷题 27. 移除元素

目录 1.思路&#xff1a; 2.解题方法&#xff1a; 3.复杂度&#xff1a; 4.Code 题目&#xff1a; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须…

设备树的概念、设备树如何变成device、与driver的匹配

在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离&#xff0c;但每次调整资源还是会涉及到内核&#xff0c;所以现在更加流行的是设备树方式。设备树的好处是通过独立于内核存在&#xff0c;这样如果设备上外设功能启用与否以及位置变动的话很多时候不用修改…

【论文速读】| 基于大语言模型的模糊测试技术

本次分享论文为&#xff1a;Large Language Models Based Fuzzing Techniques: A Survey 基本信息 原文作者&#xff1a;Linghan Huang, Peizhou Zhao, Huaming Chen, Lei Ma 作者单位&#xff1a;悉尼大学&#xff1b;东京大学&#xff1b;阿尔伯塔大学 关键词&#xff1a;…

8. Spring Boot 配置文件

源码地址&#xff1a;SpringBoot_demo 本篇文章内容源码位于上述地址的com/chenshu/springboot_demo/config包下 1. 配置文件是什么 上节说到&#xff0c;Spring Boot的项目分为三个部分&#xff1a;源代码目录、资源目录、单元测试目录。 而配置文件的位置就位于资源目录res…

Python-VBA函数之旅-delattr函数

目录 1、 delattr函数&#xff1a; 1-1、Python: 1-2、VBA&#xff1a; 2、相关文章&#xff1a; 个人主页&#xff1a;https://blog.csdn.net/ygb_1024?spm1010.2135.3001.5421 delattr函数在Python中具有广泛的应用场景&#xff0c;主要用于动态地管理对象的属性。常用…

Python十大最佳实践:高效Python编程的十个关键方法

在当今的软件开发世界中&#xff0c;Python是一种极其重要且广泛使用的编程语言。以下是Python编程的十大最佳实践&#xff0c;这些实践将帮助你提升编程效率&#xff0c;优化代码质量&#xff0c;以及更好地应用Python的强大功能。 1.理解Pythonic的方式 “Pythonic”是指遵循…

傲基科技冲刺上市:依赖单一产品,元气未恢复,有股东提前退出

近日&#xff0c;傲基科技股份有限公司&#xff08;下称“傲基科技”&#xff09;递交招股书&#xff0c;准备在港交所主板上市&#xff0c;华泰证券为其独家保荐人。 据招股书介绍&#xff0c;傲基科技是一家提供家具家居类产品的品牌运营商及出口物流服务商。傲基科技在招股…

Java+vue2+springboot智慧班牌系统源码,支持PC端、移动客户端、电子班牌端,SaaS模式部署

智慧班牌作为一个班级的标识&#xff0c;也是班级空间日常管理的载体&#xff0c;作为班级文化展示交流窗口与学科教学、德育管理&#xff0c;以及学校信息収布等有机结合起来&#xff0c;作为学生展示的平台&#xff0c;又可应用于普及教育安全知识和科学文化&#xff0c;拓展…

Python爬虫:requests模块的基本使用

学习目标&#xff1a; 了解 requests模块的介绍掌握 requests的基本使用掌握 response常见的属性掌握 requests.text和content的区别掌握 解决网页的解码问题掌握 requests模块发送带headers的请求掌握 requests模块发送带参数的get请求 1 为什么要重点学习requests模块&…

shell-将密码输入错误超过4次的IP地址通过firewalld防火墙阻止访问

应用场景&#xff1a;防止恶意IP尝试ssh登录 脚本说明&#xff1a;将密码输入错误超过四次得ip地址通过iptable防火墙访问。 分析&#xff1a; 首先&#xff0c;需要知道ssh远程访问记录在哪一个文件中 /var/log/secure 其次&#xff0c;模拟远程访问输错密码&#xff0c;查…

2024 十五届蓝桥杯省赛Python B组

以下仅是我的答案&#xff0c;仅供参考&#xff0c;欢迎讨论。 A&#xff1a;穿越时空之门 二进制、四进制转换。答案&#xff1a;63。 B&#xff1a;数字串个数 排除0&#xff0c;总的方案数9^10000,减去不存在3和不存在7的2*8^10000&#xff0c;再加上同时不存在3和7的7^…

MYSQL09_行格式概述、变长字段、NULL值、记录头信息、真实数据、内部结构

文章目录 ①. InnoDB - 行格式概述②. 变长字段长度列表 ③. NULL值列表④. 记录头信息5字节⑤. 记录的真实数据⑥. Compact行记录的内部结构⑦. Dynamic和Compressed行格式 ①. InnoDB - 行格式概述 ①. 我们平时的数据以行为单位来向表中插入数据,这些记录在磁盘上的存放方式…

【C语言基础】:编译和链接(计算机中的翻译官)

文章目录 一、翻译环境和运行环境1. 翻译环境1.1 编译1.1.1 预处理1.1.2 编译1.1.3 汇编 1.2 链接 2. 运行环境 一、翻译环境和运行环境 我们在Visual Studio上写的C语言代码其实都是一些文本信息&#xff0c;计算机是不能够直接执行他们的&#xff0c;计算机只能够执行二进制…

基于Linux定时任务实现的MySQL周期性备份

1、创建备份目录 sudo mkdir -p /var/backups/mysql/database_name2、创建备份脚本 sudo touch /var/backups/mysql/mysqldump.sh# 用VIM编辑脚本文件&#xff0c;写入备份命令 sudo vim /var/backups/mysql/mysqldump.sh# 内如如下 #!/bin/bash mysqldump -uroot --single-…

下载好了annaconda,但是在创建一个新的Conda虚拟环境报错

文章目录 问题描述&#xff1a;解决方案1.生成一个配置文件 问题总结 问题描述&#xff1a; ProxyError(MaxRetryError(“HTTPSConnectionPool(host‘repo.anaconda.com’, port443): Max retries exceeded with url: /pkgs/pro/win-64/repodata.json.bz2 (Caused by ProxyErr…

系统架构最佳实践 -- 金融企业的资损防控

一、资损产生的原因 由于支付行业的特殊性与复杂性&#xff08;主要处理资金相关业务&#xff09;&#xff0c;支付公司处于资损的风口浪尖&#xff0c;最容易发生资损&#xff0c;可以说资损风险无处不在。 常规来说&#xff0c;资损原因主要可以分为以下三类&#xff1a; 1…

【鸿蒙开发】第二十章 Camera相机服务

1 简介 开发者通过调用Camera Kit(相机服务)提供的接口可以开发相机应用&#xff0c;应用通过访问和操作相机硬件&#xff0c;实现基础操作&#xff0c;如预览、拍照和录像&#xff1b;还可以通过接口组合完成更多操作&#xff0c;如控制闪光灯和曝光时间、对焦或调焦等。 2 …