【C++】深入理解自定义 list 容器中的 list_iterator:迭代器实现详解

news2024/11/16 5:45:49

个人主页: 起名字真南的CSDN博客

个人专栏:

  • 【数据结构初阶】 📘 基础数据结构
  • 【C语言】 💻 C语言编程技巧
  • 【C++】 🚀 进阶C++
  • 【OJ题解】 📝 题解精讲

目录

  • 📌 引言
  • 📌 1. 为什么 `list` 容器需要 `list_iterator`
  • 📌 2. `list_iterator` 的设计与实现
    • ✨ 2.1 `list_iterator` 的基本结构
    • ✨ 2.2 重载 `*` 和 `->` 操作符
    • ✨ 2.3 重载 `++` 和 `--` 操作符
      • 🚀 前置 `++` 和后置 `++`
      • 🚀 前置 `--` 和后置 `--`
    • ✨ 2.4 重载比较运算符 `==` 和 `!=`
  • 📌 3. `list_iterator`、`list` 和 `list_node` 的关系
    • ✨ 3.1 `list_iterator` 与 `list_node`
    • ✨ 3.2 `list_iterator` 与 `list`
  • 📌 4. 使用 `list_iterator` 遍历链表
  • 📌 5. const 迭代器的实现
  • 📌 6. 迭代器失效问题
  • 📌 总结

📌 引言

在上一篇文章中,我们从零实现了一个 list 容器,包括节点结构、迭代器设计、增删查操作等。然而,对于一个成熟的容器来说,迭代器是不可或缺的部分,因为它提供了遍历和访问容器元素的标准接口。本篇文章将补充说明 list_iterator 的设计和实现,帮助大家深入理解迭代器的原理以及在 list 容器中的重要作用。


📌 1. 为什么 list 容器需要 list_iterator

list 是一种双向链表,节点之间的内存地址并不连续。为了支持容器的标准遍历接口,必须通过迭代器封装节点间的前后关系。list_iterator 实现了 ++--*-> 等操作符,使得我们可以在链表上使用 STL 的标准迭代器操作,并方便地对节点数据进行访问和修改。


📌 2. list_iterator 的设计与实现

✨ 2.1 list_iterator 的基本结构

list_iterator 是一个模板类,内部封装了指向链表节点的指针 _node。通过 _node,迭代器可以在节点间移动,并访问节点的数据。

template<class T, class Ref, class Ptr>
struct list_iterator {
    typedef list_node<T> Node;                 // 节点类型
    typedef list_iterator<T, Ref, Ptr> Self;   // 迭代器类型
    Node* _node;                               // 当前节点的指针

    // 构造函数,初始化为指定节点
    list_iterator(Node* node = nullptr) : _node(node) {}
};
  • 模板参数

    • T:节点中数据的类型。
    • Ref* 操作符的返回类型,通常为 T&const T&
    • Ptr-> 操作符的返回类型,通常为 T*const T*
  • 成员变量 _node:指向当前节点的指针,用于定位链表中的一个节点。


✨ 2.2 重载 *-> 操作符

为了让迭代器可以像指针一样访问节点数据,我们重载了 *-> 操作符。这两个操作符分别返回节点的数据值和数据地址。

Ref operator*() {
    return _node->_data;  // 返回节点的数据引用
}

Ptr operator->() {
    return &_node->_data; // 返回节点数据的地址
}
  • operator*:返回当前节点的数据引用,类型为 Ref,通常为 T&const T&
  • operator->:返回节点数据的地址,类型为 Ptr,通常为 T*const T*

这样一来,我们可以直接使用 *itit-> 来访问节点的数据,例如:

*it += 10;          // 修改当前节点的数据
cout << it->value;  // 访问节点数据成员

✨ 2.3 重载 ++-- 操作符

在链表中,前进和后退一个节点的操作不是简单的指针加减,而是通过 _next_prev 指针。因此,我们重载 ++-- 运算符,使迭代器能够在节点间移动。

🚀 前置 ++ 和后置 ++

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

Self operator++(int) {
    Self tmp(*this);        // 创建当前迭代器的临时副本
    _node = _node->_next;   // 将 _node 指向下一个节点
    return tmp;             // 返回旧的迭代器
}
  • 前置 ++:将 _node 指针移动到下一个节点,返回修改后的迭代器自身。
  • 后置 ++:先保存当前迭代器的副本,再将 _node 指向下一个节点,最后返回未修改的副本。

🚀 前置 -- 和后置 --

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

Self operator--(int) {
    Self tmp(*this);         // 创建当前迭代器的临时副本
    _node = _node->_prev;    // 将 _node 指向前一个节点
    return tmp;              // 返回旧的迭代器
}
  • 前置 --:将 _node 指向前一个节点,返回修改后的迭代器自身。
  • 后置 --:先保存当前迭代器的副本,再将 _node 移动到前一个节点,最后返回旧的副本。

通过这两个运算符的重载,我们可以在链表上实现正向和反向遍历,符合 STL 迭代器的标准行为。


✨ 2.4 重载比较运算符 ==!=

为了判断两个迭代器是否指向相同的节点,我们重载了 ==!= 运算符。当两个迭代器的 _node 指针相等时,它们表示相同的位置。

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

bool operator!=(const Self& other) const {
    return _node != other._node;
}
  • operator==:比较两个迭代器的 _node 指针是否相等。
  • operator!=:判断两个迭代器是否不相等,通常用于循环结束条件。

📌 3. list_iteratorlistlist_node 的关系

✨ 3.1 list_iteratorlist_node

  • list_iterator 依赖 list_nodelist_iterator 通过 _node 指向 list_node,实现对链表节点的访问和操作。++-- 操作通过 _node->_next_node->_prev 实现节点的前进和后退。
  • 数据访问依赖 list_node*-> 操作符的重载直接访问 _node->_data,即 list_node 中的数据域,使得迭代器可以返回节点中的数据。
  • 双向链接关系list_node_prev_next 指针实现了双向链接,使得 list_iterator 可以方便地前后移动,而不需要依赖连续的内存地址。

✨ 3.2 list_iteratorlist

  • list 通过迭代器提供访问接口listbegin()end() 返回 list_iterator,分别指向链表的第一个节点和尾后节点。外部代码可以使用迭代器在 list 容器上进行遍历和访问。
  • 迭代器操作封装链表结构listinserterase 等操作在返回迭代器时,允许用户在链表上插入和删除节点,保持接口一致性。
  • 迭代器失效问题:在 listerase 等操作中,若迭代器失效,需要返回新的有效迭代器,这保证了链表操作的安全性。

📌 4. 使用 list_iterator 遍历链表

借助 list_iterator,我们可以像遍历数组那样遍历链表。例如,下面的代码展示了如何使用迭代器遍历自定义 list 容器。

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);

list<int>::iterator it = lt.begin();
while (it != lt.end()) {
    cout << *it << " ";  // 输出当前节点的数据
    ++it;                 // 前进到下一个节点
}

在上述代码中,begin() 返回链表首节点的迭代器,end() 返回链表尾后位置的迭代器。通过 ++it 操作符,我们可以依次访问链表的每一个节点。


📌 5. const 迭代器的实现

list_iterator 中使用了模板参数 RefPtr,可以根据需求指定 T&const T&,从而支持常量迭代器 const_iterator。当使用 const_iterator 时,数据不可被修改。

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);

list<int>::const_iterator it = lt.begin();
while (it != lt.end()) {
    cout

 << *it << " ";  // 仅能读取数据,不能修改
    ++it;
}

const_iterator 中,RefPtr 分别设置为 const T&const T*,确保 *it 不能用于修改节点的数据。


📌 6. 迭代器失效问题

在链表中删除或插入元素时,可能导致迭代器失效。例如,在 erase 删除某个节点后,指向该节点的迭代器将变为无效,继续使用会引发错误。因此,在 erase 函数中返回下一个有效迭代器,以确保遍历过程中不会访问失效的节点。

auto it = lt.begin();
while (it != lt.end()) {
    if (*it % 2 == 0) {
        it = lt.erase(it);  // 删除节点,返回下一个有效迭代器
    } else {
        ++it;
    }
}

📌 总结

