C++——STL之vector详解

news2025/1/19 14:14:01

C++——STL之vector详解

  • 🏐1.什么是vector
  • 🏐2.vector的使用
    • 🏀2.1vector的实例化
    • 🏀2.2访问遍历vector
      • ⚽2.2.1**下标+[]**
      • ⚽2.2.2**迭代器**
      • ⚽2.2.3**范围for**
    • 🏀2.3.vector容量问题
      • ⚽2.3.1size和capacity
      • ⚽2.3.2reserve和resize
    • 🏀3.4.vector中插入/查找/删除
      • ⚽3.4.1insert
      • ⚽3.4.2erase
      • ⚽3.4.3find
    • 🏀排序(体验泛型排序
    • 🏀push_back
  • 🏐vector的实现(含迭代器失效问题)
    • 🏀insert
    • 🏀erase
  • 💬一些小点

👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍

🏐1.什么是vector

学习了string之后,学习其他模板将会更容易上手,一起来看一下vector吧!

在这里插入图片描述

vector是表示大小可以变化的数组的序列容器。

  • 就像数组一样,vector对其元素使用连续的存储位置,这意味着也可以使用指向其元素的常规指针上的偏移量来访问其元素,并且与数组中的元素一样有效。但与数组不同的是,它们的大小可以动态变化,它们的存储由容器自动处理。
  • 在内部,vector使用动态分配的数组来存储其元素。当插入新元素时,可能需要重新分配此数组以增加大小,这意味着分配新数组并将所有元素移动到该数组。就处理时间而言,这是一项相对昂贵的任务,因此,vector不会在每次将元素添加到容器时重新分配。
  • 因此,与数组相比,vector消耗更多的内存,以换取管理存储和以有效方式动态增长的能力。

🏐2.vector的使用

🏀2.1vector的实例化

作为类模板来说,vector只能显式实例化

void test_vector1()
	{
		vector<int> v1;
		vector<int> v2(10, 1);
		vector<int> v3(v2);//类模板必须显示实例化,要说明类型为int
	}

🏀2.2访问遍历vector

访问遍历有大概三种方式

⚽2.2.1下标+[]

在这里插入图片描述

相比于string来说,[]返回的不仅仅是char类型,返回的是reference,也就是pos位置数据的引用

void test_vector2()
	{
		vector<int> v1;
		v1.push_back(0);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);

		// 下标+[]
		for (size_t i = 0; i < v1.size(); ++i)
		{
			v1[i]++;
		}
		for (size_t i = 0; i < v1.size(); ++i)//相比于string来说,[]返回的不只是char,返回的是pos位置数据元素的引用
		{
			cout << v1[i] << " ";
		}
		cout << endl;
}

⚽2.2.2迭代器

	void test_vector2()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		// 迭代器
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			(*it)--;

			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

⚽2.2.3范围for

	void test_vector2()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

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

范围for的底层也是迭代器,可以迭代器就可以用范围for

🏀2.3.vector容量问题

⚽2.3.1size和capacity

通过size函数获取当前容器中的有效元素个数,通过capacity函数获取当前容器的最大容量。

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 2);
	cout << v.size() << endl; //获取当前容器中的有效元素个数
	cout << v.capacity() << endl; //获取当前容器的最大容量
	return 0;
}

⚽2.3.2reserve和resize

在这里插入图片描述

通过reserse函数改变容器的最大容量,resize函数改变容器中的有效元素个数。

reserve规则:
 1、当所给值大于容器当前的capacity时,将capacity扩大到该值。
2、当所给值小于容器当前的capacity时,什么也不做。

resize规则:
 1、当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。(也就是将扩容的空间进行了初始化
 2、当所给值小于容器当前的size时,将size缩小到该值。

void TestVectorExpand()
	{
		size_t sz;
		vector<int> v;
		//v.resize(100);//resize是不止开辟了空间,还插入了100个数据,这地方不能用
		//v.reserve(100);//空间提前开好,提高效率

		sz = v.capacity();
		cout << "making v grow:\n";
		for (int i = 0; i < 100; ++i)
		{
			v.push_back(i);
			if (sz != v.capacity())
			{
				sz = v.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
	}

在这里插入图片描述

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size

🏀3.4.vector中插入/查找/删除

⚽3.4.1insert

如果在某一特定值位置进行插入或删除,需要用到find函数。不然直接指定位置插入

void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);//嫌长也可以用auto
		if (pos != v1.end())//为什么说!=而不是<,因为不一定像string那样是连续的存储空间,比如树
		{
			v1.insert(pos, 567);
		}
	}

注意:insert如果插入位置>capacity,那么将会在最后一个位置的下一个位置进行插入,并不会报错(不要有侥幸心理),erase就不一样了,如果删除位置>capacity就会报错。

⚽3.4.2erase

void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

        vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);//嫌长也可以用auto
        pos = find(v1.begin(), v1.end(), 300);//进行erase的时候会报错,没有300这个数的位置(虽然这个时候返回的end位置,但是仍不在capacity内,end表示的是最后一个位置的下一个位置
		if (pos != v1.end())
		{
			v1.erase(pos);
		}
	}

⚽3.4.3find

vector不提供find,像vector,list等容器,find是通用的,都是在一段迭代器区间去找,所以直接写在了algorithm中,就是个函数模板
在这里插入图片描述

  • find函数共三个参数,前两个参数确定一个迭代器区间(左闭右开),第三个参数确定所要寻找的值。
  • find函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数last。
void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

        vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);//嫌长也可以用auto
        if (pos != v1.end())
		{
		    v1.insert(pos, 567); //在3的位置插入567
		}
       
        pos = find(v.begin(), v.end(), 3); //获取值为3的元素的迭代器

		if (pos != v1.end())
		{
		   v1.erase(pos); //删除3
		}
	}

注意

  1. find函数是在算法模块(algorithm)当中实现的,不是vector的成员函数。vector不提供find
  2. find的实现只能用!=,不能用<,因为对于string,vector,空间连续,还可以这样干,但是list这些,空间不连续,<根本不能用

🏀排序(体验泛型排序

以前我们常用qsort函数来实现排序,这次我们看一下

🏀push_back

在这里插入图片描述
我们来充分理解一下这里的参数**(为什么要有const和&)**,一般来说我们插入数据我们有这么几种方式:

vector <string> str;
第一种
string str1;
str1="小彭";
str.push_back(str1);
第二种
str.push_back(string("老彭");
第三种
str.push_back("彭彭");

引用&是有必要的,第一种如果没有引用,那就会进行拷贝构造,开辟空间是个深拷贝,过程代价大,所以避免多次开空间,加&。
我们可以看到const的使用也是有必要的,比如第二种我们使用了匿名对象,像匿名对象,临时对象这种不能改变的量我们都需要加上const,为了避免出现权限放大的问题。
为什么支持第三种写法呢,也是因为有const,我们看到string的构造函数string(const char str){}*,这里面有隐式类型的转换,会产生临时变量。(这里涉及到右值引用)
如下

double d=1.1;
int i=d;//这样赋值完全没问题,隐式类型转换
看下面
double d=1.1;
int& i=d;//这样就是错误的,引用的不是d,而是中间的临时变量,而临时变量具有常性,但我们加个const就没有问题了,
再看下面
double d=1.1;
const int& i=d;

看完这里,你估计就明白了
关于范围for加const &也是一样,假如我们要把上面插入的数据输出

for(auto e:str)
{
	cout<<str<<endl;
}

在这个过程中,底层会发生的是迭代器将每个数据拷贝给范围变量e,又是拷贝!!!所以我们直接写成下面这种

for(const auto& e:str)
{
	cout<<str<<endl;
}

🏐vector的实现(含迭代器失效问题)

我们尝试写一下vector的一些接口

🏀insert

这个地方跟string相比,不是用下标,不用考虑无符号有符号的边界问题(用指针的优势

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

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;
		}

当我们用自己实现的接口进行操作时我们发现会出现迭代器失效的问题,(迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。) 经检查发现是扩容的问题,因为扩容会导致开辟新的空间,把数据拷贝过去,那么就自然的出现了原来指针指向的空间没有数据的现象,也就是迭代器失效。
为解决迭代器失效问题,在上面接口中是这么解决的,我们要做的就是更新pos位置,那么我们可以a.算好pos的相对位置计算出len长度,更新,当然还可以b.重新find一遍数据。 推荐b方法,因为用第一种,还会出现插入数据后不能访问的问题,形参的改变不能改变实参,函数接口里pos位置是改变了,但是函数外并没有改变。不如用find在函数外找一遍,继续对数据进行相关操作。
要点:更新迭代器位置

🏀erase

stl 规定erase返回删除位置下一个位置迭代器
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}

			--_finish;
			//if (size() < capacity()/2)
			//{
			//	// 缩容 -- 以时间换空间
			//}

			return pos;
		}

可以看到接口的实现决定了是否会出现迭代器失效的问题,c++和stl并没有规定怎么去实现这些接口,只是一个规范,所以看具体实现。
我们就认为用insert和erase后不要再直接访问pos位置的数据了

💬一些小点

  • 容器的查找都可以给迭代器区间[左闭右开)
  • vector没有这个需求,没有流插入流提取,迭代器和[]就够了,string有这个需求,用来打印字符
  • vector <char> v可以代替string str吗,不能,是不一样的,比如结尾的’/0’;有些操作是string独特的,比如+=,<<, find,比较大小,to_string等

在这里插入图片描述

觉得还不错的铁汁点赞关注一下吧😀

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

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

相关文章

二叉树先、中、后遍历递归+非递归

文章目录前言思路设计思想非递归前序遍历的思路非递归中序遍历的思路非递归后序遍历的思路层序遍历的思路完整代码MyBinaryTree.hMyBinaryTree.cppMain.cpp效果展示前言 作者水平有限&#xff0c;全部的代码是学习前人部分原创不要搬代码&#xff0c;一定要借鉴学习&#xff0…

接口测试的痛点和解决办法

在做接口测试时&#xff0c;以下几个测试痛点&#xff0c;一定要仔细琢磨下&#xff1a; 痛点①&#xff1a;由于测试环境数据被改动&#xff0c;导致接口测试失败 这个问题&#xff0c;最好的办法就是重新调用数据库中的最新数据。在做测试用例时&#xff0c;也要考虑到实时调…

汽车服务行业概况与供应商评估方案介绍,数商云SRM系统助力多维考核供应商

近年来&#xff0c;汽车保有量不断增长&#xff0c;中国汽车市场连续多年成为全球产销第一&#xff0c;伴随物联网技术的不断渗透&#xff0c;车联网开始兴起&#xff0c;带给线上线下汽车相关服务行业新的发展机遇。 当前汽车服务行业整体概况 1、市场需求增加&#xff1a;市…

Vue基础超详细

目录 一、Vue的简介 1、什么是vue 2、vue 的特性&#xff08;数据驱动视图、双向数据绑定&#xff09; 3、MVVM及其工作原理 二、Vue的基本实用 1、基本使用步骤 2、配置Vue的调试工具 3、指令与过滤器 3.1内容渲染指令 4、属性绑定指令 5、使用Javascript表达式 6、事件绑…

“ 念旧真的是一件很无趣的事 “

想要的都拥有 得不到的都释然 My Jinji音频&#xff1a;00:0006:40 | 01 | 想通就释然 想不通就茫然 每天不停的循环 | 02 | 终于理解你曾经说的 “有些人不能做朋友” 现在我和你一样惨 | 03 | 村上春树说过&#xff1a; “如果一直想见谁 肯定迟早会见到” 但是他还…

android入门之创建service

1. 前言 Service意为&#xff1a;服务&#xff0c;是一种运行时用户不可见的活动机制。可以理解为它是一个没有布局的Activity。 典型的场景&#xff1a;音乐后台播放、后台下载。 Service不同于子线程&#xff0c;Service是运行在主线程中的&#xff0c;因此不能进行耗时操作。…

电线电缆行业MES系统,帮助企业快速应对订单变化,减少资金占用

电线电缆行业生产管理现状 电线电缆行业是典型的重资产行业&#xff0c;原材料成本高、产品价值高、资金占用大、产品规格型号多达数万种&#xff0c;BOM管理繁杂&#xff0c;现场管理粗放&#xff0c;订单经常会合并或拆分生产&#xff0c;对排程要求高。 1、计划制定不准确…

无分类编址CIDR

无分类编址CIDR 构成超网 将多个子网聚合成一个较大的子网&#xff0c;叫做构成超网&#xff0c;或路由聚合。 方法&#xff1a;将网络前缀缩短&#xff08;所有网络地址取交集&#xff09;。 例题 某路由表中有转发接口相同的4条路由表项&#xff0c;其目的网络地址分别为…

Unicode编码的理解

1、Unicode 这个单词可以拆解为两部分&#xff0c;一个是Uni ,即英文单词unique的意思&#xff0c;也就是唯一的意思。code就是编码的意思。 GBK编码的理解_sgmcy的博客-CSDN博客 在上节博客里面&#xff0c;介绍了ASCII编码、ASCII编码表的扩展以及我们国家汉字的GBK编码。 …

详解OpenCV的椭圆曲线点坐标近似计算函数ellipse2Poly()

详解OpenCV的椭圆曲线点坐标近似计算函数ellipse2Poly() 函数ellipse2Poly()可用于近似计算椭圆曲线的像素坐标。 而前面介绍过的函数ellipse()则是直接在图像中绘制椭圆&#xff0c;详情见 https://www.hhai.cc/thread-174-1-1.html 函数ellipse2Poly()的C原型如下&#xff…

【Shell】find文件查找

语法格式 find [路径] [选项] [操作]选项参数对照表 常用选项 -name 查找/etc目录下以conf结尾的文件ind /etc -nam -iname 查找当前目录下文件名为aa的文件,不区分大小写 find . -iname aa -user 查找文件属主为hdfs的所有文件find . -user hdfs -group 查找文件属组为yarn的…

虹科方案 | 解决连接到IEEE 1588高可用性网络的SCADA系统的NTP同步参考问题

目前&#xff0c;各个行业都在朝着以太网融合的趋势发展&#xff0c;近年来也出现了一些可用于增强标准以太网弹性的技术创新&#xff0c;并被用作运营&#xff08;OT&#xff09;和信息技术&#xff08;IT)的通用链路层。电气等具有高可用性和严格时序要求的关键领域推动了这些…

1362:家庭问题(family)

1362&#xff1a;家庭问题(family) 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 6732 通过数: 3529 【题目描述】 有n个人&#xff0c;编号为1,2,……n&#xff0c;另外还知道存在K个关系。一个关系的表达为二元组&#xff08;α&#xff0c;β&#xff09;形式…

GBK编码的理解

1、我们学程序的时候&#xff0c;所熟知的ASCII码&#xff0c;就是一种编码方式 计算机底层&#xff0c;就只认识0和1. 举个例子&#xff0c;以3bit为例&#xff1a; 所以&#xff0c;如果是000的话&#xff0c;可以对应数字0 如果是001的话&#xff0c;可以对应数字1 。。…

非零基础自学Golang 第13章 并发与通道 13.4 select 13.5 小结

非零基础自学Golang 文章目录非零基础自学Golang第13章 并发与通道13.4 select13.4.1 select作用13.4.2 超时13.4.3 死锁13.5 小结第13章 并发与通道 13.4 select Go语言中&#xff0c;通过关键字select可以监听channel上的数据流动。 select的用法和switch非常相似&#xf…

Python中的基本数据类型

文章目录前言一、字符串类型字符串表示方法二、数字类型1. 整数2.浮点数3.复数三、布尔类型总结前言 我们一般在电脑中存储的数据有多种数据类型。比如下图这张员工工资表&#xff1a; 表中员工姓名可以用字符串类型存储&#xff08;比如"李世民"、“侯君集”&#…

Hadoop学习----HDFS

文件系统 文件系统&#xff1a;是一种存储和组织数据的方法&#xff0c;实现数据的存储、分级组织、访问和获取等操作&#xff0c;使得用户对文件访问和查找变得容易。文件系统使用树形目录的抽象逻辑概念代替了硬盘等物理设备使用数据块的概念&#xff0c;用户不必关心数据底…

实验三 进程的互斥与同步

文章目录一、 实验目的二、 实验原理三、实验内容四、我的代码内容和现象1、philosopher12、philosopher2这个程序不会发生死锁&#xff0c;因为五、课后习题&#xff1a;1.什么是死锁&#xff1f;产生死锁的原因和必要条件是什么&#xff1f;2.实验中给出的伪代码流程&#xf…

Problem Set 3

1Lagrange Duality Formulate the Lagrange dual problem of the following linear programming prob-lem min cT rs.t.Ax 二b where a ∈R is variable,c ∈ R"&#xff0c;A ∈Rkn, b ∈ Rk. 解&#xff1a;设拉格朗日函数为L(x,λ)cTxλT(Ax−b)\mathcal{L}(x,\lambda)…

第十七章 webpack5项目搭建Vue-Cli(开发模式)

step1–创建项目目录 创建一个目录用来搭建vue-cli的项目 mkdir vue-cli cd vue-clistep2–初始化项目 初始化项目&#xff0c;生成一个package.json文件 npm init -ystep3–编写vue-cli的开发模式配置 新建目录 / |-config | |--webpack.dev.js | |--webpack.prod.js我…