探索 List 的奥秘:自己动手写一个 STL List✨

📖引言

大家好!今天我们要一起来揭开 C++ 中 list 容器的神秘面纱——不是直接用 STL,而是亲手实现一个简化版的 list!🎉

你是不是曾经好奇过:

  • list 是怎么做到高效插入和删除的?🔍

  • 迭代器到底是怎么工作的?🔄

  • 为什么 list 是双向循环链表?🔗

在这篇博客中,我们将从零开始,一步步实现一个名为 amber::list 的模板类,包含:

  • ✅ 双向链表结构 list_node

  • ✅ 迭代器 __list_iterator(支持 ++--*->

  • ✅ 常用接口:push_backpush_frontinserteraseclear...

  • ✅ 拷贝构造、赋值重载、初始化列表构造

  • ✅ 异常安全的资源管理 🛡️

我们还会深入探讨:

  • 🧠 迭代器的设计哲学

  • 🔄 双向链表的插入与删除逻辑

  • 💡 模板编程中的技巧与陷阱

不管你是刚学完数据结构,还是想深入理解 STL 的实现,这篇博客都会让你收获满满!🌟

接下来,就让我们一起进入 list 的世界吧!👇


在 list 的模拟实现中,我们一共会用到三个类,分别是 list_node,__list_iterator 和 list。我们需要多加关注的是如何利用c++的特性去模拟实现STL中的list(例如一个模板完成两种迭代器的实现)。

list<T> │├── 包含多个 list_node<T> 节点│└── 提供 iterator 和 const_iterator│└── 由 __list_iterator<T, Ref, Ptr> 实现│└── 内部持有 list_node<T>* 用于遍历

1. list_node<T>:链表节点类

template<class T>
struct list_node
{T _data;list_node<T>* _next;list_node<T>* _prev;explicit list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}
};

list_node<T>是链表节点类,用于list类的数据存储。

这个类一共有三个成员对象,存储数据的_data和用于指向前后节点的_prev和_next指针。

list_node构造函数初始化了一个阶段存放了数据 x,这里的参数接口设计采用了c++的匿名对象参数缺省值的语法,然后赋值给const修饰的T类型引用的x形参,缺省值用匿名对象有效的防止了创建节点时未传参的情况。

如果没传参在会创建一个T类型的对象并且调用对应的默认构造,可以在不传参构建一个节点(在用list创建的链表对象的哨兵位节点_head就采用了这一特性,哨兵位节点只用于找链表的头与尾,不存储有效数据)。

我们通过初始化列表,在将x赋值给_data后把_next和_prev指针都初始化为nullptr空指针。

然后我们explicit关键字修饰构造函数防止了隐式类型转换,在编译时能够有效的发现代码编写错误。

2. __list_iterator<T, Ref, Ptr>:迭代器类

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):_node(node){}//成员数据访问运算符Ref operator*(){return _node->_data;//返回当前节点的数据类型对象}//自定义类型成员数据访问运算符Ptr operator->(){return &_node->_data;}//前置++Self& operator++(){_node = _node->_next;//迭代器只想下一个节点return *this;//返回当前迭代器对象}//后置++Self operator++(int){Node* temp = _node;//创建一个临时对象保存当前迭代器指向_node = _node->_next;//迭代器指向下一个节点return temp;//返回临时对象}//前置--Self& operator--(){_node = _node->_prev;//迭代器指向当前节点的前一个节点return *this;//返回当前对象}//后置--Self operator--(int){Node* temp = _node;//创建一个临时对象保存当前迭代器指向_node = _node->_prev;//迭代器指向当前节点的前一个节点return temp;//返回临时对象}//不等于比较运算符bool operator!=(const Self& it) const{return _node != it._node;//两个迭代器指向节点不相等返回true}//等于比较运算符bool operator==(const Self& it) const{return _node == it._node;//两个迭代器指向节点相等返回true}
};

迭代器类采用了三个模版参数 数据类型T数据对象引用 Ref 控制访问权限参数 Ptr

  • T:元素类型。

  • Ref:引用类型(T& 或 const T&)。

  • Ptr:指针类型(T* 或 const T*)。

该类有一个成员对象为_node,并且把list_node<T>类型和__list_iterator<T, Ref, Ptr>类型进行了typedef为Node和Self便于读写以及实现不同版本的迭代器iterator和常量迭代器const_iterator,由于两种迭代器的实现仅仅只有类型的不同,所以我们通过一个迭代器模板就有效地减少了代码的冗余,符合STL一贯的书写习惯。

由于list链表在物理空间上的不连续性,无法通过简单的typedef指针类型来进行迭代器模拟。

