C++:vector和list的迭代器区别和常见迭代器失效问题
迭代器常见问题的汇总
- vector迭代器和list迭代器的使用
- vector迭代器
- list迭代器
- vector迭代器失效问题
- list迭代器失效问题
- vector和list的区别
vector迭代器和list迭代器的使用
学习C++,使用迭代器和了解迭代器失效的原因是每个初学者都需要掌握的,接下来我们就先从使用迭代器开始,帮助大家浅浅的认识一下迭代器究竟是个什么东西
首先有一点我们需要认识到的是,vector和list在内存上是有差异的,vector是连续的一段内存,而list是链表方式建立起来的。
vector迭代器
查询网站–www.cplusplus.com 建议切换到旧版进行查询

因为本篇文章的重点在于了解迭代器的失效问题,所以这里我们只介绍一下前四个的使用,要想了解的更多,还请大家查询相关的文章。
#include <iostream>
#include <vector>
#include <list>
using namespace std;int main()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//通过迭代器来遍历vector容器auto it = v1.begin();while (it != v1.end()){cout << *it++ << " ";}cout << endl;return 0;
}
我们暂时可以将vector的容器理解成指针,这能更好的帮助我们理解接下来的东西,但是是否真的是指针,是跟编译器有关的,像VS下vector的迭代器就不是指针,而g++下就是使用指针来实现的。
上面我们创建了一个int类型的容器,使用vector就是说明使用的是一段连续的空间(数组),如下图所示:

begin()就是第一个元素的位置,end()就是最后一个元素的下一个位置,这么看的话我们是否觉得使用迭代器还不如直接使用指针呢?但是别忘了vector是被叫作容器的,看下面的一段代码:
vector<string> s1;s1.push_back("holle");s1.push_back("world");s1.push_back("holle");s1.push_back("iterator");auto lt = s1.begin();while (lt != s1.end()){cout << *lt++ << " ";}cout << endl;
这次我们在容器中放入的是字符串类型,而不是内置类型,这在使用传统的数组是无法办到的,这才是vector容器的价值体现,所以我们C++才需要封装一个叫迭代器的东西以便我们在面对不同的容器的时候都能通过同样的方式去遍历和访问,这一点在list中的体现更大。
list迭代器

list<int> ls;//链表ls.push_back(1);ls.push_back(2);ls.push_back(3);ls.push_back(4);ls.push_back(5);auto LT = ls.begin();while (LT != ls.end()){cout << *LT++ << " ";}cout << endl;
在使用上是看不出来vector和list的迭代器的差别的,但是我们细想一下的话,就能发现其中并不是那么简单的!
list是一种带头双向循环的链表,那么每个结点就有三个区域
指向下一个结点的next指针
指向前一个结点的prev指针和
数据域data
如图:

