STL_vector详解和迭代器失效问题解释

在这里插入图片描述

文章目录

    • vector介绍
    • vector的使用
      • vector构造函数的使用
      • vector迭代器的使用
      • vector空间函数的使用
      • vector的增删查改
    • 关于迭代器失效问题

二次修订于date:2024:3:16

vector介绍

vector是一个大小动态可变的一个数组的序列容器。
动态可变自然就是他的大小可以根据插入的数据多少动态的扩容,来适应存储更多的数据。内存不够了会自动增容。数组也就是说vector可以像数组一样使用下标对每个元素进行高效的访问与修改。并且他也像数组一样在内存之中占用一块连续的内存空间。容器就是说明这个vector可以存储各种类型的数据也可以是其他容器。
image.png
第一个参数是模板参数,指明了vector可以存储任意类型的数据,第二个参数alloc是空间配置器,也就是内存池。这里给了默认的参数,使用了STL的内存池,也可以传用户写的内存池。

vector的使用

vector构造函数的使用

vector的构造函数总共由四种方式初始化。
image.png
第一是不初始化,也即一个无参的构造函数。
第二是使用n个val来构造初始化vector,这里的value_type 就是T,这里在文档首页都可以查询到。
第三是使用一个迭代器区间来初始化,不仅可以使用vector的迭代器,也可以使用其他容器的迭代器,因为这个InputIterator也是一个模板类型。
第四是拷贝构造,使用一个vector来拷贝构造一个vector对象。

下面来看一下这个使用迭代器来给vector初始化。

#include<iostream>
#include<vector>
#include<string>
using namespace std;int main()
{vector<int> v1;vector<int> v2(10, 6);vector<int> v3(v2.begin(), v2.end());string s1("hello kisskernel");vector<char> v4(s1.begin(), s1.end());return 0;
}

image.png

可以看到vector 也可以像string一样存储字符,那么vector可以代替string类嘛?

答案:不可以

string类和vector 的区别是:
vector里面是没有保存字符串里面的\0的,也就是vector这个就是数组里面存放的字符,string可以使用cout直接输出,vector只能一个一个字符的输出
第二:vector要想在末尾添加字符只能使用push_back,没有+=,append可以直接在末尾添加一个字符串
第三:string可以重载大于小于来比较大小。但是vector的比较大小并没有意义。
并且,这两个类的流提取,流插入的作用也是不一样的。就是<<和>>

vector迭代器的使用

vector也是和string一样四种迭代器类型,正向迭代器、反向迭代器、const类型的正向迭代器、const类型的反向迭代器。
iterator begin是指向vector的第一个元素,end是指向最后一个元素的下一个位置。
image.png
在vector和string这里正向迭代器就是指针,后面的容器不一定是指针,反向迭代器就是通过封装正向迭代器得到的。具
image.png

#include<iostream>
#include<vector>
#include<string>
using namespace std;int main()
{string s1("hello kisskernel");vector<char> v4(s1.begin(), s1.end());vector<char>::iterator it = v4.begin();while (it != v4.end()){cout << *it;it++;}return 0;
}

it一定是要使用!=,虽然在连续的容器里面比如是string和vector,使用<也是没有问题的。但如果是list和map就不可以使用小于号了,而!=是通用的。不管是什么类型的容器都可以使用这种方式来进行遍历。

vector空间函数的使用

image.png
关于vector的空间函数用到比较多的是size,resize,capacity,reserve
image.png
resize就是开辟n个元素的空间,并且使用val来进行初始化。
image.png
reserve就是开辟空间但是不会进行初始化。更直观的区别也就是,使用resize扩容后,size会跟着改变,使用reserve扩容后,size并不会跟着改变。
reserve扩容之后,比如reserve(100),然后再reserve(50)不会再将容量缩小,所以reserve不会缩容。
如果事先知道了vector需要的内存是多少,就可使用reserve来进行内存分配,以减少push_back内存不足的时候向系统申请内存空间产生的性能消耗。

关于vs的PJ版本他的增容方式是1.5倍作用的方式增容,也就是vs使用的STL版本。
G++使用的SGI版本的STL增容方式是严格的2倍增长。

vector的增删查改

vector增删查改函数作用简介
push_back尾插
pop_back尾删
find在容器内查找(这并不是vector的成员函数,是算法模块实现的)
insert在pos位置之前插入一个val
erase删除pos位置的值,或者删除迭代器区间
swap交换两个vector的数据空间
operator[ ]类似数组访问的方式来访问vector

image.png
find参数是模板参数的迭代器,和一个参数类型的值val,使用方法是传一个迭代器区间在这个迭代器区间内查找一个值val。返回值也是一个迭代器,就是查找到value的这个迭代器位置。这个查找的范围也是和迭代器遍历的时候是一样的左闭右开的一个区间[first,last)找不到就会返回end()位置迭代器。