基于此原因,我们需要单独把模拟实现的list的迭代器封装到一个类里面,并且自主实现前置后置版本的++和--,以及比较运算符,以便于模拟迭代器的行为有助于list链表对象的遍历。

->操作符

基于->操作符的特殊性,这里我们需要单独讲解一下->操作符:

//自定义类型成员数据访问运算符Ptr operator->(){return &_node->_data;}

对于有多个成员的内置结构类型的指针,我们可以通过一次->访问到其对应的成员,例如:

typedef struct student
{int score;int grade;
}student;student s1 = {99,1};
student* ps1 = &s1;//内置类型箭头访问操作
ps1->grade

而对于list的模板数据类型T也有可能是有多个成员的结构体类型或者类类型,我们就需要重载出对应的->访问操作符,但这里要特殊强调的是编译的底层理解。

void list_test_5()
{student s1 = { 100,1 };student* ps1 = &s1;std::cout << ps1->score << ps1->grade << std::endl;amber::list<student> slt;slt.push_back({ 99,4 });slt.push_back({ 98,5 });slt.push_back({ 97,6 });slt.push_back({ 96,7 });amber::list<student>::iterator sit = slt.begin();while (sit != slt.end()){std::cout << "{" << sit->grade << "," << (*sit).grade << "}" << " ";sit++;}std::cout << std::endl;
}

上面这段代码实现了一个自定义结构体类型的list对象的遍历和成员变量的访问,其中 sit->

grade看似是调用了一次->操作符,但实际上从编译器的角度来看是两次,先调用了一次迭代器的重载,然后调用了内置类型的->操作符

sit->grade│├── 第1次->:调用 sit.operator->()│     ↓│    返回 student* (指向真实数据)│└── 第2次->:对返回的指针使用 ->↓访问真实的 grade 成员

3. list<T>:链表容器类

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;//常量迭代器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);//返回哨兵位节点}//空链表初始化void empty_init(){_head = new Node;//new一个节点但不初始化赋值给哨兵位节点_head->_next = _head;_head->_prev = _head;//哨兵位的前后指针指向自己}//默认构造函数list(){empty_init();//调用空链表初始化}//拷贝构造函数list(const list<T>& lt){empty_init();//调用空链表初始化for (auto e : lt){push_back(e);//遍历lt对象并逐个尾插到当前链表}}///初始化链表构造list(std::initializer_list<T> il){empty_init();//调用空链表初始化for (const auto e : il){push_back(e);//遍历初始化链表的所有对象并逐个尾插到当前链表}}//成员交换函数void swap(list<T>& lt){std::swap(_head, lt._head);//调用std标准库交换函数,交换哨兵位节点std::swap(_size, lt._size);//调用std标准库交换函数,交换_size}//赋值运算符重载list<T>& operator=(list<T> lt){swap(lt);//创建一个形参lt并与当前对象交换return (*this);//返回当前对象}//析构函数~list(){clear();//清空链表delete _head;//回收哨兵位头节点资源_head = nullptr;//置空}//链表清空void clear(){iterator it = begin();while (it != end()){it = erase(it);//遍历链表并将成员逐个erase掉}}//尾插void push_back(const T& x){insert(end(), x);//复用指定位置插入}//头插void push_front(const T& x){insert(begin(), x);//复用指定位置插入}//尾删void pop_back(){erase(_head->_prev);//复用指定位置删除}//头删void pop_front(){erase(_head->_next);//复用指定位置删除}//指定位置插入iterator insert(iterator pos, const T& val){Node* cur = pos._node;//保存当前指定位置Node* newnode = new Node(val);//new一个新节点出来并用val初始化Node* prev = cur->_prev;//保存当前位置的前一个节点newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;_size++;//更新_sizereturn newnode;//返回新节点}//指定位置删除iterator erase(iterator pos){if (_size == 0 || pos._node == _head){return end();//如果链表为空或者删除头节点时返回end()}Node* cur = pos._node;//保存当前指定位置Node* next = cur->_next;Node* prev = cur->_prev;if (prev != nullptr && next != nullptr)//确保前后指针的有效性{prev->_next = next;next->_prev = prev;//链接删除位置的前后节点}delete cur;//删除当前位置的节点_size--;//更新_sizereturn iterator(next);}//返回容量大小size_t size() const{return _size;}//全局友元交换函数friend void swap(list<T>& lhs, list<T>& rhs);private:Node* _head;//哨兵位节点size_t _size = 0;//节点数量
};// 在类外部定义友元函数模板
template<class T>
void swap(list<T>& lhs, list<T>& rhs) {lhs.swap(rhs);
}

