【C++ 手写 STL 容器】手撕 list 双向循环链表|迭代器模板复用 + 反向迭代器适配器 庖丁解牛
1. 整体架构预览STLstd::list底层是带头结点双向循环链表。 难点不在于链表增删节点而在迭代器封装原生指针Node*不符合迭代器规范需要封装迭代器类。为了避免普通迭代器、const 迭代器写两份几乎完全一样的代码我们利用模板参数Ref、Ptr实现代码复用。反向迭代器采用适配器模式不重新实现一套反向遍历逻辑包装正向迭代器复用已有所有迭代器运算符。组件清单list_nodeT链表节点数据 前驱指针 后继指针_list_iteratorT,Ref,Ptr正向迭代器模板一份代码生成普通迭代器 /const 迭代器li::listT容器本体封装构造、析构、插入删除等接口reverseiteratorIterator,Ref,Ptr迭代器适配器实现反向遍历2. list.h#pragma once #includeiostream #includelist #includevector #includereverse_iterator.h using namespace std; namespace li {};2.1 链表节点list_nodetemplateclass T struct list_node { T _data; list_nodeT* _next; list_nodeT* _prev; // 节点构造函数 list_node(const T val T()) :_data(val) , _next(nullptr) , _prev(nullptr) {} };解析有哨兵位的双向链表节点每个节点保存数据、前驱、后继指针。构造函数给默认参数T()头结点就是调用这个构造头结点不存储有效数据仅占位统一头尾操作逻辑。2.2 正向迭代器 _list_iteratortemplateclass T, class Ref, class Ptr struct _list_iterator { typedef list_nodeT Node; typedef _list_iteratorT, Ref, Ptr Self; Node* _node; // 迭代器构造函数 _list_iterator(Node* node) :_node(node) {}解析迭代器本质就是封装节点指针。 模板参数说明T存储的数据类型Ref引用类型普通迭代器传Tconst 迭代器传const TPtr指针类型普通迭代器传T*const 迭代器传const T*// 解引用重载 *it Ref operator*() { return _node-_data; }解析返回引用普通迭代器可读可写const 迭代器只读。// -重载 it- Ptr operator-() { return _node-_data; }解析返回数据指针支持it-member访问。// 前置 it Self operator() { _node _node-_next; return *this; }解析迭代器移动到下一个节点返回自身引用支持连续 it。// 后置 it Self operator(int) { Self tmp(*this); _node _node-_next; return tmp; }解析int 只是占位参数用来区分前置 / 后置。先保存旧迭代器节点后移返回旧状态。// 前置-- --it Self operator--() { _node _node-_prev; return *this; }解析迭代器向前移动到上一个节点。// 后置-- it-- Self operator--(int) { Self tmp(*this); _node _node-_prev; return tmp; }// ! 判断 bool operator!(const Self it)const { return _node ! it._node; } // 判断 bool operator(const Self it)const { return _node it._node; } };判断的是两个地址是否相等2.3 list 容器类逐个函数拆解templateclass T class list { typedef list_nodeT Node; public: // 类型重定义实例化两种正向迭代器 typedef _list_iteratorT, T, T* iterator; typedef _list_iteratorT, const T, const T* const_iterator; // 反向迭代器类型 typedef reverseiteratoriterator, T, T* reverse_iterator; typedef reverseiteratorconst_iterator, const T, const T* const_reverse_iterator;empty_init 初始化函数void empty_init() { _head new Node; _head-_next _head; _head-_prev _head; _size 0; }作用创建头结点构建空双向循环链表size 置 0。默认构造list() { empty_init(); }解析调用初始化函数创建空链表。析构函数~list() { clear(); delete _head; _head nullptr; }解析先释放全部有效节点再释放头结点防止内存泄漏。拷贝构造函数list(const listT l) { empty_init(); for (const auto e : l) { push_back(e); } }解析现代写法先初始化空链表遍历原 list 逐个尾插。initializer_list 构造list(initializer_listT l) { empty_init(); for (const auto e : l) { push_back(e); } }解析支持li::listint lt{1,2,3,4}这种花括号初始化。swap 交换函数void swap(listT lt) { std::swap(_head, lt._head); std::swap(_size, lt._size); }解析O (1) 时间复杂度仅仅交换头结点指针和 size不拷贝节点。赋值重载现代写法list operator(list lt) { swap(lt); return *this; }解析参数传值自动调用拷贝构造生成临时对象swap 交换资源函数结束临时对象销毁带走旧内存。天然处理自赋值。clear 清空所有有效节点void clear() { iterator it begin(); while (it ! end()) { it erase(it); } }❌错误写法erase(it); it;erase 之后 pos 迭代器直接失效it 是未定义行为。✅正确it erase(it)erase 返回下一个有效迭代器。rbegin /rend 反向迭代器接口reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rbegin()const { return const_reverse_iterator(end()); } const_reverse_iterator rend()const { return const_reverse_iterator(begin()); }解析反向迭代器适配器rbegin 包装正向 endrend 包装正向 begin。begin /end 正向迭代器接口iterator begin() { return iterator(_head-_next); } iterator end() { return iterator(_head); } const_iterator begin()const { return const_iterator(_head-_next); } const_iterator end()const { return const_iterator(_head); }解析begin ()指向第一个有效节点end ()指向头结点尾后迭代器不能解引用push_back 尾插void push_back(const T x) { insert(end(), x); }解析复用 insert在 end 前面插入节点就是尾插。push_front 头插void push_front(const T x) { insert(begin(), x); }pop_back 尾删void pop_back() { erase(--end()); }解析--end()拿到最后一个有效节点迭代器直接 erase 删除。pop_front 头删void pop_front() { erase(begin()); }insert 插入函数iterator insert(iterator pos, T val) { Node* newnode new Node(val); Node* pcur pos._node; Node* prev pcur-_prev; newnode-_next pcur; pcur-_prev newnode; newnode-_prev prev; prev-_next newnode; _size; return iterator(newnode); }解析STL 规定 insert 是在 pos 迭代器前面插入节点。 四步指针链接insert不会让任何迭代器失效。返回新节点迭代器。erase 删除函数iterator erase(iterator pos) { Node* cur pos._node; Node* prev cur-_prev; Node* next cur-_next; prev-_next next; next-_prev prev; delete cur; --_size; return iterator(next); }解析删除 pos 指向节点pos 迭代器失效其他迭代器保持有效。返回下一个节点迭代器。size 获取元素个数size_t size()const { return _size; } private: Node* _head; size_t _size 0; }; } //namespace li3. reverse_iterator.h 反向迭代器适配器逐函数拆分设计思路#pragma once templateclass Iterator, class Ref, class Ptr struct reverseiterator { typedef reverseiteratorIterator, Ref, Ptr Self; Iterator _it; // 构造函数 reverseiterator(Iterator it) :_it(it) {}// 解引用 Ref operator*() { Iterator tmp _it; --tmp; return *tmp; }解析底层正向迭代器是尾后迭代器不能直接解引用拷贝临时迭代器向前挪一位取值。// -重载 Ptr operator-() { return (operator*()); }// 反向迭代器 Self operator() { --_it; return *this; }解析反向迭代器 等价底层正向迭代器 --。// 反向迭代器 -- Self operator--() { _it; return *this; }解析反向迭代器 -- 等价底层正向迭代器 。bool operator!(const Self s) { return _it ! s._it; } bool operator(const Self s) { return _it s._it; } };核心原理反向迭代器不维护节点指针仅仅包装正向迭代器。 ❗面试考点为什么拷贝 tmp不能直接--_it如果直接修改_it会破坏反向迭代器底层保存的迭代器状态遍历逻辑错乱。只能临时拷贝一份再自减。4. main.cpp 测试代码#includelist.h int main() { li::listint lt{ 1,2,3,4,5 }; //正向遍历 li::listint::iterator it lt.begin(); while (it ! lt.end()) { cout *it ; it; } cout endl; //反向遍历 li::listint::reverse_iterator rit lt.rbegin(); while (rit ! lt.rend()) { cout *rit ; rit; } cout endl; lt.push_back(6); lt.push_front(0); lt.pop_back(); lt.pop_front(); for (auto x : lt) { cout x ; } return 0; }5、list 优缺点总结优点任意位置 O (1) 插入删除找到位置之后insert 不会迭代器失效缺点不支持随机访问不能l[0]不能 it 5查找是 O (N)节点碎片化内存不连续缓存命中率低注意迭代器不是原生指针是对 Node * 的包装类。模板三个参数T,Ref,Ptr的作用用来复用一份迭代器代码同时实现iterator和const_iteratoriteratorRefTPtrT*const_iteratorRefconst TPtrconst T*operator-返回元素地址支持it-member语法有特殊简化规则迭代器 / !比较内部节点指针不是比较元素it1 it2判断两个迭代器指向同一个节点不是元素相等。 /-- 修改迭代器内部_node前置返回引用后置返回值临时拷贝比较函数必须加constconst 迭代器调用不会报错。❗list 迭代器是双向迭代器只支持 、--不支持 it n /it - n不能随机访问总结成员变量Node* _head;哨兵头结点默认构造创建哨兵 headhead-_next head; head-_prev head;自循环析构先 clear再 delete _head不能漏删哨兵节点clear遍历删除所有有效节点不要删哨兵 head拷贝构造深拷贝新建哨兵循环把原链表每个元素 push_back 到新 list赋值重载现代写法swap简单安全注意自赋值问题

相关新闻

指针妙用:从数组到字符串的灵活操作

指针妙用:从数组到字符串的灵活操作

指针如果要传的类型和指针的类型不匹配,则强转例:int a 10;char *p (char *)&a;指针——整型一维数组int a[10] {1,2,3,4};int *p &a[0]; a; 指针——字符型一维数组--主要用来存放字符串 局部作用域的一个数组 char s[] "hello";…

2026/10/10 20:32:54 阅读更多 →
学Simulink——无人机机翼颤振边界条件预测

学Simulink——无人机机翼颤振边界条件预测

目录 手把手教你学Simulink——无人机机翼颤振边界条件预测 一、目标与边界 1.1 输出指标(颤振边界) 1.2 建模对象选择 二、理论方程 2.1 通用模态方程 2.2 典型二元翼段(Theodorsen) 2.3 求解方法对照 三、Simulink 建模…

2026/10/11 14:25:38 阅读更多 →
Univer Workspace CLI完全指南:10个命令让Claude等AI智能体自动化办公任务

Univer Workspace CLI完全指南:10个命令让Claude等AI智能体自动化办公任务

Univer Workspace CLI完全指南:10个命令让Claude等AI智能体自动化办公任务 【免费下载链接】univer-workspace An open-source Office workspace where people and AI agents create, collaborate, and review together. 项目地址: https://gitcode.com/gh_mirror…