通过 list_iterator,我们实现了自定义 list 容器的标准遍历方式。总结 list_iterator 的关键点如下:

  1. 封装节点指针list_iterator 通过持有 list_node 指针 _node 来访问和移动链表节点。
  2. 重载操作符
    • *-> 用于访问节点数据。
    • ++-- 用于迭代器的前进和后退。
    • ==!= 用于迭代器的比较。
  3. list_iteratorlistlist_node 的关系
    • list_iterator 依赖 list_node 实现节点移动和数据访问。
    • list 通过 list_iterator 提供统一的接口,使链表可以通过迭代器进行遍历、插入和删除操作。

通过 list_iterator,自定义的 list 容器具备了与 STL 容器一致的遍历能力,使链表在不连续内存结构中也可以支持标准的迭代器操作。


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

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

相关文章

MuMu模拟器安卓12安装Xposed 框架

MuMu模拟器安卓12安装Xposed 框架 当开启代理后,客户端会对代理服务器证书与自身内置证书展开检测,只要检测出两者存在不一致的情况,客户端就会拒绝连接。正是这个原因,才致使我们既没有网络,又抓不到数据包。 解决方式: 通过xposed框架和trustmealready禁掉app里面校验…

MongoDB分布式集群搭建----副本集----PSS/PSA

MongoDB分布式集群 Replication 复制、Replica Set 复制集/副本集 概念 一、 副本集的相关概念 1.概念 “ A replica set is a group of mongod instances that maintain the same data set. ” 一组MongoDB服务器&#xff08;多个mongod实例&#xff09;&#xff08;有不…

Java篇String类的常见方法

目录 一. String类的概念 1.1 String类的特性 二. 字符串的构造方式 三. 常用方法 3.1 字符串查找 3.2 字符串转换 3.3 字符串比较 3.3.1 equals( )方法 3.3.2 compare To( )方法 3.3.3 compare ToIgnoreCase( )方法 3.4 字符串替换 3.4.1 replace( )方法 3.4.2 r…

「QT」文件类 之 QDataStream 数据流类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定制…

MySQL45讲 第二十三讲 是怎么保证数据不丢的?

文章目录 MySQL45讲 第二十三讲 是怎么保证数据不丢的&#xff1f;一、binlog 写入机制&#xff08;一&#xff09;事务执行与 binlog cache&#xff08;二&#xff09;事务提交与 binlog 文件写入 二、redo log 写入机制&#xff08;一&#xff09;事务执行与 redo log buffer…

pgaudit插件-pgslq

使用pgaudit插件 一.介绍 postgresql可以通过log_statementall 提供日志审计&#xff0c;但是无法详细的提供日志信息&#xff0c;使用ogaudit能够提供详细的会话和对象审计日志&#xff0c;是PG的一个扩展插件 注意&#xff1a;pgAudit可能会生成大量日志。请谨慎确定要在您…

系统掌握大语言模型提示词 - 从理论到实践

以下是我目前的一些主要个人标签&#xff1a; 6 年多头部大厂软件开发经验&#xff1b;1 年多 AI 业务应用经验&#xff0c;拥有丰富的业务提示词调优经验和模型微调经验。信仰 AGI&#xff0c;已经将 AI 通过自定义 Chatbot /搭建 Agent 融合到我的工作流中。头部大厂技术大学…

Vue 项目打包后环境变量丢失问题(清除缓存),区分.env和.env.*文件

Vue 项目打包后环境变量丢失问题&#xff08;清除缓存&#xff09;&#xff0c;区分.env和.env.*文件 问题背景 今天在导报项目的时候遇到一个问题问题&#xff1a;在开发环境中一切正常&#xff0c;但在打包后的生产环境中&#xff0c;某些环境变量&#xff08;如 VUE_APP_B…

群控系统服务端开发模式-应用开发-前端菜单功能开发

今天优先开发菜单及角色&#xff0c;明天将开发岗位配置、级别配置等功能。具体看下图 而前端的路由不需要手动添加&#xff0c;是依据数据库里面存储的路径。 一、添加视图 在根目录下src文件夹下views文件夹下permission文件夹下menu文件夹下&#xff0c;新建index.vue&…

数据结构Python版

2.3.3 双链表 双链表和链表一样&#xff0c;只不过每个节点有两个链接——一个指向后一个节点&#xff0c;一个指向前一个节点。此外&#xff0c;除了第一个节点&#xff0c;双链表还需要记录最后一个节点。 每个结点为DLinkNode类对象&#xff0c;包括存储元素的列表data、…

【HarmonyOS学习日志(8)】UIAbility,HAP,AbilityStage组件及其生命周期

基本概念 UIAbility组件是一种包含UI的应用组件&#xff0c;主要用于和用户交互。 在项目创建时&#xff0c;系统默认生成的EntryAbility类继承了UIAbility类。 ExtensionAbility组件&#xff1a;是基于特定场景&#xff08;例如服务卡片、输入法等&#xff09;提供的应用组件…

【Linux】多线程(中)

目录 一、线程互斥 1.1 互斥概念 1.2 互斥量mutex 1.3 互斥量相关API &#xff08;1&#xff09;初始化互斥量 &#xff08;2&#xff09;销毁互斥量 &#xff08;3&#xff09;互斥量加锁和解锁 1.4 互斥量原理 1.5 重入和线程安全 二、死锁 2.1 概念 2.2 造成死锁…

【数字图像处理+MATLAB】基于 Sobel 算子计算图像梯度并进行边缘增强:使用 imgradientxy 函数

引言 在图像处理中&#xff0c;边缘通常是图像中像素强度变化最大的地方&#xff0c;这种变化可以通过计算图像的梯度来量化。梯度是一个向量&#xff0c;它的方向指向像素强度增加最快的方向&#xff0c;它的大小&#xff08;或者说幅度&#xff09;表示像素强度增加的速度。…

Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解

title: Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解 date: 2024/11/14 updated: 2024/11/14 author: cmdragon excerpt: schema:beforeWrite 钩子是 Vite 提供的一个功能强大的生命周期钩子,允许开发者在 JSON Schema 被写入之前执行自定义操作。利用这个钩子,您可以…

k8s服务内容滚动升级以及常用命令介绍

查看K8S集群所有的节点信息 kubectl get nodes 删除K8S集群中某个特定节点 kubectl delete nodes/10.0.0.123 获取K8S集群命名空间 kubectl get namespace 获取K8S所有命名空间的那些部署 kubectl get deployment --all-namespaces 创建命名空间 web界面上看到的效果,但是…

MinIo在Ubantu和Java中的整合

1.MinIo在Ubantu中的部署 首先准备好一台已经安装好Ubantu系统的服务器 MinIO是一个开源的对象存储服务器&#xff0c;兼容Amazon S3&#xff0c;性能卓越&#xff0c;适合存储非结构化数据&#xff0c;例如照片、视频、日志文件、备份和容器镜像等。 1&#xff1a;更新系统…

设计模式-参考的雷丰阳老师直播课

一般开发中使用的模式为模版模式策略模式组合&#xff0c;模版用来定义骨架&#xff0c;策略用来实现细节。 模版模式 策略模式 与模版模式特别像&#xff0c;模版模式会定义好步骤定义好框架&#xff0c;策略模式定义小细节 入口类 使用模版模式策略模式开发支付 以上使用…

【LeetCode】【算法】53. 最大子数组和

LeetCode 53. 最大子数组和 题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。子数组是数组中的一个连续部分。 思路 思路&#xff1a;动态规划秒了 具体递推式如…

供应SW6301V单C口多协议升降压移动电源IC

1. 概述 SW6301V 是一款高集成度的单 C 口多协议升降压移动电源 SOC。集成双向升降压控制器&#xff0c;支持 2~6 节 电池串联&#xff0c;提供 100W 功 率 输 入 输 出 &#xff1b; 支 持 C 口 快 充 输入输出 &#xff1b; 支 持UFCS/PPS/PD/SVOOC/VOOC/SCP/FCP/QC/AFC/BC…

C++常用的新特性-->day06

时间间隔duration duration表示一段时间间隔&#xff0c;用来记录时间长度&#xff0c;可以表示几秒、几分钟、几个小时的时间间隔。duration的原型如下 // 定义于头文件 <chrono> template<class Rep,class Period std::ratio<1> > class duration;Rep&…