这时如果我们还将迭代器简单的理解为指针的话,那么不妨想一下,如果我们使用++运算符的话,还能找到下一个结点的位置吗?答案是不能的,因为list不像vector一样是一段连续的内存空间,我们如果还以为迭代器是指针的话,那么++的位置只能是结点的下一个位置,但这个位置极大的概率不是下一个结点的位置。
为什么说极大的概率呢?虽然list构建链表的时候,使用的是空间碎片构建的,但是不排除正好是使用的连续的两块地址空间构造的。
那又为什么我们却能像vector一样的去使用list的迭代器呢?
而且*就是访问的数据,++和- -就能找到结点的后一个结点和前一个结点。
这都归功于类的封装,在对迭代器封装的时候,重新的定义了这些符号的意义(++, - - , *)
也就是符号的重载。这才使得我们能就像使用指针一样去使用迭代器,但实际上它们的底层早已千差万别。
迭代器的封装具体是怎做到,可以看一下这里的代码(list的模拟实现),这里就模拟了一份迭代器的封装,重要的不是看懂这些代码,而是认识到迭代器并不是简单的指针
代码:
//结点template<class T>struct list_node{list_node* next;list_node* prev;T _data;list_node(const T& x = T())//所以要实现默认构造函数:next(nullptr), prev(nullptr), _data(x){}};// 1、迭代器要么就是原生指针// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为//这里如果单纯的使用结点的指针作为迭代器的话,是没法用++等操作符操作的,所以我们对结点的迭代器进行了//封装!!!template<class T, class Ref, class Ptr>//模板参数一个一个的去理解 1.先不考虑const对象的情况struct _list_iterator{//迭代器内部并不需要进行析构,因为这个类对结点的指针进行了封装,以便我们能够更好的访问链表typedef list_node<T> node;node* _node;typedef _list_iterator<T, Ref, Ptr> self;//构造出一个结点的指针,初始化这个指针的指向为我们想要的结点的位置_list_iterator(node* n):_node(n){}//const T* operator->()Ptr operator->() //优雅!{return &_node->_data;//取地址}Ref operator*() //Ref-->const T& / Ref-->T& !!!!优雅{return _node->_data;}self& operator++() //前置++{_node = _node->next;return *this;}self operator++(int) //后置++{self temp(*this);_node = _node->next;return temp;//返回的是++之前的位置,也就是原来的位置,但是迭代器已经向后移动了}self& operator--(){_node = _node->prev;return *this;}self operator--(int){self temp(*this);_node = _node->prev;return temp;}//bool operator!=(self n)//权限的放大和缩小只针对引用和指针类型bool operator!=(const self& n){return _node != n._node;}bool operator==(const self& n){return _node == n._node;}};template<class T>class list{typedef list_node<T> node;public://第三个模板参数是类型指针,为了重载->时使用typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, T*> const_iterator;void empty_init(){_head = new node;_head->next = _head;_head->prev = _head;}//构造函数--创建头结点list(){empty_init();}template<class iterator>list(iterator first, iterator second){empty_init();while (first != second){push_back(*first);++first;}}void swap(list<T>& temp){std::swap(_head, temp._head);}list(const list<T>& lt){empty_init();list<T> temp(lt.begin(), lt.end());swap(temp);}/*list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}*/list<T>& operator=(list<T> lt)//这里不能使用引用,会改变原来list的数据{swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){//it = erase(it);erase(it++);//后置++}}iterator begin(){return iterator(_head->next);//头结点的下一个就是第一个结点的位置}iterator end(){return iterator(_head);//头结点就是end结点的位置}const_iterator begin() const{return const_iterator(_head->next);}const_iterator end() const{return const_iterator(_head);}void insert(iterator pos, const T& val){node* cur = pos._node;//当前位置node* pre = cur->prev;//pos的前一个位置node* new_node = new node(val);//开始插入pre->next = new_node;new_node->prev = pre;new_node->next = cur;cur->prev = new_node;}iterator erase(iterator pos){assert(pos != _head);node* pre = pos._node->prev;//pos位置的前一个node* next = pos._node->next;//pos位置的后一个pre->next = next;next->prev = pre;delete pos._node;return iterator(next);//返回的是删除位置的下一个位置}void push_back(const T& val){创建结点//node* new_node = new node(val);//node* tail = _head->prev;//链表的最后一个结点的位置尾插过程//tail->next = new_node;//new_node->prev = tail;//new_node->next = _head;//_head->prev = new_node;insert(end(), val);//复用}void pop_back(){//erase(_head->prev);erase(--end());}void push_front(const T& val){//insert(_head->next);insert(begin(), val);}void pop_front(){//erase(_head->next);erase(begin());}private:node* _head;//头结点指针};
这份代码也是模拟实现list的完整代码。
vector迭代器失效问题
int main()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//通过迭代器来遍历vector容器auto it = find(v1.begin(), v1.end(), 3);//在begin()--end()的区间内查找1v1.erase(it);v1.insert(it, 0);for (auto e : v1){cout << e << " ";}cout << endl;
}
上面代码的意思就是找到3的并删除掉他,然后再在该位置插入0。
按照理解这段代码应该是没有问题的,就是很简单的一段删除和插入嘛。
结果:

代码崩了?
这里就是很典型的迭代器失效问题,迭代器it原本指向的是3的位置,之后我们将他删除后,it指向的数据是4,不再是原数据了,这就是迭代器的失效。对于迭代器失效VS是assert断言处理的,所以我们才看到上面的信息。
同样- -也会导致一样的问题。
解决办法就是it接收一下erase的返回值,erase的返回值是该位置的下一个位置的迭代器。这样就能有效的避免上面的问题。

