list的迭代器

1.list介绍

list在stl中是一种非常重要的容器,他的底层逻辑是我们之前的数据结构中的链表,而且这里是一种带头双向循环链表。

这是较为官方的文档介绍,大致意思就是:

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在forward_list是单链表,只能朝前迭代,已让其更简单高
    效。
  4. 与其他的序列式容器相比(array,vector,deque)list通常在任意位置进行插入、移除元素的执行效率
    更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素
    list的图解:

    就是这个图解的部分,所以list的实现可以说,我们大家都非常熟悉,但是list的难点在于他的迭代器实现,所以本章标题也围绕着list的迭代器。

2.list的使用

2.1list的构造

1:list(size_type n,const value_type&val=value_type())

主要用于构造list中包含的n个值为val的元素。

2:list()构造空list

3:list(const list&x)拷贝构造函数

4:list (InputIterator first, InputIterator last),用(first,last)区间中的元素构造list

2.2list的iterator的使用

还是那句话,list可以理解为一个指针,指向list中的某个节点。

注意:

1:begin于end为正向迭代器,对迭代器执行++操作,迭代器向后移动

2:rbegin(end)于rend(beegin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

2.3list的capacity问题

2.4list的元素操作

1:push_front

2:pop_front

3:push_back;

4:pop_back

5:insert

6;erase

7:swap 用于交换两个list中的元素

8:clear 清空list中的元素

相信有了之前链表的学习,大家都知道,上面的接口都怎么使用,代表什么意思。

3.关于list的迭代器失效

list的迭代器失效情况,较为单一,最常见的就是erase后还访问pos处的位置,这就是完完全全的野指针访问了,一般情况下的erase接口的返回值一般是iterator,返回删除节点下一个节点的指针。

4.list的模拟实现

list的模拟实现,同我们之前的双向链表的搭建,一样,不同的是,这里的list是容器,而为了和算法进行配合,就多了一个迭代器,多这个迭代器不要紧,要紧的是这个迭代器全是坑,因为对比vector来说,list的存储形式并不连续,而且存储的是节点,我们不能对齐,直接使用,需要对list的迭代器单独实现。

4.1实现list的三个类

list是双向链表,所以实现链表就必须要有节点,然后之前我们提到的迭代器类,以及最后才是我们的list类。

总结:
节点,迭代器,list

4.2list节点类的封装

回顾我们之前自主实现链表的代码,节点的三个成员分别是next,prev,以及最后的data

有了这个类,我们做许多事情就方便的多,因为后续有list的增删改查,大部分都要创建新节点,有了这个类,我们只用:

Node* newnode=new Node;

这样我们就创建了与新的节点。

4.3list类的实现

因为我们之前说过,list是一种带头双向循环的链表,因此我们的list成员只用封装一个头节点_head即可:

下面是对头节点的初始化

接下来是实现list的size接口,其实就是寻找有几个节点,因此我们就可以利用迭代器的遍历完成。

还有一个非常重要的接口那就是插入接口,相信双向链表的增删改查大家都知道,我这里就不多解释,只提供代码了:


5.list迭代器类的封装

这里我们提到过list的迭代器是一个重点,它不同于之前的string以及vector,他并不是连续储存的,所以访问它不能直接进行++或--的操作,同时,我们对于迭代器的要求是我们可以用迭代器

iteartor it

来来实现对list中数据的访问,而不是节点的访问,所以节点的指针就不能用了,综合以上,我们只有对list的节点指针进行再封装,让其单独成为一个类,这时我们才能更好的实现后续的访问。

5.1list迭代器的成员

list迭代器的模拟实现是封装和重载

封装就是让其成为一个单独的类,重载就是当我们访问iterator时,看着是访问节点,实则是访问节点中的数据。所以,我们迭代器类的成员只有一个,那就是该节点:

5.2迭代器的重载

5.2.1解引用的重载

我们说了,解引用节点的地址,实则要解引用节点中存储数据的地址,因此,我们的解引用操作要实现,返回节点中的数据。

5.2.2箭头符号重载

箭头符号于解引用的操作相同,所以我们返回的不是节点的指针,而是节点中数据的指针:

5.2.3++的重载

我们知道迭代器有一个重要的玩法就是,当我们++时,迭代器就指向了下一个元素,而这里我们的迭代器要实现的就是返回下一个节点的地址,所以其逻辑和链表的遍历差不多:

5.2.4!=符号的重载

我们使用迭代器进行遍历时,一定有一种使用方法,那就是

while(it!=ls.end());

所以我们就要实现!=符号的重载。

同样也是比较两个节点的地址,不用比较数据

注意上面我的注释,带你重温以下类和对象的内容:
因为的it.end()的返回值时iterator的传值返回,所以会生成一个iterator的临时对象,而临时对象具有常性,因此我们这里的形参就必须是const的

5.3const迭代器实现优化

现在我们上面实现了一个普通迭代器的封装重载,但是还有一个重要的迭代器,就是const迭代器要怎么实现呢?或许你会这样想:

typedef const iterator const_iterator

然后重新写一个类,来实现const迭代器,(注意这里的const迭代器,指的是节点里的数据是const的,而不是节点是const的所以 list const iterator是错误的用法),但是你有没有想到过,我们这两种迭代器的不同,仅仅只是接口返回值的不同,其他的函数体都一样,那为什么我们不套用模板,来实现不同迭代器的复用呢?是不是一种特别漂亮的做法。

我们套用不同的迭代器种类,他就是不同的iterator,这样就通过模板实现了,迭代器的复用。

6.总结

我们list的类还有很多接口没有实现,我这里不写其一是有些接口确实不常用,其二就是我们已经谢过了string类和vector类,对数据结构肯定有了自己的认识,所以就不再赘述了,这篇文章主要还是为了,让大家搞明白,list的迭代器。

相关推荐
~~李木子~~3 小时前
机器学习集成算法实践:装袋法与提升法对比分析
人工智能·算法·机器学习
老歌老听老掉牙3 小时前
使用 OpenCASCADE 提取布尔运算后平面图形的外轮廓
c++·平面·opencascade
微笑尅乐3 小时前
三种思路彻底掌握 BST 判断(递归与迭代全解析)——力扣98.验证二叉搜索树
算法·leetcode·职场和发展
闻缺陷则喜何志丹3 小时前
【动态规划】数位DP的原理、模板(封装类)
c++·算法·动态规划·原理·模板·数位dp
豆沙沙包?4 小时前
2025年--Lc194-516. 最长回文子序列(动态规划在字符串的应用,需要二刷)--Java版
java·算法·动态规划
胖咕噜的稞达鸭4 小时前
二叉树搜索树插入,查找,删除,Key/Value二叉搜索树场景应用+源码实现
c语言·数据结构·c++·算法·gitee
_extraordinary_4 小时前
Java Spring配置
java·开发语言·spring
showmethetime4 小时前
基于相空间重构的混沌时间序列预测MATLAB实现
算法
进击的大海贼4 小时前
QT-C++ 自定义加工统计通用模块
开发语言·c++·qt