C++ STL 完整入门笔记[6]:list 底层源码手写剖析:双向链表与迭代器设计全解
前言std::list是 STL 中典型双向循环链表容器和 vector 连续数组底层完全不同vector 随机访问 O (1)、中间插入删除 O (n)list 不支持随机访问但任意位置插入、删除仅 O (1)。本文结合手写简易版 list 源码拆解底层结构、迭代器区分、核心接口insert/erase/push_back、const 迭代器底层设计难点把手写 list 的所有核心知识点一次性梳理清楚。一、list 底层存储结构带哨兵头结点的双向循环链表1. 节点结构体_list_nodelist 每一个存储数据的节点都包含三部分数据、前驱指针、后继指针templateclass T struct _list_node { T _data; _list_node* _next; _list_node* _prev; };2. list 类本体哨兵头结点_headlist 内部只维护一个哨兵头结点_head链表形成双向循环_head-_next指向链表第一个有效节点begin()_head-_prev指向链表最后一个有效节点end()返回迭代器指向哨兵头结点本身哨兵节点优势链表为空 / 非空时insert、erase逻辑完全统一无需单独处理空链表边界。templateclass T class list { public: typedef _list_nodeT Node; private: Node* _head; // 哨兵头结点 };二、list 迭代器深度拆解普通迭代器 vs const 迭代器list 迭代器不是原生指针是封装链表节点指针的类因为链表节点不连续不能直接it跳转下一个元素。1. 迭代器模板设计核心难点迭代器模板接收两个模板参数T存储数据类型Ref引用类型普通迭代器传Tconst 迭代器传const Ttemplateclass T, class Ref struct _list_iterator { typedef _list_nodeT Node; typedef _list_iteratorT, Ref Self; Node* _node; // 构造绑定链表节点指针 _list_iterator(Node* node) :_node(node) {} // 解引用 *it Ref operator*() { return _node-_data; } // 箭头访问 it-xxx T* operator-() { return _node-_data; } // 前置 Self operator() { _node _node-_next; return *this; } // 前置-- Self operator--() { _node _node-_prev; return *this; } // 迭代器比较 bool operator!(const Self other) const { return _node ! other._node; } };2. 两种迭代器 typedef 区分在 list 类内部通过上面迭代器模板实例化出两种迭代器templateclass T class list { public: typedef _list_iteratorT, T iterator; typedef _list_iteratorT, const T const_iterator; // ... private: Node* _head; };关键区别普通迭代器 iteratorRefT*it返回数据引用可修改链表节点数据listint lt; listint::iterator it lt.begin(); *it 100; // 合法可修改const 迭代器 const_iteratorRefconst T*it返回 const 引用禁止修改节点数据void print(const listint lt) { listint::const_iterator it lt.begin(); // *it 100; 报错const迭代器不能修改数据 cout *it; }3. begin () /end () 接口实现// 普通迭代器begin iterator begin() { return iterator(_head-_next); } // const迭代器beginconst对象调用 const_iterator begin() const { return const_iterator(_head-_next); } // end永远返回哨兵头结点 iterator end() { return iterator(_head); } const_iterator end() const { return const_iterator(_head); }三、核心接口源码实现与图解1. 构造函数、析构函数构造初始化哨兵循环链表list() { _head new Node; _head-_next _head; _head-_prev _head; }析构循环销毁所有节点释放哨兵~list() { iterator it begin(); while(it ! end()) { it erase(it); // erase自动返回下一个有效迭代器 } delete _head; _head nullptr; }2. insert 任意位置插入O (1)逻辑图解pos 迭代器指向当前节点 cur新建 newnode 插入 cur 前面保存 cur 前驱 prev cur-_prevprev 后继指向 newnodenewnode 前驱指向 prev后继指向 curcur 前驱指向 newnodevoid insert(iterator pos, const T val) { Node* cur pos._node; Node* newnode new Node; newnode-_data val; Node* prev cur-_prev; prev-_next newnode; newnode-_prev prev; newnode-_next cur; cur-_prev newnode; }3. erase 删除指定迭代器位置O (1)逻辑图解删除 pos 指向 cur 节点连接前后节点返回下一个有效迭代器保存 cur 前驱 prev、后继 nextprev 后继改为 nextnext 前驱改为 prev释放 cur 节点内存返回迭代器绑定 next 节点iterator erase(iterator pos) { Node* cur pos._node; Node* prev cur-_prev; Node* next cur-_next; prev-_next next; next-_prev prev; delete cur; return iterator(next); }4. push_back /push_front 复用 insert底层直接调用 insert简化代码// 尾插在end()哨兵前插入 void push_back(const T x) { insert(end(), x); } // 头插在begin()第一个元素前插入 void push_front(const T x) { insert(begin(), x); }四、手写 list 核心易错点总结迭代器不能复用原生指针vector 迭代器本质是T*原生指针list 节点不连续必须封装节点指针实现迭代器。const 迭代器不能修改数据迭代器模板通过第二个模板参数区分引用类型const 迭代器解引用返回 const 引用拦截数据修改。哨兵头结点统一边界逻辑无论链表空还是有数据insert、erase 无需额外判空空链表begin()end()。erase 迭代器失效问题erase 传入的 pos 迭代器失效但返回值是下一个有效迭代器vector erase 会导致后续所有迭代器失效list 仅被删除迭代器失效。list 不支持随机访问没有重载[]运算符不能lt[0]访问元素遍历只能依靠迭代器循环。五、list vs vector 底层对比特性std::list双向链表std::vector连续数组底层存储离散节点双向循环链表连续堆内存数组随机访问 []不支持O (n) 遍历支持O (1)头部 / 中间插入删除O (1)仅修改指针O (n)需要挪动元素迭代器失效仅被 erase 的迭代器失效erase 后所有后续迭代器失效内存开销每个节点存 prev/next 双指针开销大仅存储数据内存紧凑六、完整简易版 list 全部源码#includeiostream using namespace std; templateclass T struct _list_node { T _data; _list_node* _next; _list_node* _prev; }; templateclass T, class Ref struct _list_iterator { typedef _list_nodeT Node; typedef _list_iteratorT, Ref Self; Node* _node; _list_iterator(Node* node) :_node(node) {} Ref operator*() { return _node-_data; } T* operator-() { return _node-_data; } Self operator() { _node _node-_next; return *this; } Self operator--() { _node _node-_prev; return *this; } bool operator!(const Self other) const { return _node ! other._node; } }; templateclass T class list { public: typedef _list_nodeT Node; typedef _list_iteratorT, T iterator; typedef _list_iteratorT, const T const_iterator; list() { _head new Node; _head-_next _head; _head-_prev _head; } ~list() { iterator it begin(); while(it ! end()) { it erase(it); } delete _head; _head nullptr; } iterator begin() { return iterator(_head-_next); } const_iterator begin() const { return const_iterator(_head-_next); } iterator end() { return iterator(_head); } const_iterator end() const { return const_iterator(_head); } void insert(iterator pos, const T val) { Node* cur pos._node; Node* newnode new Node; newnode-_data val; Node* prev cur-_prev; prev-_next newnode; newnode-_prev prev; newnode-_next cur; cur-_prev newnode; } iterator erase(iterator pos) { Node* cur pos._node; Node* prev cur-_prev; Node* next cur-_next; prev-_next next; next-_prev prev; delete cur; return iterator(next); } void push_back(const T x) { insert(end(), x); } void push_front(const T x) { insert(begin(), x); } }; // 测试打印函数 void print(const listint lt) { listint::const_iterator it lt.begin(); while(it ! lt.end()) { cout *it ; it; } cout endl; } int main() { listint lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_front(0); print(lt); listint::iterator it lt.begin(); it; lt.erase(it); print(lt); return 0; }结语手写 list 是吃透 STL 容器底层的经典案例重点掌握双向循环链表哨兵设计、模板迭代器区分普通 /const、insert/erase 指针修改逻辑三大核心。 对比 vector 底层实现后可以根据业务场景灵活选择容器频繁中间插入删除选 list随机访问、尾部扩容选 vector。

相关新闻

ThreadLocal 原理与实践:线程隔离、内存泄漏与使用边界

ThreadLocal 原理与实践:线程隔离、内存泄漏与使用边界

ThreadLocal 原理与实践:线程隔离、内存泄漏与使用边界 目录 线程私有变量Thread 的内部结构ThreadLocalMapset、get 和 remove 的过程内存泄漏与数据污染实战场景使用边界小结 多线程操作共享变量时,通常需要考虑加锁、原子操作和可见性问题。但有些…

2026/10/10 9:48:31 阅读更多 →
基于Arduino与PS2手柄的Makeblock搬运遥控车制作全攻略

基于Arduino与PS2手柄的Makeblock搬运遥控车制作全攻略

1. 项目概述:当经典手柄遇上开源硬件如果你手头正好有一套Makeblock的金属结构件,几个电机,还有一个在角落里吃灰多年的SONY PS2手柄,那么恭喜你,一个充满乐趣的周末项目正在向你招手。这个项目的核心,就是…

2026/9/25 10:45:30 阅读更多 →
RAG文档切片优化:解决AI检索中的上下文断裂问题

RAG文档切片优化:解决AI检索中的上下文断裂问题

1. RAG检索中的文档切片困境:当AI变成"近视眼"那天凌晨3点,我被报警短信惊醒——客户的知识问答系统突然开始胡言乱语。查看日志发现,当用户询问"我司2023年推出的新产品有哪些核心优势"时,系统竟回答"根…

2026/10/5 9:42:23 阅读更多 →

最新新闻

官媒集体下场做GEO优化: 工程师视角的三个落地层

官媒集体下场做GEO优化: 工程师视角的三个落地层

从央视315曝光GEO投毒,到总台发布GEO战略「四库三线」,8个月里官媒集体下场做GEO优化。技术视角看,这不是营销新闻,而是一次检索逻辑的换轨:大模型升级检索时天然向权威信源倾斜。结论先说:工程侧的应对&am…

2026/10/15 1:41:05 阅读更多 →
ML307N/Y/X三大型号差异解析与选型实战指南

ML307N/Y/X三大型号差异解析与选型实战指南

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

2026/10/15 1:41:05 阅读更多 →
表达式树破解C#泛型带参构造难题:高性能对象工厂实践

表达式树破解C#泛型带参构造难题:高性能对象工厂实践

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

2026/10/15 1:41:05 阅读更多 →
单机搭建Ozone 0.5.0-beta:从环境准备到S3网关读写验证

单机搭建Ozone 0.5.0-beta:从环境准备到S3网关读写验证

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

2026/10/15 1:41:05 阅读更多 →
YOLO目标检测全解析:从v1到v8的演进、实操与落地选型指南

YOLO目标检测全解析:从v1到v8的演进、实操与落地选型指南

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

2026/10/15 1:41:05 阅读更多 →
OpenFeign 实战指南:从 RestTemplate 到声明式微服务调用

OpenFeign 实战指南:从 RestTemplate 到声明式微服务调用

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

2026/10/15 1:40:05 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →