【C++STL】list的详细用法和底层实现

🌟个人主页:第七序章  

🌈专栏系列:C++

目录

❄️前言:

🌈一:介绍

🌈二:list的创建

☀️基本框架

🌙节点类

🌙构造函数:

☀️list类

🌙构造函数

🌙拷贝构造函数

🌙赋值重载

🌙析构函数

☀️++运算符重载

🌙- -运算符的重载

🌙==运算符重载

🌙!=运算符的重载!=运算符的重载

🌙*运算符的重载

🌙->运算符的重载

☀️迭代器相关函数

☀️插入和删除函数

🌙insert

🌙erase函数

🌙push_fron, pop_back, pop_front

☀️其他函数

🌙size函数

🌙clear函数

🌙swap函数

☀️list的sort vs 库的sort

🌙vector和list的排序效率

☀️从迭代器类重新理解封装

🌻共勉:


❄️前言:

上一篇我们学习了C++STL库vector的详细用法,今天我们来学习一下list的详细用法和底层实现。

🌈一:介绍

⭐list是一种序列式容器(带头双向循环链表)。该容器的底层是用双向链表实现的, 在链表的任一位置进行元素的插入、删除操作都是快速的。

🌈二:list的创建

☀️基本框架

🌙节点类

template<class T>
struct list_node
{T _data;list_node<T>* _next;list_node<T>* _prev;
};

🌙构造函数:

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){}
};
  • ⭐这里我们就用初始化列表对链表节点对象进行初始化,对节点存储的值用匿名对象进行缺省参数赋值。前后节点指针初始化为空指针 
  • ⭐这里的匿名对象对于自定义类型会去调用他们的默认构造来初始化,内置类型也有构造函数就是int,float,double是0

☀️list类

🌙构造函数

void empty_init()
{_head = new Node(); //调用构造函数,创造节点初始化,链表是一个个节点连接起来_head->_next = _head;_head->_prev = _head;	//带头双向循环
}
  • ⭐我们实现一个成员函数来初始化哨兵位节点

这里补充一下_head, 是链表的哨兵位节点

private:Node* _head;
  • ⭐构造函数直接调用这个初始化函数就行了
list()
{empty_init();
}

🌙拷贝构造函数

		list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}
  • ⭐在string和vector两个容器里面我已经详细讲解了拷贝构造的要求,最重要的就是要完成深拷贝。这里我们就用了push_back这个接口来完成深拷贝。
	void push_back(const T& x){Node* new_node = new Node(x); //new 可以开空间,也能调用构造函数初始化Node* tail = _head->_prev;tail->_next = new_node;new_node->_prev = tail;new_node->_next = _head;_head->_prev = new_node;	//改成_head->_prve就没有问题了_size++;}

可以看到我们的push_back函数用new开了新空间,并且把对应的指针指向了新空间,这就完成了深拷贝。
或许有同学疑问,为啥这_head->_prev = new_node;不写成tail = new_node
原因就是tail是一个指针变量,我们改变指针变量的值是不能改变指针变量指向的值的。

所以改变tail并不能改变_head->_prev,这里我面的本意是想把_head->_prev这个指针的指向改变

🌙赋值重载

	list<T>& operator=(list<T> lt){swap(lt);return *this;}

还记得我在前面两章说vector和string的赋值重载的时候吗,资本家思想。如果我想要得到lt的并且想扔掉原来的资源,只需要把swap一下,lt这个局部变量在调用完这个函数就会销毁。因为我现在已经把原来this的资源交换给了lt, 所以销毁lt就相当于销毁了原来this指向的资源。

🌙析构函数

	~list(){clear();delete _head;_head = nullptr;}
  • ⭐这里我们直接先提前看一下clear的内部实现,来了解析构函数
		void clear(){auto it = begin();while (it != end()){it = erase(it);}}
  • ⭐可以看到就是从头结点删除到尾节点,这里的erae方法后面介绍
  • ⭐所以析构的时候我们就只需要delete一下哨兵节点就行了

☀️++运算符重载

Self& operator++()
{_node = _node->_next;return *this;
}
  • ⭐从链表节点的类中我们知道节点的下一个节点的位置是用一个_next成员变量指针指向的,所以指向那个位置就行了。

这就是一个后置++的实现,接下来我们实现前置++

	Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}
  • ⭐这里我们就是返回自增之前的那个对象
  • ⭐解释下Self的类型,其实就是迭代器对象的类型
typedef list_iterator<T, Ref, Ptr> Self;

🌙- -运算符的重载

		Self& operator--(){_node = _node->_prev;return *this;}
  • ⭐和++相反,++是找到后面的那个节点,而–就是找到前面那个节点。
		Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}
  • ⭐前置- - 同上面前置++

🌙==运算符重载

bool operagor == (const Self & s)
{return _node == s._node;
}
  • ⭐判断两个节点指针指向的地址是否相同即可

🌙!=运算符的重载!=运算符的重载

  • ⭐!=运算符刚好和==运算符的作用相反,我们判断这两个迭代器当中的结点指针的指向是否不同即可
	bool operator!=(const Self& s){return _node != s._node;}

🌙*运算符的重载

		Ref operator*(){return _node->_data;}
  • ⭐返回节点指针的存储数据的成员变量_data就可以了
  • ⭐这里的Ref是引用的类型,模板推导出来的引用类型

🌙->运算符的重载

		Ptr operator->(){return &_node->_data;}

☀️迭代器相关函数

	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

iterator insert(iterator pos, const T& val)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;_size++;return iterator(newnode);}

这里插入很easy,只需要把插入的新节点前后指针更新到对应的指针就行了

  • 注意这里插入不存在迭代器失效的问题,因为我们的扩容都是一个个独立的空间,不存在像vector那样扩容后会导致迭代器无法找到新开的空间,这里的迭代器是通过指针来找到空间的

🌙erase函数

	iterator erase(iterator pos){assert(pos != end()); //注意这里是给end, pos是迭代器的类型对象Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;next->_prev = prev;prev->_next = next;delete cur;_size--;return iterator(next);}

注意:这里释放节点就要注意迭代器失效的问题了,我们删除后,指向这个节点的迭代器指向的就是一个无效的内存,这时候就需要更新这个迭代器让他指向有效的内存。

🌙push_fron, pop_back, pop_front

void push_front(const T& x)
{insert(begin(), x);}
void pop_front()
{erase(begin());
}
void pop_back()
{erase(--end());
}
  • 复用insert和erase接口就行,push_front就是insert到头结点位置,pop_front就是删除头结点位置,pop_back则是删除end(end是最后一个节点的下一个节点)前一个位置

☀️其他函数

🌙size函数

size_t size() const
{return _size;
}
  • 我们通过,一个成员变量来实现,如果链表插入节点的时候就自增_size,删除节点的时候就自减_size,可以看看前面的插入和删除函数

🌙clear函数

	void clear(){auto it = begin();while (it != end()){it = erase(it);}}
  • 析构函数哪里前面已经解释过

🌙swap函数

		void swap(list<T>& tmp){std::swap(_head, tmp._head);}
  • 这里ist容器当中存储的实际上就只有链表的头指针,我们就交换两个变量的头结点就行了,就让他们交换了数据,
  • 这里我们还重载了一个全局的swap函数
template<class T>
void swap(list<T>& a, list<T>& b)
{a.swap(b);
}

为了防止使用算法库中的i那个很多拷贝构造的swap函数,我们重载一个全局函数通过模板有现成吃现成(两个链表类型的变量,相同的类型),会优先匹配我们自己实现的swap函数,然后我们这个全局的函数又调用成员函数swap,这个效率比算法库的那个效率高很多,为啥效率高,请看前面两个容器的讲解很详细。

☀️list的sort vs 库的sort

List为啥要自己实现一个sort函数来排序,不能直接用算法库中的sort吗?
1.不能,因为我们的迭代器按功能角度来分类有3种:

(1)单向迭代器(支持++) 例如:foward_list(单链表)
(2)双向迭代器(支持++.–), list
(3)随机迭代器(支持++,–, +, -) string,vector

注意:算法库中的是必须要是随机迭代器,而我们 list 实际上是一个带头双向链表,只能用双向迭代器,那么就不能传给算法库中的 sort。随机迭代器就兼容单向和双向迭代器,相当于是一个包含关系。

🌙vector和list的排序效率

  • 我们第一组数据是vector用算法库sort和list用他的成员函数sort单独排序,第二组数据是list的数据拷贝到vector给算法库的sort排序和list用他的成员函数sort单独排序
  • 可以看到copy到vector给算法库的sort排序都比list的sort快

原因:
链表这个不连续的结构并不适合大量数据的排序,他的索引访问不能像vector那样连续的索引访问那么高效,需要更多时间来找到索引位置
算法库中的sort是快速排序算法,list的sort用的是归并排序,快速排序还是比归并排序要厉害一点的。

  • 想要更高的效率排序,最好拷贝到vector中排序,排完再拷贝回list

☀️从迭代器类重新理解封装

  • 大家可以看到我们的迭代器类实际上是一个
	struct list_iterator
  • 在类外是可以访问的,虽然在类外是可以访问,但是我们通过对迭代器类重命名
	typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;

提供了类外统一用iterator来访问的方式,这样外面并不知道我实际是什么名字,并不能很好的猜出来,可以看到我们stl中容器的迭代器都是统一命名为iterator, 但是每个容器的迭代器的底层细节实现方式可能都有差异,但是用户都可以通过iterator来访问各个容器,只能通过我给的接口访问,这就是一个隐式的封装。
为啥要写成struct,就是为了方便类里面对他的高频访问的问题。如迭代器函数begin,插入insert

举个通俗例子:
想象你开车时,只需要操作方向盘、油门和刹车等控制系统,来控制车子的前进和停止。这些操作背后涉及了复杂的机械结构、发动机和电控系统等内部实现,但你作为司机并不需要了解这些内部细节,只需要通过车内的接口(方向盘、油门、刹车等)来进行控制。

 

在这个例子中:

  • 车子就是对象,封装了车的内部组件(发动机、轮胎、刹车系统等)。
  • 方向盘、油门和刹车是暴露出来的接口,司机通过这些接口与车子互动。
  • 司机不需要知道发动机如何工作或者刹车是如何控制的,这些细节被隐藏了

同样,封装在编程中通过类和方法来实现,外界使用对象时,只需要调用公开的接口(方法),而不需要关心类的内部实现。

🌻共勉:

以上就是本篇博客的所有内容,如果你觉得这篇博客对你有帮助的话,可以点赞收藏关注支持一波~~🥝


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

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

相关文章

AI大模型开发(多模态+提示词)

接着之前的例子&#xff0c;继续测试模型对话&#xff0c;今天主要测试多模态加上系统提示词。 一.多模态 多模态方法&#xff0c;主要添加了对图片的测试。 public String chatWithMessage(UserMessage userMessage){ChatResponse chatResponse qwenChatModel.chat(userMess…

Qt程序单独运行报错问题

Qt程序单独运行报错问题介绍问题原因分析解决方案&#xff08;从最佳实践到临时方法&#xff09;方法一&#xff1a;使用 windeployqt 工具&#xff08;最推荐、最规范&#xff09;方法二&#xff1a;临时修改系统 PATH&#xff08;适合开发调试&#xff09;方法三&#xff1a;…

Flask学习笔记(二)--路由和变量

一、路由Flask支持两种路由1、使用route()装饰器将URL绑定到函数app.route(/hello)def hello_world():return hello world2、使用应用程序对象的add_url_rule()函数def hello_world():return hello worldapp.add_url_rule(/, hello, hello_world)二、变量规则Flask开发中&#…

Skywalking告警配置+简易邮件告警应用配置(保姆级)

Skywalking告警配置简易邮件告警应用配置前言&#xff1a; 前文&#xff1a;SkyWalking Elasticsearch8 容器化部署指南&#xff1a;国内镜像加速与生产级调优_skywalkinges-CSDN博客 ​ SKywalking Agent配置Oracle监控插件安装指南-CSDN博客 Skywalking版本&#xff1a;V10.…

无人机如何实现图传:从原理到实战的全景解读

无人机图传的工作不是简单地把镜头的数据直接“丢”到一个屏幕上&#xff0c;而是一个由编码、传输、解码三段组成的系统。首先是视频编码&#xff1a;摄像头采集的原始画面通常需要经过编解码器压缩&#xff0c;常见标准包括H.264、H.265和VP9等。压缩的目的是减少数据量&…

AS32S601在轨重构(OTA)方案的优化与分析

摘要在轨重构&#xff08;OTA&#xff09;技术因其在航天、工业控制、物联网等领域的高可靠性和持续服务需求而备受关注。本文以国科安芯推出的AS32S601芯片为研究对象&#xff0c;深入分析其OTA方案的设计原理、技术细节及优化策略&#xff0c;并结合芯片的硬件特性&#xff0…

修复Android studio的adb无法连接手机问题

复制下面的内容到一个文本txt里面然后把里面的Android studio路径和sdk路径改成你自己的&#xff0c;然后改成把.txt改成bat 右键管理员运行 echo off REM Deep Fix for "Couldnt terminate the existing process" error REM This script will completely reset ADB …

css优化都有哪些优化方案

CSS 优化其实可以分成几个层面&#xff1a;性能优化、可维护性优化、兼容性优化以及用户体验优化。这里我帮你梳理一份比较系统的 CSS 优化方案清单&#xff0c;方便你参考&#xff1a;&#x1f539; 一、加载性能优化减少 CSS 文件体积压缩 CSS&#xff08;去掉空格、换行、注…

vue,uniapp 实现卷帘对比效果

需求&#xff1a;两张图重叠放在一起&#xff0c;拖动分割线实现卷帘对比效果&#xff0c;如图一、vue2代码 <template><div class"main"><div class"img-comparison" mousedown"startSlide"><img class"before"…

【笔记】空气弹簧概述、刚度调节原理

参考链接&#xff1a;汽车底盘空气悬架关键零部件之空气弹簧 1.概述 汽车空气弹簧&#xff08;Air Spring&#xff09;是一种以“压缩空气”作为弹性介质的悬架元件&#xff0c;用来取代传统钢制螺旋弹簧或钢板弹簧。它在乘用车、客车、重卡及轨道交通上越来越普及&#xff0…

UDP Socket 进阶:从 Echo 到字典服务器,学会 “解耦” 网络与业务

开篇&#xff1a;从 “回显” 到 “字典”&#xff0c;核心变在哪&#xff1f;上一篇我们实现了 Echo 服务器 —— 网络层和业务层是 “绑死” 的&#xff1a;网络层收到数据后&#xff0c;直接把原数据发回去。但实际开发中&#xff0c;业务逻辑会复杂得多&#xff08;比如查字…

数据结构之复杂度

数据结构的理解 数据本身是杂乱无章的&#xff0c;需要结构进行增删查改等操作更好的管理数据&#xff1b; 比如&#xff1a;在程序中需要将大量的代码&#xff08;数据&#xff09;通过结构进行管理&#xff1b; 再比如&#xff1a;定义1000个整型变量的数组&#xff0c;我们…

运维安全06 - 服务安全

云计算服务安全 在当今数字化时代&#xff0c;各种服务&#xff08;如网络应用、云计算平台、数据库系统等&#xff09;已成为我们日常生活和工作中不可或缺的一部分。 然而&#xff0c;随着服务的广泛应用&#xff0c;其安全性问题也日益凸显。 一、服务安全 服务安全是一…

01数据结构-初探动态规划

01数据结构-初探动态规划前言1.基本思想2.重叠子问题3.斐波那契数列4.备忘录&#xff08;记忆化搜索表&#xff09;4.1备忘录&#xff08;记忆化搜索表&#xff09;代码实现5.DP table5.1DP table代码实现6.练习前言 在学习动态规划时切忌望文生义&#xff0c;因为其名字与其思…

[智能算法]可微的神经网络搜索算法-FBNet

一、概述 相较于基于强化学习的NAS&#xff0c;可微NAS能直接使用梯度下降更新模型结构超参数&#xff0c;其中较为有名的算法就是DARTS&#xff0c;其具体做法如下。 首先&#xff0c;用户需要定义一些候选模块&#xff0c;这些模块内部结构可以互不相同&#xff08;如设置不同…

Elasticsearch安装启动常见问题全解析

文章目录&#x1f4da; Elasticsearch 安装与启动问题总结一、核心问题概览二、详细问题分析与解决方案1. &#x1f510; **权限问题&#xff1a;AccessDeniedException**❌ 错误日志&#xff1a;&#x1f4cc; 原因&#xff1a;✅ 解决方案&#xff1a;2. ⚙️ **配置冲突&…

Uniapp中使用renderjs实现OpenLayers+天地图的展示与操作

Uniapp中自带的地图组件对支持的地图服务略有局限&#xff0c;同时&#xff0c;该组件在样式布局上层级过高且无法控制&#xff0c;无法满足部分高度自定义化的需求。故引入renderjs视图层工具搭配OpenLayers框架对地图功能进行实现&#xff0c;但由于renderjs的限制&#xff0…

从C++开始的编程生活(8)——内部类、匿名对象、对象拷贝时的编译器优化和内存管理

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第8篇主要讲的是有关于C的内部类、匿名对象、对象拷贝时的编译器优化和内存管理。 C才起步&#xff0c;都很简单&#xff01;&#xff01; 目录 前言 内部类 性质 匿名对象 性质 ※对象拷贝时的…

MT5追大速率回测BUG

将MT5策略测试器中的回测速率调到最大(最快速度),**确实非常容易导致出现不符合策略逻辑的秒级成交(闪电交易)**。这并非MT5的“bug”,而是由**回测引擎的工作方式**与**策略代码的编写方法**在高速运行下不匹配所导致的。 --- ### 为什么最大速率会导致问题? MT5回测…

[数据结构——lesson10.堆及堆的调整算法]

引言 上节我们学习完二叉树后[数据结构——lesson9.二叉树]&#xff0c;这节我们将学习数据结构——堆 学习目标 1.堆的概念及结构 堆是一种特殊的完全二叉树结构&#xff0c;在计算机科学和数据结构中广泛应用&#xff0c;特别是在堆排序算法和优先队列的实现中&#xff0c;…