迭代器区间

在一般的迭代器区间函数里,begin()指向的容器的第一个元素,end()指向的容器的最后一个元素的下一个位置,但是在list链表里,由于其首尾相接的特性,最后一个元素的下一个位置是哨兵位头节点_head

通过这个实现项目,我们深入理解了:

  1. 数据结构:双向链表的实现原理和优势

  2. C++模板:模板编程的强大能力和灵活性和其他语言泛型的区别

  3. 迭代器设计:STL迭代器接口的设计哲学

  4. 内存管理:RAII原则和异常安全编程

  5. 软件工程:接口设计、代码复用、可维护性

🚀 结束语

实现一个完整的list容器不仅仅是一个编程练习,更是对C++核心概念的深度探索。从这个项目中,我们:

"不仅学会了如何写代码,更学会了如何设计代码"

💪 掌握的技能:

  • 模板元编程的艺术

  • 迭代器设计的精髓

  • 内存管理的最佳实践

  • 异常安全的编程思维

  • STL兼容的接口设计

✨ 最后的思考

C++的魅力在于它提供了从底层内存管理到高级抽象的全方位控制能力。通过亲手实现标准库组件,我们不仅加深了对语言特性的理解,更培养了系统级的编程思维。

记住:好的代码不是写出来的,是设计出来的。

希望这个list实现之旅对你有所启发,继续在C++的世界里探索前行!🎉

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

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

相关文章

mysql占用高内存排查与解决

mysql占用高内存排查-- 查看当前全局内存使用情况&#xff08;需要启用 performance_schema&#xff09; SELECT * FROM sys.memory_global_total; -- 查看总内存使用 SELECT * FROM sys.memory_global_by_current_bytes LIMIT 10; -- 按模块分类查看内存使用排行memory/perfor…

构建真正自动化知识工作的AI代理

引言&#xff1a;新一代生产力范式的黎明 自动化知识工作的人工智能代理&#xff08;AI Agent&#xff09;&#xff0c;或称“智能体”&#xff0c;正迅速从理论构想演变为重塑各行各业生产力的核心引擎。这些AI代理被定义为能够感知环境、进行自主决策、动态规划、调用工具并持…

青少年机器人技术(四级)等级考试试卷-实操题(2021年12月)

更多内容和历年真题请查看网站&#xff1a;【试卷中心 -----> 电子学会 ----> 机器人技术 ----> 四级】 网站链接 青少年软件编程历年真题模拟题实时更新 青少年机器人技术&#xff08;四级&#xff09;等级考试试卷-实操题&#xff08;2021年12月&#xff09; …

最新短网址源码,防封。支持直连、跳转。 会员无广

最新短网址源码&#xff0c;防封。支持直连、跳转。 会员无广告1.可将长网址自动缩短为短网址&#xff0c;方便记忆和使用。2.短网址默认为临时有效&#xff0c;可付费升级为永久有效&#xff0c;接入支付后可自动完成&#xff0c;无需人工操作。3.系统支持设置图片/文字/跳转页…

缓存-变更事件捕捉、更新策略、本地缓存和热key问题

缓存-基础知识 熟悉计算机基础的同学们都知道&#xff0c;服务的存储大多是多层级的&#xff0c;呈现金字塔类型。通常来说本机存储比通过网络通信的外部存储更快&#xff08;现在也不一定了&#xff0c;因为网络传输速度很快&#xff0c;至少可以比一些过时的本地存储设备速度…

报表工具DevExpress .NET Reports v25.1新版本亮点:AI驱动的扩展

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 DevExpress Reporting控件日前正式发布了v25.1…

kubernetes中pod的管理及优化

目录 2 资源管理方式 2.1 命令式对象管理 2.2 资源类型 2.2.1 常用的资源类型 2.2.2 kubectl常见命令操作 2.3 基本命令示例 2.4 运行和调试命令示例 2.5 高级命令示例 3 pod简介 3.1 创建自主式pod&#xff08;生产环境不推荐&#xff09; 3.1.1 优缺点 3.1.2 创建…

解释一下,Linux,shell,Vmware,Ubuntu,以及Linux命令和shell命令的区别

Linux 操作系统概述Linux 是一种开源的类 Unix 操作系统内核&#xff0c;由 Linus Torvalds 于 1991 年首次发布。作为现代计算的基础设施之一&#xff0c;它具有以下核心特征&#xff1a;多用户多任务特性允许多个用户同时操作系统资源&#xff0c;而模块化设计使其能够适应从…

Windows 系统中,添加打印机主要有以下几种方式

