C++【深入 STL--list 之 迭代器与反向迭代器】

server/2025/2/7 8:01:00/

        接前面的手撕list(上)文章,由于本人对于list的了解再一次加深。本文再次对list进行深入的分析与实现。旨在再一次梳理思路,修炼代码内功。

1、list 基础架构

        list底层为双向带头循环链表,问题是如何来搭建这个list类。可以进行下面的考虑:       

1、list为带头双向循环链表,链表离不开节点。要能在list内部创建节点。
2、list内部没有数据时,也应该存在哨兵位头节点。
3、list内部的数据类型是未知的,需要写成类模板。
4、list支持迭代器访问数据,但是由于链表的结构来说,普通指针类型的迭代器不能实现++和解引用等基础访问操作,这就需要封装一个迭代器对象。

        由于链表是双向的,所以节点的成员属性主要为三个:  

  1. 节点指针类型的_next:指向该节点的下一个节点。
  2. 节点指针类型的_prev:指向该节点的前一个结点。
  3. 未知数据类型的数据_val:链表中存放的数据。

        那么list的成员属性应该有什么?

  1. 头节点的指针_head: 作为链表的起始点,通过它可以访问链表的第一个元素和最后一个元素。在双向循环链表中,头节点的_next 指针指向链表的第一个有效节点,_prev指针指向链表的最后一个有效节点。
  2. 节点的个数_size:记录链表中有效节点的数量,方便快速获取链表的长度,而不需要遍历整个链表来计算。在插入和删除节点时,需要相应地更新这个计数器。

        迭代器主要依赖于节点,利用节点才能找到节点当中的数据,并可以通过对运算符的重载实现迭代器本身的基础操作。故迭代器的成员属性为节点的指针。

        由于在list当中要使用到节点当中的所有成员变量,所以这里直接就将节点类写为struct主要就是在内部的访问限定符默认为public,迭代器类型也是同样的道理。

namespace ltq
{template<class T>struct __list_node{__list_node<T>* _prev;__list_node<T>* _next;T _val;};template<class T>struct __list_iterator{typedef __list_node<T> Node;Node* _node;};template<class T>class list{typedef __list_node<T> Node;public:typedef __list_iterator<T> iterator;private:Node* _head;size_t _size;};
}

        下面需要完善的就是三个类型的构造函数, 首先需要明确关系:list要使用的是节点类和迭代器类,在list类中创建了节点和迭代器对象就会去调用它们自己的构造函数。当然,在外部要是使用到list创建了对象,那么也会调用list自身的构造函数。

		list(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}

        new先开辟空间,然后调用Node的构造函数,由于list的哨兵位节点中可以不用存放数据,所以直接调用Node的默认构造即可。下面就需要完善Node的默认构造。

	template<class T>struct __list_node{__list_node<T>* _prev;__list_node<T>* _next;T _val;__list_node(const T& x = T()):_prev(nullptr),_next(nullptr),_val(x){}};

        这里的默认构造使用缺省参数,当没有形参传过来时就创建T类型的匿名对象对节点中的数据进行初始化。 

2、void push_back(const T& x)

        双向带头循环链表的末尾插入需要找到末尾的节点,再创建新的节点进行链接。这里需要更新_size。

		void push_back(const T& x){Node* tail = _head->_prev;Node* newNode = new Node(x);tail->_next = newNode;newNode->_prev = tail;newNode->_next = _head;_head->_prev = newNode;++_size;}

3、迭代器

        迭代器开始位置返回的是哨兵位头节点的下一个节点,迭代器的末尾返回的是哨兵位头节点的指针,这样设计就能实现左闭右开的区间。

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

         在前面我故意没有写迭代器的构造函数,其实这里就会很明显的发现,不管是什么类型的迭代器在返回的时候都是传递节点的指针。由于单参数的构造函数支持隐式类型的转换,那么节点的指针就会通过迭代器的构造函数构造出一个迭代器对象并返回,这里需要注意的是,传值返回会生成临时对象,临时对象具有常性。

        那么,我们现在来实现一下迭代器的构造函数。

	template<class T>struct __list_iterator{typedef __list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}};

3.1、必要的运算符重载

    T& operator*(){return _node->_val;}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}    __list_iterator<T> operator++(int){__list_iterator<T> tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const __list_iterator<T>& it){return _node != it._node;}

        有了迭代器就支持范围for了,现在我们来测试一下目前的list是否可用:

3.2、箭头的重载

        假如链表中存放的是结构体类型的数据,假设结构体为:

struct A
{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;
};

        如果要访问A内部的数据,此时对迭代器进行解引用是不能访问到A内部的数据的。此时重载箭头,通过拿到A对象的地址,使用A的地址来达到访问内部数据的内容。重载箭头可以通过解引用再取地址的方式进行实现。

        当然也可以通过使用对象+.的方式来进行访问。

