Cpp::STL—vector类的使用与理解(上)(10)

news2024/11/24 22:27:33

文章目录

  • 前言
  • 一、vector的介绍
    • 三个原生指针的图示
  • 二、vector的构造函数
    • 一个注意事项
  • 二、vector的空间大小、调整函数
    • size()
    • capacity()
    • empty()
    • resize()
    • reserve()
  • 三、vector的增删查改
    • push_back & pop_back
    • insert & erase
    • find
    • swap
    • front & back
    • operator[ ] & 正反迭代器
  • 四、迭代器失效问题及解决方法
    • 问题举例一
    • 问题举例二
    • 解决方法
  • 总结


前言

  结束了第一关string后,我们将来学习一下vector
  应该说有了string的经验后,vector设计得就不那么冗余,但是仍有很多有趣的东西值得我们学习

  来试试吧!


一、vector的介绍

vector文档介绍
关于vector,在正式开始前,你先要有以下认识:

  1. vector是表示可变大小数组的序列容器,底层是动态开辟顺序表
  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
  3. 当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间,并不是真的多开一个两个空间!释放就是全部释放,开辟就是全新开辟!
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长(如同string),因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的(vector采用_start、_finish、_end_of_storage三个原生指针来完成)
  5. 由于vector采用连续的空间来存储元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低

三个原生指针的图示

_start指向vector的开头
_finish指向最后一个有效元素的后一位
_end_of_storage指向存储空间的最后一个的后一位

在这里插入图片描述

我们就用这三个原生指针去模拟迭代器(尽管它不是真正的迭代器),代码如下:

template<class T>
    class vector
    {
    public:
        typedef T* iterator;
        typedef const T* const_iterator;

    private:
        iterator _start = nullptr;
        iterator _finish = nullptr;
        iterator _end_of_storage = nullptr;
    };

二、vector的构造函数

在这里插入图片描述
  allocator_type内存配置器(内存池)和explicit这个阶段都不用去理会它,首先我们发现真的跟string相比简要了不少,所以我讲解的也就方便一些,哈哈!

vector();

vector类的默认构造函数,构造一个没有元素的空容器
在这里插入图片描述

vector(size_type n, const value_type& val = value_type());

  构造一个vector类对象并用n个val初始化,value_type()是模板参数列表实例化转化的T类型,其实就是为了来个缺省,可是缺省不能为0,这样自定义类型就无法默认初始化,于是,Cpp给了内置类型和默认类型一种相同方法的初始化赋值方式:
在这里插入图片描述

具体实例如下:
在这里插入图片描述

vector(const vector& v);

vector类的拷贝构造函数
在这里插入图片描述

template < class InputIterator >
vector(InputIterator first, InputIterator last);

使用迭代器进行初始化构造
在这里插入图片描述
请注意!这里begin()和end()函数是传值返回返回临时对象具有常性,不能通过 ++ 或 - - 修改临时对象

一个注意事项

初始化构造逻辑是没有问题的,但是在调用过程可能会出现问题

    vector<int> v1(10, 1);
    print_vector(v1);

  我们看这段代码,很显然我们的想法是调用 vector(size_type n, const value_type& val = value_type()); 这个初始化构造,可问题是,我们发生了非法的间接寻址报错,这很奇怪,那我在这里也不卖关子,直接告诉大家这是编译器调用错了构造函数,调用了利用模板迭代器构造的那个

其实,编译器有时候也没那么聪明,它只会匹配它认为最适合的那个

  在这个例子中,我们看两个参数10、1都是int类型,而我们想调用的在这里是size_t、const int类型,相比之下,编译器把InputIterator模板实例化为int,可这哪里对呢,在这里我们应该传入的是迭代器,所以在内部访问的时候必然会发生错误
在这里插入图片描述
  解决方法也很简单,就是像上方一样,满足编译器选择最合适的这一特性,传入元素个数n的时候数字后面跟个u表示无符号整数 -> v2(10u,1) 或者你干脆直接重载,其他不怎么变,size_t 改为 int 就行

二、vector的空间大小、调整函数

size()

  获取有效元素个数
在这里插入图片描述

capacity()

  获取容量大小
在这里插入图片描述

empty()

  判断容器是否为空