在 Windows 系统中,添加打印机主要有以下几种方式,我将从最简单到最复杂为您详细介绍。 方法一:自动安装(推荐首选) 这是 Windows 10 和 Windows 11 中最简单、最现代的方法。系统会自动搜索网络(包括无线和有线网络)上可用的打印机并安装驱动程序。 操作步骤: 进入…

Mixture of Experts Guided by Gaussian Splatters Matters

Mixture of Experts Guided by Gaussian Splatters Matters: A new Approach to Weakly-Supervised Video Anomaly Detection ICCV2025 https://arxiv.org/pdf/2508.06318 https://github.com/snehashismajhi/GS-MoEAbstract 视频异常检测&#xff08;VAD&#xff09;是一项具有…

SeaTunnel Databend Sink Connector CDC 功能实现详解

Databend 是一个面向分析型工作负载优化的 OLAP 数据库&#xff0c;采用列式存储架构。在处理 CDC&#xff08;Change Data Capture&#xff0c;变更数据捕获&#xff09;场景时&#xff0c;如果直接执行单条的 UPDATE 和 DELETE 操作&#xff0c;会严重影响性能&#xff0c;无…

算法230. 二叉搜索树中第 K 小的元素

题目&#xff1a;给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。示例 1&#xff1a;输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff1a;1 示例 2&#xff1…

Seaborn数据可视化实战:Seaborn多变量图表绘制高级教程

Seaborn多变量图表实战&#xff1a;从数据到洞察 学习目标 本课程将带领学员深入了解Seaborn库中用于绘制多变量图表的高级功能&#xff0c;包括联合图&#xff08;Joint Plot&#xff09;、对角线图&#xff08;Pair Plot&#xff09;等。通过本课程的学习&#xff0c;学员将能…

【数智化人物展】首衡科技CTO李蒙:算法会过时,数据会贬值,只有系统智能才具未来性

李蒙本文由首衡科技CTO李蒙投递并参与由数智猿数据猿上海大数据联盟共同推出的《2025中国数智化转型升级先锋人物》榜单/奖项评选。大数据产业创新服务媒体——聚焦数据 改变商业“算法会过时&#xff0c;数据会贬值。”当我第一次在内部战略会上抛出这句话时&#xff0c;现场…

word——将其中一页变成横向

在word中如何将其中一页变成横向&#xff1f; 在需要横向的这一页和上一页插入分节符&#xff08;连续&#xff09; 1.点击布局→分隔符→分节符&#xff08;连续&#xff09; 2.在所需要横向页将纸张方向改为横向即可。

使用WORD实现论文格式的样式化制作【标题样式、自动序列、页号(分节)、自动目录(修改字体类型)】

背景 每家院校对论文的格式都有一系列的特定要求&#xff0c;相应的会有一份格式标准的说明文档&#xff0c;该说明文档中会罗列对文档各个项的格式标准要求&#xff08;例如&#xff1a;题目、1级标题、2级标题、页号、每个级别的字体字号&#xff0c;行距&#xff0c;段前段…

分享一个免费开源的网站跟踪分析工具Open-Web-Analytics(和GoogleAnalytics一样)

做独立网站的福音&#xff0c;这个是免费开源的&#xff0c;可增改性强。 开源地址&#xff1a;https://github.com/Open-Web-Analytics/Open-Web-Analytics 下载源码包 接着下载PHP工具&#xff1a;我用XP小皮 phpstudy_pro 地址&#xff1a;phpStudy - Windows 一键部署 …

Maxscript如何清理3dMax场景?

在3ds Max的创作过程中,随着项目的推进,场景往往会积累许多冗余元素,如孤立帮助对象、隐藏对象以及空层等,它们不仅让场景显得杂乱无章,还会占用资源、降低视口性能,影响工作效率。别担心,在本教程中,我们将为大家带来实用妙招——通过简单的Maxscript脚本片段,快速清…

JavaScript 性能优化实战:从分析到落地的全指南

一、引言&#xff1a;为什么 JS 性能优化至关重要&#xff1f;用户体验的直接影响&#xff1a;加载慢、交互卡顿如何流失用户&#xff08;引用 Google 研究&#xff1a;页面加载延迟 1 秒&#xff0c;转化率下降 7%&#xff09;业务价值关联&#xff1a;性能优化对 SEO、留存率…

线性回归学习笔记

一、线性回归简介1. 核心定义线性回归是一种通过属性的线性组合进行预测的线性模型&#xff0c;核心目标是找到一条直线&#xff08;二维&#xff09;、一个平面&#xff08;三维&#xff09;或更高维的超平面&#xff0c;使模型的预测值与真实值之间的误差最小化。2. 适用场景…