4、iterator insert(iterator pos, const T& x)

		iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newNode = new Node(x);newNode->_next = cur;cur->_prev = newNode;newNode->_prev = prev;prev->_next = newNode;++_size;return newNode;}

         虽然链表的插入不像vector的插入会产生扩容问题而引发迭代器失效,这里返回新插入节点的迭代器主要是方面插入之后的链式操作。其次与STL保持一致。

5、iterator erase(iterator pos)

       删除当前迭代器位置,这里也不像vector,删除当前位置的数据并不影响后续节点的迭代器,但是当list删除当前节点时,会进行释放节点。那么当前节点的迭代器就会悬空,对悬空的迭代器进行操作就会引发错误,所以,在删除之后也会返回下一个节点的迭代器。

		iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}

6、void clear()

        内部调用erase函数对节点进行清除释放,但保留头节点。

		void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}

7、拷贝构造 

        这里要进行深拷贝,先为新对象创建一个头结点,再使用范围for拿出目标链表中的每一个数据,直接进行push_back()操作即可。

		list(const list<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto& e:lt){push_back(e);}}

8、赋值重载

        直接采用传值传参,传值传参调用拷贝构造,之后进行对象交换即可。

		void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> tmp){swap(tmp);return *this;}


http://www.ppmy.cn/server/165627.html

相关文章

YOLOv11-ultralytics-8.3.67部分代码阅读笔记-tasks.py

tasks.py ultralytics\nn\tasks.py 目录 tasks.py 1.所需的库和模块 2.class BaseModel(nn.Module): 3.class DetectionModel(BaseModel): 4.class OBBModel(DetectionModel): 5.class SegmentationModel(DetectionModel): 6.class PoseModel(DetectionModel): …

基于多重算法的医院增强型50G全光网络设计与实践:构建智慧医疗新基石(下)

四、关键算法在医院 50G 全光网络中的应用场景 4.1 智能流量调度算法 4.1.1 基于 DQN 的流量分类 深度 Q 网络&#xff08;DQN&#xff09;是一种将深度学习与 Q 学习相结合的算法&#xff0c;它在医院 50G 全光网络的流量分类中发挥着重要作用。其核心原理是通过构建深度神…

【LeetCode 刷题】贪心算法(2)-进阶

此博客为《代码随想录》二叉树章节的学习笔记&#xff0c;主要内容为贪心算法进阶的相关题目解析。 文章目录 135. 分发糖果406. 根据身高重建队列134. 加油站968. 监控二叉树 135. 分发糖果 题目链接 class Solution:def candy(self, ratings: List[int]) -> int:n len…

景联文科技:专业数据采集标注公司 ,助力企业提升算法精度!

随着人工智能技术加速落地&#xff0c;高质量数据已成为驱动AI模型训练与优化的核心资源。据统计&#xff0c;全球AI数据服务市场规模预计2025年突破200亿美元&#xff0c;其中智能家居、智慧交通、医疗健康等数据需求占比超60%。作为国内领先的AI数据服务商&#xff0c;景联文…

吴恩达深度学习——卷积神经网络实例分析

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 LeNet-5AlexNetVGG-16ResNets残差块 1*1卷积 LeNet-5 输入层&#xff1a;输入为一张尺寸是 32 32 1 32321 32321的图像&#xff0c;其中 32 32 3232 3232是图像的长和宽&…

面向智慧农业的物联网监测系统设计(论文+源码+实物)

1系统方案设计 根据系统功能的设计要求&#xff0c;展开面向智慧农业的物联网监测系统设计。如图2.1所示为系统总体设计框图。系统采用STM32单片机作为系统主控核心&#xff0c;利用YL-69土壤湿度传感器、光敏传感器实现农作物种植环境中土壤湿度、光照数据的采集&#xff0c;系…

基于PostGIS的省域空间相邻检索实践

目录 前言 一、相关空间检索函数 1、ST_touches函数 2、ST_Intersects函数 3、ST_Relate函数 4、区别于对比 二、空间相邻检索实践 1、省域表相关介绍 2、相关省域相邻查询 3、全国各省份邻居排名 三、总结 前言 在当今数字化时代&#xff0c;地理空间数据的高效管理…

自测|注意力机制的理解

自注意力机制 自注意力机制&#xff08;Self - Attention&#xff09;是Transformer架构中的核心组件&#xff0c;主要用于处理序列数据&#xff1a; 生成Q、K、V矩阵&#xff1a;对于输入序列&#xff08;假设长度为 n n n &#xff09;&#xff0c;首先通过三个不同的线性变…