在这里插入图片描述

resize()

  将有效元素的个数修改为n,并且如果n大于原来的size,多出来的地方用val填充,如果没有给出val,就用默认初始化的值填充(内置类型一般就是0,自定义类型就是默认初始化构造
在这里插入图片描述

reserve()

  改变容器的最大容量
在这里插入图片描述

resize 与 reserve 的对比
resize:当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0;当所给值小于容器当前的size时,将size缩小到该值
reserve:当所给值大于容器当前的capacity时,将capacity扩大到该值;当所给值小于容器当前的capacity时,什么也不做

三、vector的增删查改

push_back & pop_back

  对容器进行尾插尾删

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

int main()
{
	vector<int> v;
	
	v.push_back(1); // 尾插元素1
	v.push_back(2); // 尾插元素2
	v.push_back(3); // 尾插元素3
	v.push_back(4); // 尾插元素4

	v.pop_back(); // 尾删元素
	v.pop_back(); // 尾删元素
	v.pop_back(); // 尾删元素
	v.pop_back(); // 尾删元素
	
	return 0;
}

insert & erase

  通过insert函数可以在所给迭代器位置插入一个或多个元素,通过erase函数可以删除所给迭代器位置的元素,或删除所给迭代器区间内的所有元素(左闭右开)

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

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	
	v.insert(v.begin(), 0); // 在容器开头插入0
	v.insert(v.begin(), 5, -1); // 在容器开头插入5个-1
	v.erase(v.begin()); // 删除容器中的第一个元素
	v.erase(v.begin(), v.begin() + 5); // 删除在该迭代器区间内的元素(左闭右开)
	
	return 0;
}

find

  请注意!!!vector并没有单独实现find,但这并不代表“查”不了,我们可以调用算法模块(algorithm)来实现
  find函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数,即v1.end()

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

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	
	vector<int>::iterator pos = find(v.begin(), v.end(), 2); // 获取值为2的元素的迭代器
	v.insert(pos, 10); // 在2的位置插入10
	pos = find(v.begin(), v.end(), 3); // 获取值为3的元素的迭代器
	v.erase(pos); // 删除3

	return 0;
}

swap

  可以交换两个容器的数据空间,实现两个容器的交换

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

int main()
{
	vector<int> v1(10, 1);
	vector<int> v2(10, 2);
	
	v1.swap(v2); //交换v1,v2的数据空间

	return 0;
}

front & back

  分别返回容器中第一个元素和最后一个元素的引用
在这里插入图片描述

operator[ ] & 正反迭代器

  vector当中实现了 [ ] 操作符的重载,因此我们也可以通过 “下标+[ ]” 的方式对容器当中的元素进行访问,且vector是支持迭代器的,所以我们还可以用范围for对vector容器进行遍历。(支持迭代器就支持范围for,因为在编译时编译器会自动将范围for替换为迭代器的形式

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

int main()
{
	vector<int> v(10, 1);
	// 使用“下标+[]”的方式遍历容器
	for (size_t i = 0; i < v.size(); i++){
		cout << v[i] << " ";
	}
	cout << endl;

	// 使用“范围for”(本质上是迭代器)的方式遍历容器
	for (const auto& e : v){
		cout << e << " ";
	}
	cout << endl;
	
	return 0;
}

四、迭代器失效问题及解决方法

问题举例一

  迭代器的主要作用就是让我们在使用各个容器时不用关心其底层的数据结构,而vector的迭代器在底层实际上就是一个指针。

在这方面上,VS可以说是非常严格,进行了强制检查,迭代器用了之后就视为失效

void test_vector()
{
    vector<int> v1;
    
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);
    v1.push_back(6);
    v1.push_back(7);
    v1.push_back(8);
    print_vector(v1);

    vector<int>::iterator it = v1.begin() + 3;
    v1.insert(it, 40);
    print_vector(v1);

    cout << *it << endl; // err
}

  这里报错其实很好理解,我一开始便说vector的扩容不是在原先的基础上多开空间,而是重新开辟一块符合长度要求的空间,再释放旧空间,这就导致了it传参后,尽管就算形参在内部有更新,可形参的改变不影响实参,这是我们从C语言的共识,此时it就是错误的,再去访问它的数据就更是错误了
在这里插入图片描述

你可能会想,那我insert采用引用接收不就行了吗,可是你要知道v2.begin()返回的是临时常性变量,不能通过引用来接收,否则就是权限的放大,怎么解决呢?请保留这份疑问来继续往下看~

问题举例二

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

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 6; i++)
	{
		v.push_back(i);
	}
	
	vector<int>::iterator it = v.begin();
	
	while (it != v.end())
	{
		if (*it % 2 == 0) // 删除容器当中的全部偶数
		{
			v.erase(it);
		}
		it++;
	}
	
	return 0;
}

  该代码看上去实际上并没有什么错误,但如果你画图仔细分析,你就会发现该代码的问题所在,迭代器访问到了不属于容器的内存空间,导致程序崩溃
在这里插入图片描述

或者pos刚好对应最后一个元素,删除后迭代器pos就超出了有效元素范围,可能导致非法访问,这也属于迭代器失效

解决方法

  如上,我们会发现,扩容缩容等都有可能导致迭代器失效,因此,VS考虑到这点,迭代器it使用过一次后就立马失效,若再次使用或者访问则报错,因此,每次使用前,对迭代器进行重新赋值是不二的好方法!

		std::vector<int> v1;

		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		int x;
		cin >> x;

		std::vector<int>::iterator it = find(v1.begin(), v1.end(), x);
		if (it != v1.end()) {
			it = v1.erase(it); // right
			// 	v1.erase(it); // err
			
			if (it != v1.end()) {
				cout << *it << endl; 
			}
		}
	vector<int> v;
	for (size_t i = 1; i <= 6; i++)
	{
		v.push_back(i);
	}
	
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) // 删除容器当中的全部偶数
		{
			it = v.erase(it); // 删除后获取下一个元素的迭代器
		}
		else
		{
			it++; // 是奇数则it++
		}
	}

总结

  可以还是发现有新内容的,请加油,下篇vector的实现会更加精彩!

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

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

相关文章

JVM Class类文件结构

国庆节快乐 2024年10月2日17:49:22 目录 前言 magic 数 文件版本 使用JClassLib观察class文件 一般信息 接口 常量池 字段 方法 常量池计数器 常量池 类型 CONSTANT_Methodref_info CONSTANT_Class_info 类型结构总表 访问标志 类索引, …

信息安全工程师(30)认证概述

前言 认证&#xff0c;作为一种信用保证形式&#xff0c;是通过一系列的程序和标准来确认某人或某物的身份、资格、性能或质量的过程。其重要性不言而喻&#xff0c;是国家规范经济、促进发展的重要手段&#xff0c;也是政府保障产品、生态和人民生命财产安全的关键措施&#…

C语言 | Leetcode C语言题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; int cmp(void* _a, void* _b) {int *a *(int**)_a, *b *(int**)_b;return a[1] < b[1] ? -1 : 1; }int findMinArrowShots(int** points, int pointsSize, int* pointsColSize) {if (!pointsSize) {return 0;}qsort(points, pointsSi…

深度剖析音频剪辑免费工具的特色与优势

是热爱生活的伙伴或者想要记录美好声音的普通用户&#xff0c;都可能会需要对音频进行剪辑处理。而幸运的是&#xff0c;现在有许多优秀的音频剪辑软件提供了免费版本&#xff0c;让我们能够轻松地施展音频剪辑的魔法。接下来&#xff0c;就让我们一同深入了解这些音频剪辑免费…

【Docker】docker的存储

介绍 docker存储主要是涉及到3个方面&#xff1a; 第一个是容器启动时需要的镜像 镜像文件都是基于图层存储驱动来实现的&#xff0c;镜像图层都是只读层&#xff0c; 第二个是&#xff1a; 容器读写层&#xff0c; 容器启动后&#xff0c;docker会基于容器镜像的读层&…

VScode 自定义代码配色方案

vscode是一款高度自定义配置的编辑器, 我们来看看如何使用它自定义配色吧 首先自定义代码配色是什么呢? 看看我的代码界面 简而言之, 就是给你的代码的不同语义(类名, 函数名, 关键字, 变量)等设置不同的颜色, 使得代码的可读性变强. 其实很多主题已经给出了定制好的配色方案…

「C++系列」预处理器

【人工智能教程】&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站&#xff1a;【人工智能教程】 文章目录 一、预处理器1. 宏定义&#xff08;Macro Definition&#xff09;2…

【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。使用的软件&#…

【C语言】数组(下)

6、二维数组的创建 6.1二维数组的概念 通过数组&#xff08;上&#xff09;介绍&#xff0c;我们学习了一维数组&#xff0c;数组的元素都是内置类型的&#xff0c;如果我们把一维数组作为数组的元素&#xff0c;这时就是二维数组&#xff0c;以此类推&#xff0c;如果把二维…

基于单片机跑步机控制系统设计

** 文章目录 前言概要功能设计设计思路 软件设计效果图 程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对…

【嵌入式裸机开发】智能家居入门3(MQTT服务器、MQTT协议、微信小程序、STM32)

前面已经写了两篇博客关于智能家居的&#xff0c;服务器全都是使用ONENET中国移动&#xff0c;他最大的优点就是作为数据收发的中转站是免费的。本篇使用专门适配MQTT协议的MQTT服务器&#xff0c;有公用的&#xff0c;也可以自己搭建 前言一、项目总览二、总体流程分析1、了解…

海外合规|新加坡推出智慧国2.0计划 设新网络安全与保障机构

智慧国2.0计划&#xff1a;政府将成立新机构杜绝网络伤害和援助受害者。政府将成立新机构&#xff0c;并制定新法令&#xff0c;以杜绝网络伤害行为和为受害者提供更多援助与保障。新加坡总理兼财政部长黄循财星期二&#xff08;10月1日&#xff09;在推介晚宴上&#xff0c;宣…

基于单片机的花色可调跑马灯设计

**基于单片机频率花色可调跑马灯 文章目录 前言概要设计思路 软件设计效果图 程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/A…

51c自动驾驶~合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/11466109 #HTCL 超过所有视觉方案&#xff01;HTCL&#xff1a;分层时间上下文问鼎OCC 本文是对ECCV2024接受的文章 HTCL: 的介绍&#xff0c;HTCL在SemanticKITTI基准测试中超过了所有基于相机的方法&#xff0c;甚至在和…

在pycharm中使用PySpark 出现Java gateway process exited before sending its port number.

# 原因是没有下载Java&#xff08;jdk&#xff09; 程序出现下面错误&#xff1a; 解决办法&#xff1a; 1、2、3、先点击“” &#xff0c;添加这一行&#xff0c;点击确定即可。再次之前先判断你电脑上没有jdk&#xff0c;有的话&#xff0c;直接添加&#xff0c;也可以手动…

国庆节快乐前端(HTML+CSS+JavaScript+BootStrap.min.css)

一、效果展示 二、制作缘由 最近&#xff0c;到了国庆节&#xff0c;自己呆在学校当守校人&#xff0c;太无聊了&#xff0c;顺便做一个小demo帮祖国目前庆生&#xff01;&#xff01;&#xff01; 三、项目目录结构 四、准备工作 (1)新建好对应的文件目录 为了方便&#xff…

【超详细】Python、JDK、vscode安装

Python 下载 首先去Python官网下载安装程序Python官网&#xff0c;鼠标悬浮到Download后选择推荐的Python版本(笔者为Windows系统故选择Windows版本安装程序) 之后点击打开文件&#xff0c;或者点击文件的图标打开下载的目录&#xff0c;打开下载好的安装程序 安装 首先勾…

测试-----BUG篇

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 软件测试的生命周期bug的概念描述bugbug的级别bug的生命周期 软件测试的生命周期 软件测试贯穿与软件的整个生命周期&#xff0c;它的具体流程是: 1.需求分析 2.测…

【初阶数据结构】排序——归并排序

目录 前言归并排序归并排序的非递归写法计数排序排序总结 前言 对于常见的排序算法有以下几种&#xff1a; 前面我们已经学习了&#xff1a; 【初阶数据结构】排序——插入排序【初阶数据结构】排序——选择排序【初阶数据结构】排序——交换排序 下面这节我们来看最后一个…

基于SSM的O20兼职系统的设计与实现(源码+定制+文档)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…