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

        接前面的手撕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.mzph.cn/news/894717.shtml

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

相关文章

如何打开vscode系统用户全局配置的settings.json

&#x1f4cc; settings.json 的作用 settings.json 是 Visual Studio Code&#xff08;VS Code&#xff09; 的用户配置文件&#xff0c;它存储了 编辑器的个性化设置&#xff0c;包括界面布局、代码格式化、扩展插件、快捷键等&#xff0c;是用户全局配置&#xff08;影响所有…

STM32 ADC模数转换器

ADC简介 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 12位逐次逼近型ADC&#xff0c;1us转换时间 输入电压范围&#xff1a;0~3.3V&#xff0…

(2025,LLM,下一 token 预测,扩散微调,L2D,推理增强,可扩展计算)从大语言模型到扩散微调

Large Language Models to Diffusion Finetuning 目录 1. 概述 2. 研究背景 3. 方法 3.1 用于 LM 微调的高斯扩散 3.2 架构 4. 主要实验结果 5. 结论 1. 概述 本文提出了一种新的微调方法——LM to Diffusion (L2D)&#xff0c;旨在赋予预训练的大语言模型&#xff08;…

学习threejs,pvr格式图片文件贴图

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️PVR贴图1.2 ☘️THREE.Mesh…

tkvue 入门,像写html一样写tkinter

介绍 没有官网&#xff0c;只有例子 安装 像写vue 一样写tkinter 代码 pip install tkvue作者博客 修改样式 import tkvue import tkinter.ttk as ttktkvue.configure_tk(theme"clam")class RootDialog(tkvue.Component):template """ <Top…

Java—不可变集合

不可变集合&#xff1a;不可以被修改的集合 创建不可变集合的应用场景 如果某个数据不能被修改&#xff0c;把它防御性地拷贝到不可变集合中是个很好的实践。当集合对象被不可信的库调用时&#xff0c;不可变形式是安全的。 简单理解&#xff1a;不想让别人修改集合中的内容…

每日Attention学习18——Grouped Attention Gate

模块出处 [ICLR 25 Submission] [link] UltraLightUNet: Rethinking U-shaped Network with Multi-kernel Lightweight Convolutions for Medical Image Segmentation 模块名称 Grouped Attention Gate (GAG) 模块作用 轻量特征融合 模块结构 模块特点 特征融合前使用Group…

响应式编程_04Spring 5 中的响应式编程技术栈_WebFlux 和 Spring Data Reactive

文章目录 概述响应式Web框架Spring WebFlux响应式数据访问Spring Data Reactive 概述 https://spring.io/reactive 2017 年&#xff0c;Spring 发布了新版本 Spring 5&#xff0c; Spring 5 引入了很多核心功能&#xff0c;这其中重要的就是全面拥抱了响应式编程的设计思想和实…

html中的表格属性以及合并操作

表格用table定义&#xff0c;标签标题用caption标签定义&#xff1b;用tr定义表格的若干行&#xff1b;用td定义若干个单元格&#xff1b;&#xff08;当单元格是表头时&#xff0c;用th标签定义&#xff09;&#xff08;th标签会略粗于td标签&#xff09; table的整体外观取决…

基于Springboot+vue的租车网站系统

基于SpringbootVue的租车网站系统是一个现代化的在线租车平台&#xff0c;它结合了Springboot的后端开发能力和Vue的前端交互优势&#xff0c;为用户和汽车租赁公司提供了一个高效、便捷、易用的租车体验和管理工具。以下是对该系统的详细介绍&#xff1a; 一、系统架构 后…

蓝桥杯之c++入门(二)【输入输出(上)】

目录 前言1&#xff0e;getchar和 putchar1.1 getchar()1.2 putchar() 2&#xff0e;scanf和 printf2.1 printf2.1.1基本用法2.1.2占位符2.1.3格式化输出2.1.3.1 限定宽度2.1.3.2 限定小数位数 2.2 scanf2.2.1基本用法2.2.2 占位符2.2.3 scanf的返回值 2.3练习练习1&#xff1a…

Docker数据卷管理及优化

一、基础概念 1.docker数据卷是一个可供容器使用的特殊目录&#xff0c;它绕过了容器的文件系统&#xff0c;直接将数据存在宿主机上。 2.docker数据卷的作用&#xff1a; 数据持久化&#xff1a;即使容器被删除或重建数据卷中的数据仍然存在 数据共享&#xff1a;多个容器可以…

java:mysql切换达梦数据库(五分钟适配完成)

背景 因为项目需要国产数据库的支持&#xff0c;选择了达梦数据库&#xff0c;由于我们之前使用的是MySQL今天我们就来说一说&#xff0c;如何快速的切换到达梦数据库&#xff0c;原本这一章我打算写VIP章节的后续想想&#xff0c;就纯分享。毕竟是国产数据库迁移数据库 这里…

在游戏本(6G显存)上本地部署Deepseek,运行一个14B大语言模型,并使用API访问

在游戏本6G显存上本地部署Deepseek&#xff0c;运行一个14B大语言模型&#xff0c;并使用API访问 环境说明环境准备下载lmstudio运行lmstudio 下载模型从huggingface.co下载模型 配置模型加载模型测试模型API启动API服务代码测试 deepseek在大语言模型上的进步确实不错&#xf…

[leetcode]两数之和等于target

源代码 #include <iostream> #include <list> #include <iterator> // for std::prev using namespace std; int main() { int target 9; list<int> l{ 2, 3, 4, 6, 8 }; l.sort(); // 确保列表是排序的&#xff0c;因为双指针法要求输入是…

C# OpenCV机器视觉:学生注意力监测

小王是一位充满活力的年轻教师&#xff0c;刚接手了一个新班级。他满心欢喜地准备在课堂上大显身手&#xff0c;把自己的知识毫无保留地传授给学生。可没上几节课&#xff0c;他就发现了一个让人头疼的问题&#xff1a;课堂上总有那么几个学生注意力不集中&#xff0c;要么偷偷…

【嵌入式】C语言多文件编程与内联函数

文章目录 0 前言1 从C语言编译说起2 重复定义错误&#xff08;ODR violation&#xff09;和条件编译3 内联函数inline和static inline4 总结 0 前言 最近在研究ARM内核代码时&#xff0c;看到core_cm3.h中有大量的内联函数&#xff0c;为此查阅了很多资料&#xff0c;也和朋友讨…

10分钟本地部署Deepseek-R1

10分钟本地部署DeepSeek-R1 什么是DeepSeek-R1快速本地部署DeepSeek-R1Ollama下载Ollama安装检查是否安装成功 安装DeepSeek-R1模型模型使用测试 什么是DeepSeek-R1 DeepSeek-R1是中国的深度求索&#xff08;DeepSeek&#xff09;公司开发的智能助手。其具有极佳的语义理解和生…

Office / WPS 公式、Mathtype 公式输入花体字、空心字

注&#xff1a;引文主要看注意事项。 1、Office / WPS 公式中字体转换 花体字 字体选择 “Eulid Math One” 空心字 字体选择 “Eulid Math Two” 2、Mathtype 公式输入花体字、空心字 2.1 直接输入 花体字 在 mathtype 中直接输入 \mathcal{L} L \Large \mathcal{L} L…

【C++】STL——vector底层实现

目录 &#x1f495; 1.vector三个核心 &#x1f495;2.begin函数&#xff0c;end函数的实现&#xff08;简单略讲&#xff09; &#x1f495;3.size函数&#xff0c;capacity函数的实现 &#xff08;简单略讲&#xff09; &#x1f495;4.reserve函数实现 &#xff08;细节…