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;影响所有…

wordpress外贸独立站常用询盘软件

LiveChat LiveChat是一家提供实时聊天软件的公司&#xff0c;帮助企业通过其平台与客户进行即时通讯&#xff0c;提高客户满意度和忠诚度。他们的产品允许企业在网站、应用程序或电子邮件等多个渠道与客户互动&#xff0c;从而提升客户体验并促进销售增长。 LiveChat的软件特…

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

DeepSeek 与 ChatGPT 对比分析

一、技术背景与研发团队 ChatGPT 由 OpenAI 开发&#xff0c;自 2015 年 OpenAI 成立以来&#xff0c;经过多年的技术积累和迭代&#xff0c;从 GPT-1 到 GPT-4o&#xff0c;每一次升级都带来了技术上的突破。OpenAI 拥有雄厚的技术实力和海量的数据、强大的算力支持&#xff…

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

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

DeepSeek R1技术报告关键解析(8/10):DeepSeek-R1 的“aha 时刻”,AI 自主学习的新突破

1. 什么是 AI 的“aha 时刻”&#xff1f; 在强化学习过程中&#xff0c;AI 的推理能力并不是线性增长的&#xff0c;而是会经历一些关键的“顿悟”时刻&#xff0c;研究人员将其称为“aha 时刻”。 这是 AI 在训练过程中突然学会了一种新的推理方式&#xff0c;或者能够主动…

python:递归函数与lambda函数

递归函数&#xff1a;1.函数内调用自己 2.有一个出口 1.递归 一.有出口时 def sum(num):if num1:return 1return numsum(num-1) asum(3) print(a) #num3 3sum(2) #num2 2sum(1) #num1是返回1 #即3sum(2&#xff09;即32sum(1)即321运行结果 6 二.无出口时 def sum(num)…

ABB 3BSE018741R30 带插头连接器的电缆

产品ID:3BSE018741R30 ABB型号名称:PFTL 101/201/PFCL 201 30米 目录描述:带插头连接器的电缆&#xff0c;30米 ABB型号名称:PFTL 101/201/PFCL 201 30米 核心信用:0.00 原产国:瑞典波兰 海关税则号:85389091 框架尺寸:备件 毛重:5公斤 媒体描述:带插头连接器的电缆 最小订购数…

SpringMVC请求

一、RequestMapping注解 RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系 RequestMapping注解可以作用在方法和类上 1. 作用在类上&#xff1a;第一级的访问目录 2. 作用在方法上&#xff1a;第二级的访问目录 3. 细节&#xff1a;路径可以不编写 / 表示应…

VUE的响应性调试:组件调试钩子、计算属性调试、侦听器调试【仅会在开发模式下工作】

文章目录 引言I 组件调试钩子调试事件对象的类型定义钩子II 计算属性调试例子回调函数说明III 侦听器调试引言 VUE的响应性调试的使用场景:确切地知道Vue 的响应性系统正在跟踪什么,或者是什么导致了组件重新渲染。 I 组件调试钩子 组件调试钩子仅会在开发模式下工作 调试…

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…

蓝桥杯试题:排序

一、问题描述 给定 nn 个正整数 a1,a2,…,ana1​,a2​,…,an​&#xff0c;你可以将它们任意排序。现要将这 nn 个数字连接成一排&#xff0c;即令相邻数字收尾相接&#xff0c;组成一个数。问&#xff0c;这个数最大可以是多少。 输入格式 第一行输入一个正整数 nn&#xff…

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;这其中重要的就是全面拥抱了响应式编程的设计思想和实…

C/C++编译器

C/C 代码是不可跨平台的&#xff0c;Windows 和 Unix-like 有着不同的 API&#xff0c;C/C 在不同平台有着不同编译器。 MSVC Windows 平台&#xff0c;MSVC 是 Visual Studio 中自带的 C/C 编译器。 GCC Unix-like 平台&#xff0c;GCC 原名 GNU C Compiler&#xff0c;后…

python gltf生成预览图

使用Python生成GLTF模型的预览图 随着3D技术的不断发展&#xff0c;GLTF&#xff08;GL Transmission Format&#xff09;逐渐成为了Web和移动应用程序中最流行的3D文件格式之一。GLTF文件不仅能以较小的体积存储复杂的3D模型&#xff0c;还支持动画、材质、光照和纹理等特性。…

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

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

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter3-语言基础

三、语言基础 ECMAScript 的语法很大程度上借鉴了 C 语言和其他类 C 语言&#xff0c;如 Java 和 Perl。ECMAScript 中一切都区分大小写。无论是变量、函数名还是操作符&#xff0c;都区分大小写。 所谓标识符&#xff0c;就是变量、函数、属性或函数参数的名称。标识符可以由…