image.png
insert这里给出了三种实现方式,可以再一个迭代器位置pos之前插入一个val或者n个val,或者是一段迭代器区间。

image.png
erase给出了两种实现,一种是删除pos位置,第二种是删除一个迭代器区间这个也是左闭右开的区间[first,last)

image.png
operator[]返回值是模板类型的引用其实就是n位置这个值的引用。

关于迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,而迭代器底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T*。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,或者是指针指向的空间变了,也就是这个指针的意义变了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃,特别是vs编译器对此有严格的检查,一旦使用了失效的迭代器程序就会崩溃)。

一般引起迭代器失效有两种情况,一种就是进行了某些会改变底层数据的操作之后迭代器指向的对象变了,迭代器的意义也就变了。第二种就是,迭代器指向的空间被释放了,迭代器变成了野指针。

会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、
push_back等。
下面这段代码就是insert引起的迭代器失效。

#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };auto it = v.begin();// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容// v.resize(100, 8);// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变// v.reserve(100);// 插入元素期间,可能会引起扩容,而导致原空间被释放// v.insert(v.begin(), 0);// v.push_back(8);// 给vector重新赋值,可能会引起底层容量改变,容量改变原来的空间就会被释放v.assign(100, 8);/*出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。*/while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

下面列举一种具体的迭代器失效,insert插入元素之后,迭代器失效问题。

int main()
{vector<int> v{ 2,3,1};vector<int>::iterator pos = find(v.begin(), v.end(),3);v.insert(pos, 30);cout << *pos << endl;return 0;
}

插入元素导致增容,pos就不是指向3了而是变成了野指针所以如果想要迭代器再生效就需要再次查找。当然如果没有发生增容,那么pos就是变成了指向30了,因为元素3的位置被插入了30,这时虽然pos指向了30不是空指针,但是迭代器也是失效的,如果对失效的迭代器进行解引用就会引发程序崩溃,因为迭代器的意义变了。

第二种迭代器失效问题就是,删除某些元素的时候,数据发生了挪动,导致迭代器失效。

比如要实现,删除数组中的偶数。

#include<iostream>
#include<vector>
#include<string>
using namespace std;int main()
{vector<int> v1{ 1,2,3,4,5,6,7};vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2==0){v1.erase(it);}cout << *it << " ";it++;}cout<<endl;return 0;
}

这段代码放到vs上面跑的话会直接崩溃,什么都不会输出,这是因为删除it指向的偶数之后,剩下的元素会跟着移动,但是it的位置还是不变,只是指向了被删除元素得下一个位置答案是此时迭代器已经失效了,后面对这个位置迭代器进行使用的时候就会导致程序崩溃,因为vs对迭代器失效检查得很严格,一但使用失效得迭代器,程序就会崩溃
但是Linux上面就可以正常运行,不会崩溃掉。
image.png
因为Linux对于迭代器失效得检查不是很严格,只要这个迭代器没有越界就可以正常使用,但是虽然Linux上面成功输出了结果,并不代表这段代码就是对的,将数据换成1,2,3,4,5,6那么Linux上面运行也会爆出段错误,也就是越界了。这是因为当我们删除了一个元素之后,剩下得元素向左移动it就指向了下一个元素,但是后面又将it++了,所以这个被删除元素得下一个元素没有被判断。如果有连续得偶数就会删除得不彻底,那么为什么换成1到6就会报段错误呢?
因为当最后一个元素是6,刚好这个6会被检查到,删除之后end位置向前移动就到了it得位置,但是it++跳过了end,那么就已经越界了,it后续也不可能等于end了,所以发生了段错误。

正确得代码应该如下:

#include<iostream>
#include<vector>
#include<string>
using namespace std;int main()
{vector<int> v1{ 1,2,3,4,5,6,7};vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2==0){it = v1.erase(it);}else it++;}for (auto e : v1){cout << e << " ";}return 0;
}

erase删除元素之后返回得是被删除元素得下一个位置,虽然不给it赋值it也会指向那个位置,但是这种情况下迭代器得意义被重新赋予了,所以迭代器没有失效。

insert和erase的返回值都是迭代器类型,如果使用了这俩个函数之后还需要继续使用迭代器,那么就让it接收他们的返回值,即可避免迭代器失效。insert返回的是新插入元素的第一个位置的迭代器,erase返回的是删除位置的下一个位置的迭代器。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#控制台贪吃蛇

Console.Write("");// 第一次生成食物位置 // 随机生成一个食物的位置 // 食物生成完成后判断食物生成的位置与现在的蛇的身体或者障碍物有冲突 // 食物的位置与蛇的身体或者障碍物冲突了&#xff0c;那么一直重新生成食物&#xff0c;直到生成不冲突…

JVMJava虚拟机

JVM的内存区域 程序计数器&#xff1a; 字节码解释器通过改变程序计数器来依次读取指令&#xff0c;从而实现代码的流程控制&#xff0c;如&#xff1a;顺序执行、选择、循环、异常处理。 在多线程的情况下&#xff0c;程序计数器用于记录当前线程执行的位置&#xff0c;从而当…

跨境电商选品实战——Ownips公开数据信息安全采集+Python爬虫轻松搞定Lazada电商选品

文章目录 一、引言二、Lazada电商平台选品实战2.1、分析Lazada电商平台的商品列表接口2.2、定位商品列表计算逻辑2.3、封装高质量住宅IP2.4、运行爬虫 三、数据处理及选品分析四、Ownips——企业级全球静态住宅IP&#xff0c;高效采集公开数据 一、引言 互联网与外贸的结合&am…

AI对话/绘画完整系统(附完整源码,已开源)

文章目录 功能UI界面使用地址环境完整源码 功能 支持邮件激活账号&#xff0c;微信登录&#xff0c;短信登录支持上下文对话支持GPT4,claude3文件/图片分析分析&#xff0c;几乎所有模型均支持支持模糊匹配自定义回复消息支持按此按张按余额多种扣费方式支持套餐卡密生成及自定…

D6212——安防摄像头(IPC)的步进马达及IR-CUT驱动芯片

应用领域 安防摄像头&#xff08;IPC&#xff09;的步进马达及IR-CUT驱动。 02 功能介绍 D6212内置8路带有续流二极管的达林顿驱动管阵列和一个H桥驱动&#xff0c;单芯片即可实现2个步进电机和一个IR-CUT的直接驱动&#xff0c;使得电路应用非常简单。单个达林顿管在输入电压…

SQLiteC/C++接口详细介绍之sqlite3类(十六)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十五&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍之sqlite3类&#xff08;十七&#xff09;&#xff08;未发表&#xff09; 50.sqlite…

Docker 哲学 - 容器操作

容器&#xff1a; 创建 停止 删除 强制删除&#xff08;正在运行&#xff09; run stop rm rm -f 列出本地容器&#xff1a; docker ps / docker container ls 镜像&#xff1a; search pull run &#xff1a; …

力扣100题—持续更新

目录 LC141环形列表(easy)题目描述方法1&#xff1a;快慢指针&#xff08;1&#xff09;思路&#xff08;2&#xff09;python代码&#xff08;3&#xff09;复杂度分析 LC881救生艇&#xff08;medium&#xff09;题目描述方法1&#xff1a;双指针-对撞指针&#xff08;1&…

轻松管理文件:一键批量导出位置与名称至表格

在数字化时代&#xff0c;我们每天都在与大量的文件打交道。从工作文档到个人照片&#xff0c;从研究报告到会议记录&#xff0c;管理这些文件成为了一项挑战。为了帮助您轻松应对这一挑战&#xff0c;我们推出了一项新功能——批量导出文件位置与名称至表格&#xff01;让您的…

RT-L2KS可调型漏电继电器 电压220V 动作电流30-500mA 动作时间0.2S JOSEF约瑟

RT-L系列可调型漏电继电器 系列型号&#xff1a; RT-L1K可调型漏电继电器 RT-L2K可调型漏电继电器 RT-L3K可调型漏电继电器 RT-L4K可调型漏电继电器 RT-L1KS可调型漏电继电器 RT-L2KS可调型漏电继电器 RT-L3KS可调型漏电继电器 RT-L4KS可调型漏电继电器 RT-L1K;RT-L2K漏电继电…

Day43-2-企业级实时复制intofy介绍及实践

Day43-2-企业级实时复制intofy介绍及实践 1. 企业级备份方案介绍1.1 利用定时方式&#xff0c;实现周期备份重要数据信息。1.2 实时数据备份方案1.3 实时复制环境准备1.4 实时复制软件介绍1.5 实时复制inotify机制介绍1.6 项目部署实施1.6.1 部署环境准备1.6.2 检查Linux系统支…

Ubuntu20下C/C++编程开启TCP KeepAlive

1、在linux下&#xff0c;测试tcp保活&#xff0c;可以使用tcp自带keepalive功能。 2、几个重要参数&#xff1a; tcp_keepalive_time&#xff1a;对端在指定时间内没有数据传输&#xff0c;则向对端发送一个keepalive packet&#xff0c;单位&#xff1a;秒 tcp_keep…