还有一种迭代器失效是在insert时导致的。
假如我们一开始的容器大小是5个,且这个容器已经满了,此时我再进行insert的时候,就会发生扩容。
而C++的扩容是异地扩容的,并没有像C语言的realloc函数一样的接口。
异地扩容就必定会导致一个问题,我们原来it指向的地址发生了改变,此时如果不做处理的话,it指向的就是一块未知的空间,it就变成了一个野指针,这也是一种迭代器的失效。
这个问题在大家模拟实现vector的时候是特别需要注意的一点。
list迭代器失效问题
list的迭代器在删除时也会导致和上面相同的问题,解决办法也是接受一下erase的返回值,同样list的erase的返回值也是该位置的下一个结点位置(next)。
但是list在插入的时候并不会导致迭代器失效,因为list不存在扩容的概念。


list就不存在扩容的问题了。
vector和list的区别
-
vector可以随机访问,通过[ ],而list不支持随机访问。vector访问某个元素的效率为O(1)
list访问某个元素的效率为O(n)。 -
vector中插入和删除都可能导致迭代器失效,而list中插入元素不会导致迭代器失效,删除元素会导致迭代器失效;
vector中插入元素有可能需要增容,迭代器指向的之前的空间会被释放,之前的迭代器会失效;删除元素也会导致迭代器失效;
list中插入元素只是添加了一个新的节点,不会导致之前存在的迭代器失效,而删除元素只会导致当前迭代器失效,其他迭代器不会受到影响。 -
vector中的迭代器是原生态指针,也就是元素类型的指针(vs下不是,g++是); list中的迭代器是对原生态指针(节点指针)的封装;
相关文章:
C++:vector和list的迭代器区别和常见迭代器失效问题
迭代器常见问题的汇总vector迭代器和list迭代器的使用vector迭代器list迭代器vector迭代器失效问题list迭代器失效问题vector和list的区别vector迭代器和list迭代器的使用 学习C,使用迭代器和了解迭代器失效的原因是每个初学者都需要掌握的,接下来我们就…...
SpringSecurity如何实现前后端分离
前后端分离模式是指由前端控制页面路由,后端接口也不再返回html数据,而是直接返回业务数据,数据一般是JSON格式。Spring Security默认的表单登录方式,在未登录或登录成功时会发起页面重定向,在提交登录数据时ÿ…...
为ubuntu 18.04添加蓝牙驱动
目录背景方法背景 从网上买的能直接插ubuntu 1804的usb蓝牙太少了,而且还贵。我就直接从JD下单的一个便宜的USB蓝牙,结果插上机器没有驱动起不来。我的PC是个3年前的老机器,实在是不想升级系统,于是捣鼓半天捣鼓好了,…...
Stable Diffusion Prompt用法
Stable Diffusion可以根据你输入的提示词(prompt)来绘制出想象中的画面。 1、正向提示词(Prompt): 提高图像质量的prompt: prompt用途HDR, UHD, 64K(HDR、UHD、4K、8K和64K)这样的质量词可以带来巨大的差异提升照片…...
jenkins问题
目录 python 不是内部或外部命令,也不是可运行的程序 ‘cmd’ 不是内部或外部命令,也不是可运行的程序或批处理文件。 git 不是内部或外部命令,也不是可运行的程序或批处理文件。 pywintypes.com_error: (-2147024891, ‘拒绝访问。’, None,…...
阅读笔记DeepAR: Probabilistic Forecasting with Autoregressive Recurrent Networks
zi,t∈Rz_{i,t}\in \mathbb{R}zi,t∈R表示时间序列iii在ttt时刻的值。给一个连续时间段t∈[1,T]t\in [1, T]t∈[1,T],将其划分为context window[1,t0)[1,t_0)[1,t0)和prediction window[t0,T][t_0,T][t0,T]。用context window的时间序列预测prediction window…...
01.Java的安装
1.JDK&JREJDK : Java SE Development Kit--Java开发工具JRE : Java Runtime Environment--Java运行环境Java编程,需要安装JDK;如果仅仅是运行一款Java程序则只需要运行JREJava的安装包分为两类:一类是JRE--是一个独立的Java运行环境; 一类…...
【C语言深度剖析】关键字(全)
文章目录一.存储类型关键字前言补充1:内存思考:补充2:变量与内存的关系补充3:变量的分类补充4:存储类补充5:删除数据是怎么删除的?1.auto2.register3.static4.extern基本用法:基本功能5.typedef…...
English Learning - L2 语音作业打卡 双元音 [aʊ] [əʊ] Day15 2023.3.7 周二
English Learning - L2 语音作业打卡 双元音 [aʊ] [əʊ] Day15 2023.3.7 周二💌发音小贴士:💌当日目标音发音规则/技巧:🍭 Part 1【热身练习】🍭 Part2【练习内容】🍭【练习感受】🍓元音 /eɪ…...
记第一次面试的过程(C++)
说实话三月份上旬过得很充实,而且感觉蛮值,但还有不足的地方,今晚特地看完资料分析来复盘复盘。 时间还要回到3.2中午13.35(别问我为什么那么准确,刚刚掏手机看的),我正在吃着饭看着王者荣耀的直…...
06 电力电子仿真 MATLAB/Simulink
文章目录01 单相半波整流电路02 单相全波整流电路(子系统封装模块)03 三相桥式整流电路(三相模块与示波器使用)04 相控与斩控交交调压(THD计算)05 Buck电路(PWM实现与闭环反馈)06 单…...
搞懂面向对象这五大概念,才算真正跨过初学者到开发者的“分水岭“
文章目录前言一、对象二、类三、面向对象程序设计的特点1. 封装2. 继承3. 多态前言 面向对象程序设计是在面向过程程序设计的基础上发展而来的,它比面向过程编程具有更强的灵活性和扩展性。面向对象程序设计也是一个程序员发展的 “分水岭”,很多的初学者…...
基于DelayQueue实现的延时队列
基于java中延时队列的实现该文章,我们这次主要是来实现基于DelayQueue实现的延时队列。 使用DelayQueue实现的延时队列的步骤: 定义一个继承了Delayed的类,定义其中的属性,并重写compareTo和getDelay两个方法创建一个Delayqueue…...
MATLAB实现层次分析法AHP及案例分析
层次分析法(Analytic Hierarchy Process, AHP) 1 模型背景 美国运筹学家匹兹堡大学教授Saaty在20世纪70年代初提出的一种层次权重决策分析方法。 层次分析法(Analytic Hierarchy Process, AHP)是一种定性和定量分析相结合的决策分析方法。 特点:用较少的定量信息使决策的…...
Vue 3.0 TypeScript支持
Vue CLI 提供内置的 TypeScript 工具支持。 #NPM 包中的官方声明 随着应用的增长,静态类型系统可以帮助防止许多潜在的运行时错误,这就是为什么 Vue 3 是用 TypeScript 编写的。这意味着在 Vue 中使用 TypeScript 不需要任何其他工具——它具有一流的公…...
STM8S系列基于IAR标准外设printf输出demo
STM8S系列基于IAR标准外设printf输出demo📌STM8S/A标准外设库(库版本V2.3.1)📍官网标准外设库:https://www.st.com/zh/embedded-software/stsw-stm8069.html ⛳注意事项 🚩在内存空间比较有限的情况下&am…...
PMP项目管理项目质量管理
目录1 项目质量管理概述2 规划质量管理3 管理质量4 控制质量1 项目质量管理概述 项目质量管理包括把组织的质量政策应用于规则、管理、控制项目和产品质量要求,以满足相关方目标的各个过程。项目质量管理还将以组织的名义支持过程的持续改进活动。 核心概念 质量是…...
前缀和总结
前缀和是一个常用的算法技巧,通常用于求解数组或序列的区间和。 具体来说,假设有一个长度为n的数组a,我们可以预处理出一个长度为n+1的前缀和数组s,其中s[i]表示原数组a前i个元素的和,即: s[i] = a[0] + a[1] + ... + a[i-1] 这样一来,对于任意的区间[l, r],我们可以…...
0109二分图-无向图-数据结构和算法(Java)
文章目录1 概念2 API3 分析和实现4 测试5 总结后记1 概念 二分图是一种能将所有结点分为两部分的图,其中图的每条边所连接的两个顶点都分别属于不同的部分。 2 API public classBipartiteBipartite(Graph G)预处理函数public booleanisBipartitle()是否是二分图pub…...
计算机网络题库---选择题刷题训练(100多道精品)
第一章 概述 1.下列四项内容中,不属于Internet(因特网)基本功能是___D_____。 A.电子邮件 B.文件传输 C.远程登录 D.实时监测控制 2.Internet是建立在____C_____协议集上的国际互联网络。 A.IPX B.NetBEUI C.TCP/IP …...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
