【C++】STL | list (链表)详解及重要函数的实现
目录
前言
总代码
ListNode类框架的建立 (成员与模板)
list类的框架
普通构造 与 empty_init(适合不同类型构造函数的小函数)
list的迭代器
引子
operator++、operator--(前置与后置)
operator== 与 operator!=
operator* 与 operator->
operator*
operator->
普通迭代器 与 const迭代器的实现
begin、end(const 与 非const)
insert 插入
erase 删除
push_back、pop_back、push_front、pop_front(头删尾删、头插尾插)
拷贝构造
析构函数
赋值重载
initializer_list 构造
结语

前言
相比于vector和string,list这个迭代器可就开始上强度了
其主要的强度在于:vector和string都是开辟的一段连续的空间,所以这两个的迭代器用原生指针就可以实现
但是list不一样,list的空间是分散的,只有通过每个节点里的next指针才能找到下一个节点,还有就是实现const迭代器于普通迭代器两个版本的时候,需要在模板参数上动点手脚,这也是一个相对晦涩的地方
如果有友友想看一下list全部函数的用法的话,可以到C++较为官方的网站上去查阅,如下:
https://legacy.cplusplus.com/reference/list/list/?kw=list


总代码
如果有友友只是复习需要,只想看完整代码的话,可以直接点下面的gitee链接
当然对于看完了整篇文章的友友也可以照着这里的代码敲一篇进一步理解喔...(* ̄0 ̄)ノ
gitee - list - blog - 2024-08-07
ListNode类框架的建立 (成员与模板)
首先,库里的类是一个带头双向循环链表,我们今天的底层实现将会紧跟着库里的实现走
所以,我们的成员为:
- 两个指针(一个指向下一个节点_next,一个指向上一个节点_prev)
- 一个数据(表示节点内的数据)
而为了实现这个链表什么类型的值都可以存进去,所以我们需要写一个类模板表示那个数据类型
代码如下:
template<class T>
struct ListNode
{ListNode(const T& x):_next(nullptr),_prev(nullptr),_data(x){}ListNode<T>* _next;ListNode<T>* _prev;T _data;
};
list类的框架
我们list类里面仅需要一个参数,就是一个指向头节点的指针
由于库里的类是一个带头双向循环链表,所以我们这个指针是需要指向哨兵位的(这个在构造函数部分再解决)
另外,我们还可以给ListNode类型加一个typedef,将其改成Node方便我们后续的操作
代码如下:
template<class T>
class list
{typedef ListNode<T> Node;private:Node* _head;
};
普通构造 与 empty_init(适合不同类型构造函数的小函数)
这个函数的作用就是——new一个哨兵位,因为我们后续会有很多种构造,比如普通的构造,拷贝构造,initializer_list构造,这些构造之前都需要创建哨兵位,而这些代码又都是重复的代码,所以就写一个小函数来放这一部分代码
代码如下:
// 初始化函数(创建哨兵位)void empty_init()
{_head = new Node(0);_head->_next = _head;_head->_prev = _head;
}
由于我们上面实现了一个empty_init小函数(作用是创建哨兵位),所以我们这个普通的构造直接使用这个小函数即可,代码如下:
list()
{empty_init();
}
list的迭代器
引子
我们之前的vector和string都是一段连续空间,所以原生指针就可以作为其迭代器,直接typedef一下即可
string:
typedef char* iterator;verctor:
typedef T* iterator;
我们对其原生指针++就能找到下一个,--亦然
但是我们今天的list并不是连续的空间,所以我们可以这么玩儿:搞一个类,类里面重载operator++和operator--实现下一个和上一个,然后还有operator*,operator->等等,其成员只需要一个节点即可
同时,我们这里的节点指向的空间都是浅拷贝的,因为我要的就是指向同一块空间,这样我才能修改到,如果是另外开空间的话就成大坑了
如下:
template<class T>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;ListIterator(Node* node):_node(node){}
};
我们会看到上面还typedef了一个Self,这是因为我们后续的++和--都需要返回一个iterator类型,为了方便,所以我们就多写一个typedef
operator++、operator--(前置与后置)
我们operator++的主要逻辑就是让_node指向下一个,所以我们只需要让_node做出改变,再将一个iterator类型返回即可(*this),operator--依然
//前置++与--Self& operator++()
{_node = _node->_next;return *this;
}Self& operator--()
{_node = _node->_prev;return *this;
}
注意,我们这里需要的是返回一整个iterator类型,所以是先处理_node的位置再返回*this,而不是直接返回_node->_next,这样子的话返回的类型不匹配,迭代器在外面接受的时候发现类型不匹配就报错了
你可以想象一下,这个iterator是一个整体,要我们遍历到下一个的时候,我们是将这个整体里面的_node改变了,让其指向下一个之后,再返回这个整体的
同样的
前置的版本与后置的版本唯一的区别就在于参数那个地方,加一个int(没有实际意义)之后,编译器就会知道你这个是后置的(这也算是祖师爷为这块打的一个补丁吧,记住就好)
代码如下:
//后置++与--Self& operator++(int)
{Self tmp(*this);_node = _node->_next;return tmp;
}Self& operator--(int)
{Self tmp(*this);_node = _node->_prev;return tmp;
}
operator== 与 operator!=
这块的代码相对简单,就不做讲解了,代码如下:
bool operator!=(const Self& it)
{return _node !=it._node;
}bool operator==(const Self& it)
{return _node == it._node;
}
operator* 与 operator->
operator*
我们的operator*要的就是直接返回这个链表节点里面对应的值,所以我们直接返回_node里面的_data就是了,如下:
T& operator*()
{return _node->_data;
}
operator->
而operator->相对来说会更为的晦涩难懂
我们在外面使用iterator的时候,是直接 it->_data 来取到数据的,按理来说,我们在实现的内部也应该返回一个引用
但是其实编译器在这里做了一个隐藏,你看到的 it->_data 其实被隐藏了一个 ->
it->_data;
it.operator->()->_data;
如上我们可以看到,编译器在这里确实是为了美观藏了一个 ->,所以我们在返回的时候,应该返回一个指针类型的数据,也就是 _node->_data 的指针
综上,代码如下:
T* operator->()
{return &_node->_data;
}
注意,这里的 ->优先级高于&,所以取到的是_data的地址
普通迭代器 与 const迭代器的实现
我们的const与非const,其实也就是operator*与operator->需要改写成const而已
至于其他的函数比如operator++,operator--,operator==,operator!=,这些都不会影响,因为const迭代器要的只是指向的内容不修改而已,++、--只是指向下一个节点而已,要加const的只是取出数据的那两个函数重载
但是这里有两个,我们难道再写一个类吗?太冗余了
那传一个const T过去吗?不行,类型不匹配(ListNode那个类的 T 不是const)
这里最好的方法就是,模板那里再加两个参数
T& operator*()
{return _node->_data;
}T* operator->()
{return &_node->_data;
}
我们可以看到,这里就只有两个函数需要用const修饰,而唯一需要改的就只有返回值,所以我们直接加两个参数:
一个用来代替T&(Ref),一个用来代替T*(Ptr)
如下:
template<class T, class Ref, class Ptr>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//。。。。。。其他重载函数};
这样子的话,我们在typedef迭代器的时候,就可以分别控制了,如下:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
begin、end(const 与 非const)
我们的begin和end都需要返回一个迭代器类型的引用
而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);
}
insert 插入
这里虽然插入并没有迭代器失效,但是库里面还是写了返回值,所以我们写的版本也写一个返回值吧
插入的参数是两个:
- 迭代器(代表位置)
- 待插入的数据
而我们只需要new一个新节点,然后将该节点的前后关系链接起来即可,图示如下:




代码如下:
//insert没有迭代器失效
iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;return iterator(newnode);
}
erase 删除
需要注意的是,删除这里是有迭代器失效的,因为删除之后,这个节点就没了,而我们的迭代器还指向那块被销毁的空间,所以需要返回值,在外面接收的时候就能接收到删除位置的下一个位置的迭代器,这样子就能处理好迭代器失效的问题了
图示如下:(假设此时我要删除的是3节点)




代码如下:
//erase有迭代器失效
iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);
}
push_back、pop_back、push_front、pop_front(头删尾删、头插尾插)
这里的插入和删除我们都可以复用 insert 和 erase 的逻辑
头删就是删除begin位置的节点
尾删就是删除哨兵位的上一个节点
头插就是在begin位置的前面插入新节点
尾插就是在哨兵位位置的前面插入新节点
代码如下:
void push_back(const T& x)
{insert(end(), x);
}void pop_back()
{erase(--end());
}void push_front(const T& x)
{insert(begin(), x);
}void pop_front(const T& x)
{erase(begin());
}
拷贝构造
我们的拷贝构造主要分为以下两个步骤:
- new出哨兵位(上文中我们提到了empty_init可以直接使用该函数)
- 将参数传过来的list一个一个push_back
代码如下:
//拷贝构造
list(const list<T>& lt)
{empty_init();for (auto e : lt){push_back(e);}
}void empty_init()
{_head = new Node(0);_head->_next = _head;_head->_prev = _head;
}
析构函数
这里的析构我们可以直接选择复用上文实现的erase
上文中提到,由于删除了之后,迭代器指向的节点被删除会发生迭代器失效,所以返回的是下一个节点的迭代器
我们这里可以直接使用迭代器访问,每遍历到一个就删除一个,然后再拿迭代器接收就行了,也不用走++,因为返回的就是下一个
删除完所有节点之后,我们还需要手动将哨兵位删除,最后再将_head指针置为空即可
代码如下:
~list()
{auto it = begin();while (it != end()){it = erase(it);}delete _head;_head = nullptr;
}
赋值重载
我们来思考这样一个问题,我们的整个链表,其实都只是由一个指向哨兵位的指针维护的,如果已经有一个不要的对象了,我们要获得这个对象的空间的话,我们只需要将两个对象的指针交换即可
同理,我们在这里可以让参数传一个list过来,但是我们不加&,这样编译器就会自动调用拷贝构造拷贝一份,函数结束后调用其析构销毁
而我们要快速获得这块空间的话,我们只需要将两个对象的指针交换,这样其实就间接地完成了空间的交换,也就完成了赋值重载
代码如下:
list<T>& operator=(list<T> lt)
{swap(_head, lt._head);return *this;
}
initializer_list 构造
initializer_list其实是一个类,这个类里面也有空间存着我们要的数据
我们需要先使用empty_list这个上文中提到的函数,将哨兵位创造出来
void empty_init()
{_head = new Node(0);_head->_next = _head;_head->_prev = _head;
}
然后就只需要使用范围for将里面的数据一个一个提取出来,再依次push_back即可
代码如下:
list(initializer_list<T> il)
{empty_init();for (auto& e : il){push_back(e);}
}
结语
到这里我们的list底层实现相关内容就结束啦(~ ̄▽ ̄)~
如果觉得对你有帮助的话,希望可以多多支持喔(〃 ̄︶ ̄)人( ̄︶ ̄〃)我们下一篇博客,再见!
相关文章:
【C++】STL | list (链表)详解及重要函数的实现
目录 前言 总代码 ListNode类框架的建立 (成员与模板) list类的框架 普通构造 与 empty_init(适合不同类型构造函数的小函数) list的迭代器 引子 operator、operator--(前置与后置) operator 与 operator! operator* 与 …...
警惕智能手机的“隐形眼”:如何保护我们的数字隐私堡垒
随着智能手机深入我们生活的方方面面,它变得无所不在,甚至无所不知。 但你是否意识到,你的手机可能正在悄无声息地“监听”你的一举一动? 从你的搜索习惯到日常对话,手机的个性化推荐系统正不断收集你的数据。 本文…...
人工智能算法工程师(高级)课程12-自然语言处理之NLP的语言模型-ELMo,transformer,BERT与代码详解
大家好,我是微学AI,今天给大家介绍一下人工智能算法工程师(高级)课程12-自然语言处理之NLP的语言模型-ELMo,transformer,BERT与代码详解。本课程面向高级人工智能算法工程师,深入讲解自然语言处理(NLP)中的关键语言模型技术,包括了EMLo和transformer架构。此外,课程还详细…...
PicGo + gitee 免费搭建个人图床
目录 1 图床概念2 使用gitee和PicGo搭建图床流程2.1 下载安装PicGo工具 3 图片上传错误处理3.1 PicGo客户端提示404错误信息图片上传失败3.2 PicGo客户端提示400错误信息图片上传失败 1 图床概念 "图床"是一个网络术语,它指的是一种用于存储和托管图片…...
组合数的低复杂度运算
题源 题目 F. 预期中位数 每次测试的时间限制:3 秒 每次测试的内存限制:256 兆字节 Arul 有一个长度为 n 的二进制数组* a。 他将取该数组中所有长度为 k(k 为奇数)的子序列并找到它们的中位数。 所有这些值的总和是多少…...
小型并网式光伏气象站:光伏电站的智能守护者
小型并网式光伏气象站以其独特的功能和优势,成为了电站高效运行的智能守护者。小型并网式光伏气象站通过精准的数据采集与分析,为光伏电站的运维管理提供了强有力的支持。 小型并网式光伏气象站能够实时监测并记录光伏电站周围环境的多种气象参数&#x…...
JavaScript 中的回调函数(callback)
JavaScript 中的回调函数(callback) JavaScript 中的回调函数(callback)是一个传递给另一个函数作为参数的函数,并且这个传递的函数可以在其他函数内部被调用执行。回调函数是异步编程的一个核心概念,特别…...
计算机毕业设计hadoop+spark+hive漫画推荐系统 动漫视频推荐系统 漫画分析可视化大屏 漫画爬虫 漫画推荐系统 漫画爬虫 知识图谱 大数据
HadoopSparkHive漫画推荐系统详细开题报告 一、引言 随着互联网技术的飞速发展,动漫和漫画产业的数据量急剧增长。用户面临着海量漫画作品的选择难题,如何从这些数据中高效地提取有价值的信息,为用户推荐符合其喜好的漫画作品,成…...
解决pycharm日志总是弹出“无法运行Git,未安装Git”的问题
需求分析 我电脑中安装了git,但是打开pycharm,右下角总是弹出 无法运行Git,未安装Git的日志。 解决方法 首先打开pycharm,按照以下路径,依次点击。 file -----settings-----version control -----Git----Git path(选择自己下载…...
threejs 节点材质系统 绑定attribute
新的 节点材质系统 绑定属性及使用 非常方便 不必重复声明 以instances为例 import {instancedBufferAttribute,instancedDynamicBufferAttribute,} from "three/tsl";声明一个 InstancedBufferAttribute 使用 instancedBufferAttribute包装后就可以在shader中直接使…...
Rabbitmq的几种工作模式
工具类 public class RabbitMQConnection {public static Connection getConnection() throws Exception{//1.创建connectionFactoryConnectionFactory connectionFactory new ConnectionFactory();//2.配置HostconnectionFactory.setHost("127.0.0.1");//3.设置Po…...
如何在 Debian 上安装运行极狐GitLab Runner?【二】
极狐GitLab 是 GitLab 在中国的发行版,专门面向中国程序员和企业提供企业级一体化 DevOps 平台,用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规,而且所有的操作都是在一个平台上进行,省事省心省钱。可以一键安装极狐GitL…...
简单的docker学习 第13章 CI/CD与Jenkins(下)
第13章 CI/CD 与 Jenkins 13.13 自由风格的 CI 操作(最终架构) 前面的架构存在的问题是,若有多个目标服务器都需要使用该镜像,那么每个目标服务器都需要在本地构建镜像,形成系统资源浪费。若能够在 Jenkins 中将镜像相撞构建好并推送到 Har…...
基于STM32设计的智能鱼缸_带鱼儿数量视觉识别(华为云IOT)(202)
文章目录 一、前言1.1 项目介绍【1】项目功能介绍【2】设计实现的功能【3】项目硬件模块组成1.2 设计思路【1】整体设计思路【2】ESP8266工作模式配置【3】自动换水原理1.3 项目开发背景【1】选题的意义【2】可行性分析【3】参考文献1.4 开发工具的选择【1】设备端开发【2】上位…...
立体连接模式下的传播与沟通:AI智能名片小程序的创新应用与深度剖析
摘要:在数字化浪潮的推动下,信息传播与沟通方式正经历着前所未有的变革。立体连接模式,作为这一变革的重要产物,通过整合物理空间、虚拟网络空间与社群心理空间的三维联动,实现了信息的深度传播与高效互动。AI智能名片…...
基于Python的Scrapy爬虫的个性化书籍推荐系统【Django框架、超详细系统设计原型】
文章目录 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主项目介绍系统分析系统设计展示总结 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主 项目介绍 近年来,随着互联网的蓬勃发展,企事业单…...
二叉树bst
二叉搜索树的中序遍历结果有序 ,二叉搜索树性质,左小右大,二叉搜索树中序遍历的结果应该是从小到大的。 题目描述二叉树是从上到下,从左到右描述,并非前中后序中的一种。 99. 恢复二叉搜索树 class Solution:first …...
elasticsearch的使用(二)
DSL查询 Elasticsearch的查询可以分为两大类: 叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。 复合查询(Compound query clauses)&am…...
YOLOv8由pt文件中读取模型信息
Pytorch的pt模型文件中保存了许多模型信息,如模型结构、模型参数、任务类型、批次、数据集等 在先前的YOLOv8实验中,博主发现YOLOv8在预测时并不需要指定任务类型,因为这些信息便保存在pt模型中,那么,今天我们便来看看…...
js遍历效率
1w条数据,遍历效率 1、for 15s let t(new Date()).getTime()let a[]for(var i 0; i < 100000; i){a.push({id:i,val:i})}let ts[]for(var i 0; i < a.length; i){if(a[i].val!2 && a[i].val!4 && a[i].val!8){ts.push(a[i])}}let c(new D…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...
图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...
负载均衡器》》LVS、Nginx、HAproxy 区别
虚拟主机 先4,后7...