2026/10/10 14:38:30 阅读更多 →

最新新闻

零成本AI副业:一条链接撬动第一桶金的完整实操

零成本AI副业:一条链接撬动第一桶金的完整实操

近两年"AI 副业"这四个字已经快被说烂了,各种动辄几千上万的训练营、私教课满天飞。但以我实操过十几个项目、在内容社区持续输出半年多的经验来看,AI 副业真正落地最稳的第一步,恰恰不是什么复杂系统,而是"一条链…

2026/10/12 5:55:29 阅读更多 →
MCLDNN:面向真实射频信号的多通道深度调制识别网络

MCLDNN:面向真实射频信号的多通道深度调制识别网络

简介:本资源是面向深度学习与无线通信领域研究者的自动调制识别(AMR)实战项目,聚焦于高维调制信号(如16-QAM、64-QAM)的精准分类问题,适用于具备Python编程基础及PyTorch/TensorFlow经验的研究生…

2026/10/12 5:55:29 阅读更多 →
从Excel到Spring Boot:员工考勤系统实战与踩坑复盘

从Excel到Spring Boot:员工考勤系统实战与踩坑复盘

从一张Excel考勤表说起。前年某部门的同事抱着厚厚一叠打印出来的考勤明细找我,说每个月手工核对迟到早退、请假调休要折腾三天,问能不能搞个系统自动算。我接手之后才发现,springboot员工考勤系统这种项目看着遍地都是教程,真要做…

2026/10/12 5:55:29 阅读更多 →
Android Studio国内镜像配置全攻略:一步解决SDK、Gradle下载慢

Android Studio国内镜像配置全攻略:一步解决SDK、Gradle下载慢

很多朋友学 Android 开发,第一关不是 Kotlin 语法,而是把 Android Studio 环境装起来。我这些年帮人排查过太多次环境问题,发现大多数人并不是操作步骤错了,而是下载和同步依赖时走的默认源在国外,从国内访问又慢又容易…

2026/10/12 5:55:29 阅读更多 →
哪些办公 AI 能从需求分析到最终交付完整完成任务

哪些办公 AI 能从需求分析到最终交付完整完成任务

很多企业在选择办公AI时容易陷入误区,只看单次问答的生成质量,忽略了从需求拆解、信息调研、多步骤执行到产出可交付成果的全链路能力。普通对话类AI只能完成单点内容生成,而具备智能体能力的办公AI,才可以承接完整业务任务&#…

2026/10/12 5:55:29 阅读更多 →
Doris JSON数组解析优化:从正则切分到JSONB+EXPLODE的实践

Doris JSON数组解析优化:从正则切分到JSONB+EXPLODE的实践

1. 这个版本优化到底解决了什么问题1.1 日志场景里的 JSON 数组字段做数仓的人大概率都有过这种经历:上游埋点日志为了省事,把一个事件的所有上下文全塞进一个大 JSON 字段里,其中一定会有个数组字段,比如用户浏览商品列表、曝光位…

2026/10/12 5:54:29 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →