【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:初步了解 list
🌹🌹期待您的关注 🌹🌹



❀STL之list
- 📒1. list的基本结构
- 📕2. list的模拟实现
- 🌈构造函数
- 🌞析构函数
- 🌙拷贝构造函数
- ⭐赋值运算符重载
- 📚3. list的迭代器
- 🍂迭代器的基本结构
- 🍁迭代器的运算符重载
- 🌸list的迭代器
- 📙4. list的const迭代器
- 🎩方法一
- 🎈方法二
- 📜5. 统一的方式访问STL容器中的元素
- 📔6. list与vector的对比
- 📖7. 总结补充
- 💧补充:insert和erase的模拟实现
- 🔥总结
在软件开发中,数据结构和算法的选择与实现是每一个开发者都必须面对的问题。标准模板库(STL)为我们提供了一系列高效且通用的数据结构和算法模板,极大地简化了C++编程中的许多常见任务。然而,了解这些数据结构和算法背后的实现原理,不仅有助于我们更深入地理解STL,还能提升我们的编程能力和解决问题的能力。
前言: 在STL中,list是一种双向链表,它支持在序列的任何位置进行快速插入和删除操作。与此同时,迭代器是STL中非常重要的一个概念,它使得我们能够以统一的方式遍历和访问STL容器中的元素。在深入了解STL的过程中,模拟实现list和迭代器无疑是一个极有价值的学习过程。
本节我们将从基本的链表结构开始,逐步构建出完整的list类,并实现相应的迭代器类。
📒1. list的基本结构

list是一个个带头双向循环链表,这意味着每个元素(通常称为节点)都有两个指针:一个指向前一个元素,另一个指向后一个元素,因此我们需要单独再定义一个类来表示节点结构,每个节点再串联起来构成list
节点定义(示例):
template<class T>
struct list_node
{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x),_next(nullptr),_prev(nullptr){}
};
而在构建list时,我们成员变量只需要一个头节点。
list定义(示例):
template<class T>
struct list
{typedef list_node<T> Node;
public:// 构造函数等可能的其他成员函数...
private:Node* _head;
};
📕2. list的模拟实现
注意:关于
erase和insert这两个函数的模拟我们依然作为补充放在末尾
🌈构造函数
在拥有一个list我们只需要将它的头节点初始化一下
list构造(示例):
void empty_init()
{_head = new Node;_head->_prev = _head;_head->_next = _head;
}// 无参构造
list()
{empty_init();
}
🌞析构函数
关于析构函数,我们需要的是将所有节点一 一释放就ok啦!
在模拟析构函数之前,不得不先介绍一下clear这个函数,因为clear可以删除出头节点以外的所有节点,我们可以利用这一点帮助我们优化析构函数
list析构(示例):
void clear()
{// 依次清除节点itetator it = begin(); // 稍后会提到迭代器的模拟while(it != end()){it = erase(it);}
}~list()
{clear(); // 删除出头节点以外的所有节点delete _head; // 单独删除一下头节点_head = nullptr;
}
🌙拷贝构造函数
在学习list时,我们发现list不会因为空间不够而需要扩容,因此在使用模拟list时,不用考虑是否会发生浅拷贝
list拷贝构造函数(示例):
//list(const list<T>& lt)
list(list<T>& lt) // 还未实现const迭代器,先使用常规的
{empty_init();for (auto e : lt){push_back(e); // push_back的实现其实是复用insert,文末有补充}
}
⭐赋值运算符重载
这里我们以让后传统写法和现代写法两种方法
list赋值运算符重载(示例):
// 传统写法
list<T>& operator=(const list<T>& lt)
{clear(); // 先将原来的list清空for (auto e : lt){push_back(e);}return *this;
}// 现代写法
void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}
在介绍完list基本的结构后,让我们来看看今天的重点:迭代器
📚3. list的迭代器
在我们模拟实现string,vector时,我们认为迭代器就是一个原生指针,但是在list中迭代器底层不是简单的指针,因此我们要独立定义一个新的类
🍂迭代器的基本结构
迭代器定义(示例):
template<class T>
struct __list_iterator
{typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node;// 构造函数__list_iterator(Node* node):_node(node){}
};
我们将迭代器单独写作一个类,能解决更多的问题,以及避免其他麻烦
🍁迭代器的运算符重载
因为这些函数和前面差不太多,我们简单看看代码,带过了
代码(示例):
self& operator++() // 前置++
{_node = _node->_next;return *this;
}self& operator--() // 前置--
{_node = _node->_prev;return *this;
}self operator++(int) // 后置++
{self tmp(*this);_node = _node->_next;return &tmp;
}self operator--(int) // 后置--
{self tmp(*this);_node = _node->_prev;return &tmp;
}bool operator!=(const self& tmp)
{return _node != tmp._node;
}bool operator-=(const self& tmp)
{return _node -= tmp._node;
}
而今天着重要强调以下两个运算符重载,因为const和非const下这两个是有区别的:
//可读写
T& operator*()
{return _node->_data;
}
//可读写
T* operator->()
{return &_node->_data;
}
// it.operator->()-> 编译器帮我们省略了一个箭头-> it->
在定义完迭代器类之后,我们可以实现begin()和end()来实现list的范围for
🌸list的迭代器
迭代器代码(示例):
template<class T>
struct list
{typedef list_node<T> Node;
public:typedef __list_iterator<T> iterator;iterator begin(){//return iterator(_head->_next); // 匿名对象return _head->next;}iterator end(){//return iterator(_head); // 匿名对象return _head;}
private:Node* _head;
};
当然我们这里还没有实现
const迭代器很多需要调用const对象的函数还无法使用,那么接下来让我们来模拟实现const迭代器,见证新的神奇
📙4. list的const迭代器
关于这个list的const迭代器其实有两种写法,常规的写法就是在定义一个新的
const迭代器的类,虽然这样可以解决问题,但是会造成代码的冗余,让操作繁琐。而另一种方法就是在原有的迭代器类上进行修改,让它能具有两个迭代器都能使用的特点
🎩方法一
const迭代器实现(示例):
template<class T>
struct __list_const_iterator
{typedef list_node<T> Node;typedef __list_const_iterator<T> self;Node* _node;// 构造函数__list_const_iterator(Node* node):_node(node){}//可读不可写const T& operator*(){return _node->_data;}//可读不可写const T* operator->(){return &_node->_data;}// 可能的其他成员函数...
};
🎈方法二
如果我们将这两个差异的内容单独表示出来归于模板中,因为在const与非const之间,无非就是T&,T*上能否读写的区别,不影响其他的函数实现,因此我们可以在模板上加上两个参数
| 模板参数 | 实例化类型 |
|---|---|
| Ref | T&,(const 变量时) const T& |
| Ptr | T*,(const 变量时) const T* |
const迭代器实现(示例):
// 用一个模板来解决 const与非const
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;}// 可能的其他成员函数...
};
template<class T>
struct list
{typedef list_node<T> Node;
public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_const_iterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->next;}iterator end(){return _head;}const_iterator begin() const{return _head->next;}const_iterator end() const{return _head;}
private:Node* _head;
};
关于list的模拟实现我们就讲到这里,让我看看如何以统一的方式遍历和访问STL容器中的元素
📜5. 统一的方式访问STL容器中的元素
在完成对list的模拟实现后,我们试着用来遍历和访问list中的元素
代码实现(示例):
void print_list(const list<int>& lt)
{// list<T>没有实例化话,就并不能去遍历寻找// 编译器不知道 list<T>::const_iterator 是内嵌类型,还是静态成员变量list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}
}
void test_list()
{list<int> lt;lt.push_back(1);lt.push_back(2);print_list(lt);cout << endl;
}
编译器不知道 list::const_iterator 是内嵌类型,还是静态成员变量,但是如果实例化成int后,有需要一个成员是string的列表这时我们有犯难了,这时我们就要用到typename,typename 就是告诉编译器,这是一个类型,等list实例化之后再去取
代码实现(示例):
template<typename T>
void print_list(const list<T>& lt)
{......// typename 就是告诉编译器,这是一个类型,等list实例化之后再去取typename list<T>::const_iterator it = lt.begin();......
}
但是更离谱的来了,这时又有人要求我们打印vector的值,容器都换了我们该怎么办呢?这时模板的作用又双体现出来了,这也体现了模板的本质,让我们能省的活交给编译器完成
代码实现(示例):
// 这里直接搞了一个Container来适配容器
template<typename Container>
void print_container(const Container& con)
{typename Container::const_iterator it = con.begin();while (it != con.end()){cout << *it << " ";++it;}
}
它使得我们能够以统一的方式遍历和访问STL容器中的元素
📔6. list与vector的对比
我们可以发现list与之前学的竟然有那么多的差异,我们结合上节学的vector来分析一下它们的差异:vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同
| vector | list | |
|---|---|---|
| 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
| 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容, | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
| 空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭 代 器 | 插入删除时触发条件会导致迭代器失效 | 删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
📖7. 总结补充
💧补充:insert和erase的模拟实现
代码实现(示例):
// insert会返回插入位置的一个迭代器
iterator insert(iterator pos, const T& x)
{Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;cur->_prev = newnode;newnode->_next = cur;return iterator(newnode); // 匿名对象
}
// erase会返回删除位置的next节点的迭代器
iterator erase(iterator pos)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next->_prev;next->_prev = prev->_next;return iterator(next); // 匿名对象
}
// erase和insert的复用
void push_back(const T& x) // 尾插
{insert(end(), x);
}
void push_front(const T& x) // 头插
{insert(begin(), x);
}
void pop_back() // 尾删
{erase(end());
}
void pop_front() // 头删
{erase(begin());
}
🔥总结
通过本次对STL中list和迭代器模拟实现的探索,我们深入了解了双向链表的基本结构、操作原理以及迭代器在遍历和访问链表元素中的重要作用。模拟实现的过程不仅让我们对STL中的list容器有了更深刻的理解,也锻炼了我们的编程能力和解决问题的能力
- 在模拟实现的过程中,我们学习了如何设计并实现一个双向链表结构,包括节点的定义、链表的插入、删除和遍历等操作。同时,我们也掌握了迭代器的基本概念和实现方法,理解了如何通过迭代器来统一访问和遍历不同的容器类型。
- 模拟实现STL中的list和迭代器是一个既有趣又富有挑战性的过程。它让我们更加深入地理解了数据结构和算法的基本原理,也为我们日后在实际项目中高效应用STL容器打下了坚实的基础。
最后,感谢大家的耐心阅读和学习。希望本次介绍能够为大家在STL学习和编程实践中提供一些帮助和启示。在未来的学习和工作中,让我们继续深入探索STL的奥秘,不断提升自己的编程能力和解决问题的能力

谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关文章:
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C “ 登神长阶 ” 🤡往期回顾🤡:初步了解 list 🌹🌹期待您的关注 🌹🌹 ❀STL之list 📒1. list…...
技术管理之巅—如何从零打造高质效互联网技术团队阅读体验
技术管理之巅—如何从零打造高质效互联网技术团队 《技术管理之巅:如何从零打造高质效互联网技术团队》是黄哲铿所著的一本书,致力于帮助技术管理者从零开始打造高效的互联网技术团队。该书分为多个章节,分别探讨了从团队文化建设到技术架构…...
机器学习与数据挖掘知识点总结(一)
简介:随着人工智能(AI)蓬勃发展,也有越来越多的人涌入到这一行业。下面简单介绍一下机器学习的各大领域,机器学习包含深度学习以及强化学习,在本节的机器学习中主要阐述一下机器学习的线性回归逻辑回归&…...
行心科技中禄松波携手,开启智能健康新时代
在2024年第34届健博会暨中国大健康产业文化节的盛大舞台上,广州市行心信息科技有限公司(以下简称“行心科技”)与浙江中禄松波生物工程有限公司(以下简称“中禄松波”)宣布达成战略合作,共同推动医康养产业…...
前端多人项目开发中,如何保证CSS样式不冲突?
在前端项目开发中,例如突然来了一个大项目,很可能就需要多人一起开发,领导说了,要快,要快,要快,你们给我快。然后下面大伙就一拥而上,干着干着发现,一更新代码࿰…...
【YOLOv10改进[CONV]】使用DualConv二次创新C2f模块实现轻量化 + 含全部代码和详细修改方式 + 手撕结构图 + 全网首发
本文将使用DualConv二次创新C2f模块实现轻量化,助力YOLOv10目标检测效果的实践,文中含全部代码、详细修改方式以及手撕结构图。助您轻松理解改进的方法。 改进前和改进后的参数对比: 目录 一 DualConv 1 结合33卷积和11卷积核 2 DualConv 3 可视化 二 C2f_DualConv助…...
基于SSM+Jsp的高校信息资源共享平台
开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包…...
软件测试--Linux快速入门
文章目录 软件测试-需要掌握的Linux指令Linux命令操作技巧Linx命令的基本组成常用命令 软件测试-需要掌握的Linux指令 Linux命令操作技巧 使用Tab键自动补全上下键进行翻找之前输入的命令命令执行后无法停止使用CtrC,结束屏幕输出 Linx命令的基本组成 命令 [-选项] [参数] …...
module ‘django_cas_ng.views‘ has no attribute ‘login‘
这个错误表明你正在尝试从django_cas_ng.views模块中访问一个名为login的属性,但是这个模块中并没有名为login的属性或方法。 解决这个问题,你需要确认你的代码中是否有错误的引用。django_cas_ng是一个CAS(Central Authentication Service&…...
CW32F030K8T7单片机在即热式热水器的应用介绍
随着智能家居技术的不断进步,即热式热水器作为现代家庭中的重要组成部分,正逐渐向智能化、节能化方向发展。本方案通过采用武汉芯源半导体的CW32F030系列单片机,以其高性能、超强抗干扰等特性,为即热式热水器的智能化提供了理想的…...
HTML静态网页成品作业(HTML+CSS)—— 美食湘菜介绍网页(5个页面)
🎉不定期分享源码,关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 🏷️本套采用HTMLCSS,未使用Javacsript代码,共有5个页面。 二、作品演示 三、代…...
使用redis构建简单的社交网站
Redis命令实践通常涉及对Redis服务器的直接操作,包括数据的增删改查以及管理Redis实例。以下是一些基本的Redis命令及其使用场景: 连接Redis服务器: 使用Redis客户端连接到Redis服务器:redis-cli 设置和获取键值对: SET key value࿱…...
【Java面试】九、微服务篇-SpringCloud(上)
文章目录 1、SpringCloud五大组件2、服务注册和发现2.1 Eurake2.2 Eurake和Nacos的区别 3、Ribbon负载均衡3.1 策略3.2 自定义负载均衡策略 4、服务雪崩与熔断降级4.1 服务雪崩4.2 服务降级4.3 服务熔断 5、服务限流5.1 Nginx限流5.2 网关限流 6、微服务监控7、面试 1、SpringC…...
Python 树状数组
树状数组(Binary Indexed Tree, BIT),又称为斐波那契堆,是一种数据结构,用于高效地解决以下问题: 单点更新:在数组的某个位置增加或减少一个值。区间查询:查询数组中一段连续区间的…...
【QEMU中文手册】2.2 调用方式(持续更新中)
本文由 AI 翻译(ChatGPT-4)完成,并由作者进行人工校对。如有任何问题或建议,欢迎联系我。联系方式:jelin-shoutlook.com。 原文:Invocation — QEMU documentation qemu-system-x86_64 [选项] [磁盘镜像]磁…...
(函数)判断一句话中最长的单词(C语言)
一、运行结果; 二、源代码; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明函数; int aiphabetic(char); int longest(char[]);int main() {//初始化变量值;int i;char line[100] { 0 };//获取用户输入字符…...
QT5.5.0中使用lambda表达式时遇到的问题
QT5.5中使用lambda表达式的遇到的error_qt中lamda不起作用-CSDN博客...
【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
🔥 个人主页:空白诗 文章目录 引言一、切片究竟是什么?1.1 基础的创建数组示例1.2 基础的创建切片示例1.3 切片与数组的关系 二、切片的高级特性:动态扩容2.1 使用 append 函数扩容2.2 容量管理与性能考量2.3 切片的截取与缩容 三…...
Python与C语言:深入探索两者的奥秘与差异
Python与C语言:深入探索两者的奥秘与差异 在编程的世界里,Python和C语言如同两位性格迥异的伙伴,各自拥有独特的魅力和应用场景。Python以其简洁易懂的语法和强大的库支持赢得了众多开发者的青睐,而C语言则以其接近硬件的低级特性…...
图像编解码器在AI绘画中的革新作用
随着人工智能技术的飞速发展,AI绘画已经从一个简单的概念演变为一个充满创意与可能性的领域。在这场技术与艺术的融合中,图像编解码器扮演着至关重要的角色。它们不仅提升了AI绘画的质量和效率,还拓宽了艺术创造的边界。本篇博客将深入探讨图…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...
