当前位置: 首页 > news >正文

【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类框架的建立 (成员与模板)

首先,库里的类是一个带头双向循环链表,我们今天的底层实现将会紧跟着库里的实现走

所以,我们的成员为:

  1. 两个指针(一个指向下一个节点_next,一个指向上一个节点_prev)
  2. 一个数据(表示节点内的数据)

而为了实现这个链表什么类型的值都可以存进去,所以我们需要写一个类模板表示那个数据类型

代码如下:

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 插入

这里虽然插入并没有迭代器失效,但是库里面还是写了返回值,所以我们写的版本也写一个返回值吧

插入的参数是两个:

  1. 迭代器(代表位置)
  2. 待插入的数据

而我们只需要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());
}

拷贝构造

我们的拷贝构造主要分为以下两个步骤:

  1. new出哨兵位(上文中我们提到了empty_init可以直接使用该函数)
  2. 将参数传过来的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&#xff08;适合不同类型构造函数的小函数&#xff09; list的迭代器 引子 operator、operator--&#xff08;前置与后置&#xff09; operator 与 operator! operator* 与 …...

警惕智能手机的“隐形眼”:如何保护我们的数字隐私堡垒

随着智能手机深入我们生活的方方面面&#xff0c;它变得无所不在&#xff0c;甚至无所不知。 但你是否意识到&#xff0c;你的手机可能正在悄无声息地“监听”你的一举一动&#xff1f; 从你的搜索习惯到日常对话&#xff0c;手机的个性化推荐系统正不断收集你的数据。 本文…...

人工智能算法工程师(高级)课程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 图床概念 ​ "图床"是一个网络术语&#xff0c;它指的是一种用于存储和托管图片…...

组合数的低复杂度运算

题源 题目 F. 预期中位数 每次测试的时间限制&#xff1a;3 秒 每次测试的内存限制&#xff1a;256 兆字节 Arul 有一个长度为 n 的二进制数组* a。 他将取该数组中所有长度为 k&#xff08;k 为奇数&#xff09;的子序列并找到它们的中位数。 所有这些值的总和是多少&#xf…...

小型并网式光伏气象站:光伏电站的智能守护者

小型并网式光伏气象站以其独特的功能和优势&#xff0c;成为了电站高效运行的智能守护者。小型并网式光伏气象站通过精准的数据采集与分析&#xff0c;为光伏电站的运维管理提供了强有力的支持。 小型并网式光伏气象站能够实时监测并记录光伏电站周围环境的多种气象参数&#x…...

JavaScript 中的回调函数(callback)

JavaScript 中的回调函数&#xff08;callback&#xff09; JavaScript 中的回调函数&#xff08;callback&#xff09;是一个传递给另一个函数作为参数的函数&#xff0c;并且这个传递的函数可以在其他函数内部被调用执行。回调函数是异步编程的一个核心概念&#xff0c;特别…...

计算机毕业设计hadoop+spark+hive漫画推荐系统 动漫视频推荐系统 漫画分析可视化大屏 漫画爬虫 漫画推荐系统 漫画爬虫 知识图谱 大数据

HadoopSparkHive漫画推荐系统详细开题报告 一、引言 随着互联网技术的飞速发展&#xff0c;动漫和漫画产业的数据量急剧增长。用户面临着海量漫画作品的选择难题&#xff0c;如何从这些数据中高效地提取有价值的信息&#xff0c;为用户推荐符合其喜好的漫画作品&#xff0c;成…...

解决pycharm日志总是弹出“无法运行Git,未安装Git”的问题

需求分析 我电脑中安装了git&#xff0c;但是打开pycharm&#xff0c;右下角总是弹出 无法运行Git,未安装Git的日志。 解决方法 首先打开pycharm&#xff0c;按照以下路径&#xff0c;依次点击。 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 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…...

简单的docker学习 第13章 CI/CD与Jenkins(下)

第13章 CI/CD 与 Jenkins 13.13 自由风格的 CI 操作(最终架构) 前面的架构存在的问题是&#xff0c;若有多个目标服务器都需要使用该镜像&#xff0c;那么每个目标服务器都需要在本地构建镜像&#xff0c;形成系统资源浪费。若能够在 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智能名片小程序的创新应用与深度剖析

摘要&#xff1a;在数字化浪潮的推动下&#xff0c;信息传播与沟通方式正经历着前所未有的变革。立体连接模式&#xff0c;作为这一变革的重要产物&#xff0c;通过整合物理空间、虚拟网络空间与社群心理空间的三维联动&#xff0c;实现了信息的深度传播与高效互动。AI智能名片…...

基于Python的Scrapy爬虫的个性化书籍推荐系统【Django框架、超详细系统设计原型】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍系统分析系统设计展示总结 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 近年来&#xff0c;随着互联网的蓬勃发展&#xff0c;企事业单…...

二叉树bst

二叉搜索树的中序遍历结果有序 &#xff0c;二叉搜索树性质&#xff0c;左小右大&#xff0c;二叉搜索树中序遍历的结果应该是从小到大的。 题目描述二叉树是从上到下&#xff0c;从左到右描述&#xff0c;并非前中后序中的一种。 99. 恢复二叉搜索树 class Solution:first …...

elasticsearch的使用(二)

DSL查询 Elasticsearch的查询可以分为两大类&#xff1a; 叶子查询&#xff08;Leaf query clauses&#xff09;&#xff1a;一般是在特定的字段里查询特定值&#xff0c;属于简单查询&#xff0c;很少单独使用。 复合查询&#xff08;Compound query clauses&#xff09;&am…...

YOLOv8由pt文件中读取模型信息

Pytorch的pt模型文件中保存了许多模型信息&#xff0c;如模型结构、模型参数、任务类型、批次、数据集等 在先前的YOLOv8实验中&#xff0c;博主发现YOLOv8在预测时并不需要指定任务类型&#xff0c;因为这些信息便保存在pt模型中&#xff0c;那么&#xff0c;今天我们便来看看…...

js遍历效率

1w条数据&#xff0c;遍历效率 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…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

mac:大模型系列测试

0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何&#xff0c;是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试&#xff0c;是可以跑通文章里面的代码。训练速度也是很快的。 注意…...

【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器

从本章节开始&#xff0c;进入到函数有多个参数的情况&#xff0c;前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参&#xff0c;ECX是整型的第一个参数的寄存器&#xff0c;那么多个参数的情况下函数如何传参&#xff0c;下面展开介绍参数为整型时候的几种情…...