【C++】STL之list模拟实现:关于链表容器的双向迭代器你知道多少?

【C++】STL之list模拟实现:关于链表容器的双向迭代器你知道多少?

前言:

前面的博客中我已经介绍了STL核心容器之一的list相关接口的使用,今天我们就从底层出发,来模拟实现一下list的那些核心接口函数。同时,也来感受一下list的双向迭代器到底与string和vector的随机迭代器有哪些区别?

list容器功能接口介绍:https://blog.ZEEKLOG.net/Miun123/article/details/151685386?spm=1001.2014.3001.5502

废话不多说,我们直接进入今天的正题👇️👇️👇️



list容器深度剖析及模拟实现

我们想要模拟实现list容器,那就要理解list容器的底层结构。前面的博客已经提到,其本质就是一个双向链表,所以,成员变量就应该包含一个记录头节点的指针,以及记录有效节点个数的变量。同时,为了list容器可以满足不同类型的数据,我们将所有的类实现为类模板。

1、定义节点结构

struct创建的类默认所有的成员但是公开的,而节点结构就需要公开被list访问。
template<class T> struct list_node { // 成员变量 T _data; list_node<T>* _next; list_node<T>* _prev; // 默认构造 list_node(const T& val = T()) :_data(val) , _next(nullptr) , _prev(nullptr) {} };

2、双向迭代器

我们先看一段 slt_list.h头文件 中实现list迭代器的源码:(注释是本人自己加的)

template<class T, class Ref, class Ptr> struct __list_iterator { typedef __list_iterator<T, T&, T*> iterator; // 普通迭代器 typedef __list_iterator<T, const T&, const T*> const_iterator; // const迭代器 typedef __list_iterator<T, Ref, Ptr> self; typedef bidirectional_iterator_tag iterator_category; typedef T value_type; // 数据类型 typedef Ptr pointer; // 指针类型 typedef Ref reference; // 引用类型 typedef __list_node<T>* link_type; // 节点类型 typedef size_t size_type; typedef ptrdiff_t difference_type; link_type node; // 成员变量 __list_iterator(link_type x) : node(x) {} // 拷贝构造 __list_iterator() {} // 默认构造 __list_iterator(const iterator& x) : node(x.node) {} // 拷贝构造 bool operator==(const self& x) const { return node == x.node; } // 重载== bool operator!=(const self& x) const { return node != x.node; } // 重载!= reference operator*() const { return (*node).data; } // 重载————解引用* #ifndef __SGI_STL_NO_ARROW_OPERATOR pointer operator->() const { return &(operator*()); } // 重载-> #endif /* __SGI_STL_NO_ARROW_OPERATOR */ self& operator++() { //重载前置++ node = (link_type)((*node).next); return *this; } self operator++(int) { // 重载后置++ self tmp = *this; ++*this; return tmp; } self& operator--() { // 重载前置-- node = (link_type)((*node).prev); return *this; } self operator--(int) { // 重载后置-- self tmp = *this; --*this; return tmp; } };

和前面string和vector的迭代器不同,list的迭代器不仅自己封装成了一个单独的类模板,而且这个类模板的模板参数有三个。这是为什么呢❓️

我们知道,对于const对象和非const对象,如果将模板参数只有一个<class T>,那么我们势必就要实现两个迭代器的类模板。而这两个类模板中,只有解引符号重载函数和箭头 ->重载函数由于返回值与其对应的类型有关(因为对于const对象,我们无法通过解引用和->改变const对象的值)而实现方式稍有不同;对于其他函数,在两个类模板中就重复了。所以,我们为了避免这种情况,就在类模板中引入了另外两个参数:Ref(T& / const T&——解引用操作符函数的返回值类型),     Ptr(T* / const T*—— ->重载函数的返回值类型)。

可以看到,在源码中typedef用得非常多,接下来我们就自己实现一个迭代器的类模板出来:其中++,--,==,!=,*,->这些操作符都是对节点的操作,所以,我们迭代器的类模板中应该有一个记录节点的成员变量_node。

template<class T, class Ref,class Ptr> struct list_iterator { typedef list_node<T> Node; typedef list_iterator<T, Ref, Ptr> Self; // Ref--T& / const T& ; Ptr--T* / const T* Node* _node; // 成员变量 list_iterator(Node* node) // 拷贝构造 :_node(node) {} // ... };
2.1、解引用
Ref& operator*()const { return _node->_data; }

2.2、->

Ptr operator->()const { return &(_node->_data); }
2.3、前置--  后置--
// 前置-- Self& operator--() { _node = _node->_prev; return *this; } // 后置-- Self operator--(int) { list_iterator<T> tmp(*this); _node = _node->_prev; return tmp; }
2.4、后置++  前置++
// 后置++ Self operator++(int) { Self tmp(*this); _node = _node->_next; return tmp; } // 前置++ Self& operator++() { _node = _node->_next; return *this; }
2.5、== 和 !=
bool operator==(const Self& s)const { return _node == s._node; } bool operator!=(const Self& s)const { return _node != s._node; }

3、list容器功能接口

template<class T> class list { typedef list_node<T> Node; public: typedef list_iterator<T, T&, T*> iterator; // 普通迭代器 typedef list_iterator<T, const T&, const T*> const_iterator;// const迭代器 // ... private: Node* _head; // 头节点 size_t _size;// 有效节点个数 };
3.1、链表初始化
3.1.1、构造空链表

即创建头节点(哨兵位),并初始化

void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0; }
3.1.2、默认构造
list(){ empty_init(); }
3.1.3、拷贝构造
list(const list<T>& lt) { // 由于拷贝无法完成头节点初始化,所以先初始化头节点 empty_init(); for (auto e : lt) push_back(e); }
3.1.4、初始化列表
list(initializer_list<int> il) { empty_init(); for (auto& e : il) push_back(e); }
3.1.5、赋值重载
void swap(list<T>& lt) { std::swap(_head, lt._head); std::swap(_size, lt._size); } list<T>& operator=(list<T> lt) { swap(lt); return *this; }
3.2、析构
~list() { clear(); // 清理所有节点 delete _head; // 释放头节点 _head = nullptr; }
3.3、迭代器

迭代器即可以理解为指针,我们用迭代器记录第一个有效节点和尾节点的位置。

iterator begin() { return _head->_next; } iterator end() { return _head; } const_iterator begin()const { return _head->_next; } const_iterator end()const { return _head; }
3.4、插入

先找到指定位置之后的节点:pos->next;然后。连接pos节点,新节点newnode与指定位置之后的节点pos->next。注意:连接顺序,防止改变pos->next的指向。

3.4.1、指定位置插入
iterator insert(iterator pos, const T& x) { Node* newnode = new Node(x); // 构造新节点 Node* pcur = pos._node; Node* prev = pos._node->_prev; // 连接:prev newnode pcur prev->_next = newnode; newnode->_prev = prev; newnode->_next = pcur; pcur->_prev = newnode; ++_size; // 有效节点个数加1 return pos; // 返回pos节点 }
3.4.2、头插
void push_front(const T& x) { insert(begin(), x); }
3.4.3、尾插
void push_back(const T& x) { insert(end(), x); }
3.5、删除节点

现在的指定位置节点之前的节点:pos->prev;指定位置之后的节点:pos->next。然后,连接pos->prev与pos->next。最后,释放指定位置节点。

3.5.1、删除指定位置节点
iterator erase(iterator pos) { assert(pos != end()); Node* prev = pos._node->_prev; Node* next = pos._node->_next; // 连接 prev next prev->_next = next; next->_prev = prev; // 释放 delete pos._node; pos._node = nullptr; --_size; return next; // 返回pos节点的下一个节点 }
3.5.2、头删
void pop_front() { erase(begin()); }
3.5.3、尾删
void pop_back() { erase(--end()); }
3.5.4、链表的清理
void clear() { auto it = begin(); while (it != end()) { it = erase(it); } }
3.6、获取节点个数
size_t size() const { return _size; }
3.8、判空
bool empty() const { return _size == 0; }

4、总结

那么,本期的分享就到此结束,如果大家觉得写的还不错的话,点个小爱心❤️支持一下吧😘😘😘,我们下期再见🤗🤗🤗

Read more

深度盘点:GitHub 上十大必装 Claude Skill,让你的 AI 助手效率提升 4 倍

深度盘点:GitHub 上十大必装 Claude Skill,让你的 AI 助手效率提升 4 倍

深度盘点:GitHub 上十大必装 Claude Skill,让你的 AI 助手效率提升 4 倍 Claude Code 已经很强大,但如果搭配这些精心设计的 Skills,它将变身超级生产力工具。本文为你深度解析 GitHub 上最受欢迎的 10 大 Claude Skills,帮助你找到最适合的配置方案。 引言:为什么 Claude Skills 如此重要? 在 2025-2026 年,Claude Code 生态经历了爆发式增长。Skills 系统的出现,让 Claude 从一个"对话助手"升级为"专业工具"。通过安装不同的 Skills,你可以:

By Ne0inhk
从安装到代码提交:Git 远程协作中 90% 的问题都能在这里找到答案

从安装到代码提交:Git 远程协作中 90% 的问题都能在这里找到答案

工欲善其事,必先利其器。 目录 * 安装 Git 的步骤: * 本地Git与远程仓库连接及操作全指南 * 一、本地仓库初始化与远程仓库连接 * 1. 初始化本地Git仓库 * 2. 关联远程仓库 * 1. 查看当前分支状态 * 2. 新建本地分支 * 方法1:基于当前分支创建新分支 * 方法2:创建并直接切换到新分支(推荐) * 方法3:基于远程分支创建本地分支 * 3. 切换到已有的本地分支 * 二、分支管理与远程分支同步 * 1. 查看远程分支 * 2. 拉取远程分支到本地 * 三、代码提交与推送到远程仓库 * 1. 常规提交流程 * 2. 简化推送命令 * 四、远程仓库信息查看与更新 * 1. 查看远程仓库详细信息 * 2. 同步远程仓库最新数据 * 五、常见问题解决与优化配置 * 1. 网络与连接问题修复 * 2. 推送大文件或提升传输稳定性

By Ne0inhk
2025电赛E题开源:二维云台激光打靶系统全解析(基于STM32F407+K230)

2025电赛E题开源:二维云台激光打靶系统全解析(基于STM32F407+K230)

2025电赛E题:二维云台激光打靶系统全解析——基于STM32F407的视觉伺服控制 本文详细介绍2025年全国大学生电子设计竞赛E题《二维云台激光打靶系统》的完整实现方案。项目基于STM32F407微控制器,结合视觉追踪、PID控制、步进电机驱动等技术,实现高精度的激光自动瞄准与发射功能。 🎯 项目背景与意义 在自动化控制领域,视觉伺服系统是实现高精度定位与追踪的关键技术。本次分享的项目,源自 2025 年全国大学生电子设计竞赛的赛题,题目要求设计一套二维云台系统,需具备自动识别目标、控制激光精准命中的功能。 该项目历经多重挑战,最终斩获了广东省赛区的省一等奖。由于我在此次比赛中主要负责二维云台激光打靶系统的设计,因此仅针对 25 年电赛 e 题的瞄准模块部分进行解说,自动循迹小车的内容会略过。 这个项目的成功落地,既为电子设计竞赛提供了一套完整的参考方案,也为嵌入式视觉伺服系统的教学与研究提供了宝贵的实践案例。 📊 系统总体设计 系统架构图 二维云台激光打靶系统 ├── 感知层(视觉模块) │ ├── 摄像头采集 │ └── 目标坐标提取 ├── 控制层(主控板

By Ne0inhk
OpenAI 开源模型 gpt-oss 本地部署详细教程

OpenAI 开源模型 gpt-oss 本地部署详细教程

OpenAI 最近发布了其首个开源的开放权重模型gpt-oss,这在AI圈引起了巨大的轰动。对于广大开发者和AI爱好者来说,这意味着我们终于可以在自己的机器上,完全本地化地运行和探索这款强大的模型了。 本教程将一步一步指导你如何在Windows和Linux系统上,借助极其便捷的本地大模型运行框架Ollama,轻松部署和使用 gpt-oss 模型。 一、准备工作:系统配置与性能预期 在开始之前,了解运行环境非常重要。本次部署将在我的个人电脑上进行,下面是推荐配置: * CPU: 现代多核 CPU,如 Intel Core i7 或 AMD Ryzen 7 系列 * 内存 (RAM): 32 GB 或更多 * 显卡 (GPU): 强烈推荐 NVIDIA GeForce RTX 4090 (24 GB 显存)。这是确保大型模型流畅运行与高效微调的理想选择。 * 操作系统: Linux 或 Windows

By Ne0inhk