【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

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk