【C++】vector和list的迭代器

news2024/11/24 14:40:34

目录

前言

一.迭代器的使用

1.vector迭代器

2.list迭代器的使用

二.迭代器失效问题

1.vector迭代器失效问题

2.list迭代器失效问题

三.vector和list的对比


前言

我们在学习C++STL部分的时候,在vector和list的部分初步认识了迭代器,以及在初学阶段,会觉得迭代器失效是一个很头痛的问题...

所以接下来我们就先从迭代器的使用开始,对迭代器进行一个浅浅的梳理,希望对大家有所帮助~

好的,废话不多说,直接开始今天的内容

一.迭代器的使用

1.vector迭代器

首先,我们需要先对其 建立一个整体的印象。在cpluslpus这个网站,我们可以先看一下它对vector容器的归纳:

因为今天的主题是迭代器,所以我们着重对上面框起来的部分进行讲解,剩下的模块大家有兴趣下来可以自行去cplusplus该网站去探索~

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);

	//遍历
	auto it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
        ++it;
        
	}
	cout << endl;

	return 0;
}

上面我们创建了一个int类型的容器,用它来完成了一个遍历的操作。使用vector其实使用的就是一段连续的数组空间

我们暂且可以将迭代器理解成一个指针

v1.begin()指向的就是第一个元素的位置,end()就是最后一个元素的下一个位置

这么看的话我们是否觉得使用迭代器还不如直接使用指针呢?那么接下来我们来看下面这段代码:

int main()
{
	vector<string> s1;
	s1.push_back("hello");
	s1.push_back("world");
	s1.push_back("hello");
	s1.push_back("iterator");

    //遍历 
	auto it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
        ++it;
	}
	cout << endl;

	return 0;
}

这次我们vector用的是string自定义类型,而不是内置类型int,这在使用以前的数组是无法办到的,这就体现了迭代器的价值,将其封装后可以使得底层可能截然不同的结构在使用上保持形式的统一~

这一点在之后的list中大家会有更深的体会,我们先继续

其实说了正向迭代器begin() 和end()之后,反向迭代器的使用rbegin()和 rend()就非常简单了,因为它们完全是一样的

请看下面代码:

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);

	//遍历
	auto rit = v1.rbegin();
	while (rit != v1.rend())
	{
		cout << *rit << " ";
		++rit;

	}
	cout << endl;

	return 0;
}

反向迭代器实现的效果只是将本来的内容反过来遍历了,使用起来也是一样的,是不是?

2.list迭代器的使用

对于list迭代器,同样的,我们需要先对其 建立一个整体的印象。在cpluslpus这个网站,我们可以先看一下它对list容器的归纳:

接下来是使用,代码如下:

int main()
{
    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 << " ";
        LT++;
	}
	cout << endl;

}

这里我们在使用上发现玩的跟上面的vector是一样的,这也就体现了迭代器的一个巨大的好处

因为其实在底层上,两者是完全不同的

我们知道,list是一种带头双向循环的链表,那么每个结点就有三个区域:

数据域data
指向下一个结点的next指针
指向前一个结点的prev指针

这时如果我们还将迭代器简单的理解为指针的话,那么不妨想一下,如果我们使用++运算符的话,还能找到下一个结点的位置吗?

答案是不能的,因为list不像vector一样是一段连续的内存空间,++以后,可能对应是下一个结点的位置,也可能不是,我们就没法确定了

那又为什么我们却能像vector一样的去使用list的迭代器呢?
 

这都归功于类的封装,在对迭代器封装的时候,我们重载了这些操作符的意义(例如:++,  * 等)

这才使得我们能就像使用指针一样去使用迭代器

至于迭代器的封装具体是怎么做到这样的封装,这里博主自己实现了一个list的迭代器,大家可以看一下思路,相当于是STL关于 list迭代器部分的一个简化版~:

//list的模拟实现分成3个类,这里就不细说了下面给出了大致的框架,并非完整的代码哦~

//节点的结构体
template<class T>
struct ListNode
{
	T _data;
	ListNode<T>* _next;
	ListNode<T>* _prev;

    //...
};



//list迭代器的模拟实现
template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;

	Node* _node;//变量

	ListIterator(Node* node)//构造
		:_node(node)
	{}


	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}


	Ref operator*()//解引用
	{
		return _node->_data;
	}

	Ptr operator->()//指针
	{
		return &_node->_data;
	}

	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}

};

//list--->链表
template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	typedef ListIterator<T, T&, T*>  iterator;
	typedef ListIterator<T, const T&, const T*>  const_iterator;


	iterator begin()
	{
		return iterator(_head->_next);
	}

	iterator end()
	{
		return iterator(_head);
	}
	 
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}

	const_iterator end() const
	{
		return const_iterator(_head);
	}
        
     //...
     //...
	}

private:
	Node* _head;//头节点
};

为了更能体现出迭代器并非简单的指针,所以就用list的迭代器作为实例啦(上面的vector的迭代器我们自己实现的话其实跟指针差不多,不具备代表性,所以就没有提啦)

好,接下来我们来看一下关于迭代器失效的问题,这也是一个重点问题

二.迭代器失效问题

1.vector迭代器失效问题

vector的迭代器失效,我们是在实现vector的insert和erase这两个接口时发现的

请看下面的代码:

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);


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

	auto it = find(v1.begin(), v1.end(), 3);//查找3的位置
	v1.erase(it);//删除3
	v1.insert(it, 0);//插入0

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


    return 0;
}

上面代码的意思就是找到3并删除掉,然后再在该位置插入0
按照理解这段代码应该是没有问题的,就是很简单的一段删除和插入嘛

然后,我们运行一下代码:
 

???代码崩溃了

其实这就是因为erase函数使得迭代器失效导致的

迭代器it原本指向的是3的位置,之后我们将他删除后,it指向的数据是4,不再是原数据了

而VS在实现函数时是assert处理的,所以才导致的上面的结果

解决办法就是it接收一下erase的返回值,erase的返回值是该位置的下一个位置的迭代器。即对it进行修正,这样就能有效的避免上面的问题

请看修正后的代码:


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);

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

	auto it = find(v1.begin(), v1.end(), 3);//查找3的位置
	it = v1.erase(it);//删除3
	v1.insert(it, 0);//插入0

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


	return 0;
}

还有一种迭代器失效是在insert时导致的

假如我们一开始的容器大小是5个,且这个容器已经满了,此时我再进行insert的时候,就会发生扩容,而C++的扩容是异地扩容的

异地扩容就必定会导致一个问题,我们原来it指向的地址发生了改变,此时如果不做处理的话,it指向的就是一块未知的空间,it就变成了一个野指针,这也是一种迭代器的失效

2.list迭代器失效问题

list的迭代器在删除时也会导致和上面的vector一样的问题,但与vector不同的是,vector在erase时,会导致当前迭代器失效,而list只会导致当前迭代器失效,其他迭代器不受影响

解决办法也是接收一下erase的返回值,同样list的erase的返回值也是该位置的下一个结点位置

但是list在insert插入的时候并不会导致迭代器失效,因为list不存在扩容的概念

三.vector和list的对比

最后我们将vector和list这2个容器进行一个对比:

在底层结构上:

vector是动态顺序表,开辟的是一段连续空间

list     是带头双向循环链表

在随机访问上:

vector支持随机访问,访问某个元素的效率为O(1)  ([ ]运算符访问)

list不支持随机访问,访问某个元素的效率为O(N) 

在插入和删除上:

vector在任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要扩容,开辟新空间,拷贝元素,释放旧空间,导致效率更低

list在任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)

迭代器上:

vector的迭代器是原生态指针,在插入和删除元素均会导致迭代器失效

list的迭代器是在原生态指针上进行了封装,插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响

在使用场景上:

vector在需要高效存储,支持随机访问,不关心插入和删除效率的场景下更适合

list在需要大量插入和删除操作,不关心访问的场景下更适合

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

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

相关文章

autocad背景色、引线文字大小

一、改变背景 在命令行输入op&#xff0c;回车&#xff0c;弹出配置对话框&#xff1a; 二、改变引线文字大小 选中引线&#xff0c;右键选择【特性】&#xff0c;在文字选项卡中设置文字高度&#xff1a;

小程序项目创建与Vant-UI引入

一&#xff0c;创建小程序项目 AppID可先用测试号&#xff1b; 模板来源选择 ’全部来源‘ &#xff0c;’基础‘ 。模板一定JS开头的&#xff1b; vant-weapp 官网 vant-Weapp 二&#xff0c;下载vant-weapp 组件 1&#xff0c;在新项目中打开 ’调试器‘&#xff1b; 2…

2024年人工智能与机械自动化技术国际会议( ICAIMAT 2024)

2024年人工智能与机械自动化技术国际会议( ICAIMAT 2024) 会议简介 随着科技的飞速发展&#xff0c;人工智能和机械化自动化技术已成为全球产业升级和经济发展的重要动力。为了进一步促进国际交流与合作&#xff0c;推动人工智能和机械化自动化技术的创新与应用&#xff0c;我…

【IDEA】-使用IDEA查看类之间的依赖关系

1、父子类的继承、实现关系 1.1、使用CTRL Alt U 选择 java class 依据光标实际指向的类位置 用实心箭头表示泛化关系 是一种继承的关系&#xff0c;指向父类 可以提前设置需要显示的类的属性、方法等信息 快捷键 Ctrl Alt S &#xff0c;然后搜索 Diagrams 1.2、使用…

java期末突击

目录 1. 快速入门 2. 类 3. 成员方法 4. 构造器 5. 单例模式 6. 继承 7. 抽象类 8. 多线程&#xff08;Thread与Runnable&#xff09; 1. 快速入门 public class Hello {public static void main(String[] args){System.out.println("Hello,World&q…

对象转为Map

方案一&#xff0c;Jackson String json objectMapperFace.writeValueAsString(contract);Map<String,Object> map objectMapperFace.readValue(json, Map.class);方案二 &#xff0c; apache BeanUtils Map<String,String> beanMap null;try {beanMap BeanUti…

极简朋友圈rmoments多用户版

什么是 moments &#xff1f; moments 是仿照微信朋友圈开发的个人极简朋友圈。但目前不支持多用户。 什么是 rmoments &#xff1f; rmoments 复刻自 moments&#xff0c;除了支持全部原生功能外&#xff0c;最大的特点是支持多用户。 原版 moments 目前不支持多用户。rmoment…

整理GTX收发器示例工程(高速收发器十一)

前文分析了xilinx官方提供的GTX IP示例工程&#xff0c;该代码的结构比较混乱&#xff0c;本文将该代码进行梳理&#xff0c;形成一个便于使用的模块&#xff0c;后续如果要使用多通道的收发器&#xff0c;多次例化某个模块就行了。 下图是官方例程中GTX IP相关模块的RTL视图&a…

ArcGIS教程(02):创建多模式网络数据集

启动“新建网络数据集”向导 命名网络并选择源要素类 输入网络数据集名称【ParisMultimodal_ND】&#xff0c;点击【下一页】 点击【全选】网络数据集中的要素类 点击【下一页】 设置连通性和高程策略 点击【连通性】 Metro_Entrances 的每个要素与街道要素类的折点重…

九部门联合发文知识产权保护体系建设,微版权打造全链条知产保护

近日&#xff0c;国家知识产权局会同中央宣传部、最高人民法院、最高人民检察院、公安部、司法部、商务部、海关总署、国家市场监督管理总局等八部门联合印发《知识产权保护体系建设工程实施方案》(以下简称《方案》)&#xff0c;共同加强知识产权保护体系建设。 《方案》是新时…

如此简单,一文带你玩转接口自动化上(Python + Pytest + Requests + Allure )

一. 前言 哈喽大伙们好&#xff0c;好久不见距离上次更新博客已经有一年之久了&#xff0c;这将近一年的时间小编主要的时间都花在了实习和24届校招上面了&#xff0c;最终也是收获满满&#xff0c;选择了一个还不错的offer&#xff0c;感谢一路走来的自己和身边朋友的帮助&…

Visual Studio中调试信息格式参数:/Z7、/Zi、/ZI参数

一般的调试信息都保存在pdb文件中。 Z7参数表示这些调试信息保存到OBJ目标文件中&#xff0c;这样的好处是不需要单独分发PDB文件给下游。Zi就是把所有的调试信息都保存在pdb文件中&#xff0c;以缩小发布文件的大小。ZI和Zi类似&#xff0c;但是增加了热重载的能力&#xff1…

【传知代码】MonoCon解读与复现(论文复现)

前言&#xff1a;在快速发展的计算机视觉领域&#xff0c;单目视觉&#xff08;Monocular Vision&#xff09;技术凭借其独特的优势和广泛的应用前景&#xff0c;逐渐成为了研究的热点。MonoCon作为单目视觉领域的一项重要技术&#xff0c;其独特的算法设计和高效的性能表现&am…

使用pkg打包了一个使用了sqlite3的nodejs项目,启动后闪退

从截图来看&#xff0c;问题出在 sqlite3 模块上。说明在打包过程中&#xff0c;sqlite3 模块的 .node 文件没有正确加载。 紧急解决方法&#xff1a; 其实就是exe文件还需要node_modules中的sqlite3 依赖&#xff0c;我们直接在系统顶级放一个node_modules&#xff0c;且其中只…

(2024,Video2Game,NeRF,Mesh,物理模块,游戏引擎)通过单个视频实现实时、交互、逼真且兼容浏览器的环境

Video2Game: Real-time, Interactive, Realistic and Browser-Compatible Environment from a Single Video 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 相关工作 3. Video…

论文115:Reinforced GNNs for multiple instance learning (TNNLS‘24)

文章目录 1 要点2 预备知识2.1 MIL2.2 MIL-GNN2.3 Markov博弈2.4 深度Q-Learning 3 方法3.1 观测生成与交互3.2 动作选择和指导3.3 奖励计算3.4 状态转移和终止3.5 多智能体训练 1 要点 题目&#xff1a;用于MIL的强化GNN 代码&#xff1a;https://github.com/RingBDStack/RG…

Ollama 本地大模型框架

该篇教程主要讲解*Ollama的安装和简单使用* Ollama&#xff1a; 在本地启动并运行大型语言模型。 主要流程目录&#xff1a; 1.安装 2.使用 2.1.下载模型 2.2.简单使用 2.3.中文模型 2.4.中文社区 3.总结 1.安装 创建一个容器 切换”高级视图“ 参考填写 ollama oll…

ARM32开发——总线与时钟

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 APB总线时钟树时钟树 外部晶振内部晶振 在这个例子中&#xff0c;这条大街和巴士构成了一套系统&#xff0c;我们称之为AHB总线。 …

响应式界面控件DevExtreme - 更强的数据分析和可视化功能

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…

新火种AI|OpenAI要和苹果合作了?微软有些不高兴

作者&#xff1a;一号 编辑&#xff1a;美美 和苹果之间的合作&#xff0c;可能会称为Altman引以为傲的功绩。 根据 The Information 援引知情人士的消息&#xff0c;OpenAI 已经和苹果达成了协议&#xff0c;将在其产品中运用 OpenAI 的对话式 AI。 如果进展顺利&#xff…