40 list类 模拟实现

ops/2024/12/17 10:44:14/

目录

一、list类简介

(一)概念

(二)list与string和vector的区别

二、list类使用

(一)构造函数

(二)迭代器

(三)list capacity

(四)list element access

(五)list modifiers

1、push_back与emplace_back的区别

2、注意

(六)list特有的接口

1、splice

2、sort

三、list类模拟实现


一、list类简介

(一)概念

        在 C++ 中,list是标准模板库(STL)中的一个容器类。它属于序列容器,用于存储一系列的元素。list容器是一个带头双向循环链表,这意味着每个节点包含数据以及指向前一个节点和后一个节点的指针。具体声明如下:

bb3878d651204848be86790b8c5065a2.png

       list类模板第一个参数是类型第二个参数是内存池(提高内存申请效率),在学习过程中关注第一个参数即可。

        注意:该类的头文件为<list>,在使用list类模板之前,需要包含这个头文件,例如:

#include <list>

(二)list与string和vector的区别

        list类中访问、遍历与修改的核心方式是使用迭代器并没有【 下标+ [ ] 】的概念(虽然可以实现,但效率极低)因为元素之间的物理空间并不连续,只是逻辑上连续。而string和vector既可以使用迭代器,也可以使用【 下标+ [ ] 】进行访问、遍历与修改。

        相对与string和vector接口,list类模板接口没有扩容的概念了,访问数据也只有访问头节点与尾节点的概念,修改部分接口最主要的就是头插与尾插,头删与尾删;swap操作也是直接交换地址,若直接使用算法库中的swap函数的话会进行深拷贝,效率会低。同时也有list类自身专用的接口,在本篇博客中作介绍。

二、list类使用

(一)构造函数

构造函数( constructor
接口说明
list (size_type n, const value_type& val = value_type())
构造的 list 中包含 n 个值为 val 元素
list ()
构造空的 list
list (const list& x)
拷贝构造函数
list (InputIterator first, InputIterator last)
用  [first, last)  区间中的元素构造 list

(二)迭代器

        可暂时将迭代器理解成一个指针,该指针指向list中的某个节点

函数声
接口说明
begin() + end()
返回第一个元素的迭代器+ 返回最后一个元素下一个位置的迭代器
rbegin() + rend()
返回第一个元素的 reverse_iterator, 即r end 位置返回最后一个元素下一个位
置的 reverse_iterator, 即r begin 位置

        迭代器图解如下:

fab873ac8dc845658414ffab4e6f173f.png

        注意:

        ① begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动;

        ② rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动。

(三)list capacity

函数声明
接口说明
empty
检测 list 是否为空,是返回 true ,否则返回 false
size
返回 list 中有效节点的个数

(四)list element access

函数声明
接口说明
front
返回 list 的第一个节点中值的引用
back
返回 list 的最后一个节点中值的引用

(五)list modifiers

函数声明
接口说明
push_front
在  list  首节点前插入值为 val 的节点
pop_front
删除 list 中第一个节点
push_back
list 尾部插入值为 val 的结点
pop_back
删除 list 中最后一个节点
insert
在  pos  位置中插入值为  val  的节点
erase
删除  pos  位置的节点
swap
交换两个 list 中的节点
clear
清空 list 中的有效节点

emplace_front

类似于头插

emplace_back类似于尾插

1、push_back与emplace_back的区别

        ① push_back的参数是模板类型的引用值,什么类型的list就要传什么类型的值进去,是固定的。传递参数过程如下代码所示:

class Pos
{
public:Pos(int row = 0, int col = 0):_row(row), _col(col){}private:int _row;int _col;
};void test02()
{Pos p1(1, 2);list<Pos> lt1;lt1.push_back(p1);lt1.push_back(Pos(24,36));lt1.push_back({ 24,36 });
}

        先构造出一个Pos对象,然后使用push_back进行尾插,第二、三个push_back函数的参数中构造的是Pos类的匿名对象和隐式转化的临时对象,临时对象有常性,所以push_back的参数使用const value_type& val接收。

        ② emplace_back是一个函数模板,它的可变模板参数可以传多个参数,能够自动识别实参并推导类型;但不能走隐式类型转化,因为传 {3,3} 的话会被自动推导为【用{ }括起来的列表】,因此形参并不知道是什么类型,就会报错,原因是类型不明确。而使用push_back的list已经实例化为Pos类型的对象,所以push_back的形参已经默认为是Pos类型了,可以进行隐式类型转化。

lt1.emplace_back({ 24,36 });//报错

        但可以使用下面这种方法:

lt1.emplace_back( 24,36 );

        可以直接传初始化Pos对象的值,它的可变模板参数会把这两个数识别为 int 整形,会把这两个整形变成参数包往后传,直接初始化节点对象(直接构造),会更加高效。支持传递构造对象的参数。现阶段知道有这样的用法就行。

        也只有emplace_back直接传递构造对象的参数的效率会高,传递对象或匿名对象的时候,效率与push_back没有区别。代码如下所示:

void test03()
{Pos p2(1, 2);list<Pos> lt2;lt2.emplace_back(p2);//构造 + 拷贝构造lt2.emplace_back(Pos(24, 36));//构造 + 拷贝构造//lt2.emplace_back({ 24,36 });这样写会因为类型不明确而报错lt2.emplace_back(24,36);//直接构造,比前面的效率都高
}

        编译器也可能把【匿名对象Pos(24,36)】的参数直接传给节点进行构造,提高效率。

2、注意

        list里面没有find,但可以使用算法库中的find函数,参数为一段迭代区间,没找到就返回迭代空间的最后一个迭代器,如it.end(),找到之后就返回目标节点的位置,可以进行插入或删除操作。

(六)list特有的接口

函数声明接口说明

reverse

逆置

merge

两个链表归并,有序的列表归并后依旧有序,会把参数中的列表拿走进行归并,

归并后内容为空

unique

去重复值,只保留一个,前提为有序,需要提前使用sort进行排序

remove

去除,可以理解为是 find 和 erase 的结合,删除的是迭代器指向位置的数据

remove_if

后面凡是带_if的都是满足了条件的才会进行,参数是仿函数

splice

转移,把一个链表的值转移给另一个链表,参数中被转移的链表完成转移操作后为空。

sort排序(默认升序)

1、splice

        splice可以用来调整链表中节点的顺序,最近最少用缓存算法LRU会使用到:

void test04()
{list<int> lt1 = { 1,2,3,4,5 };//LRUint x;while (cin >> x){auto pos = find(lt1.begin(), lt1.end(), x);if (pos != lt1.end())lt1.splice(lt1.begin(), lt1, pos);//在lt1链表中把pos指向的数据转移到lt1.begin()前面}
}

        中断输入:输入【ctrl+z加换行】或【ctrl+c】结束流,正常结束,后面的程序继续执行。

        注意:linux中【ctrl+c】是杀死程序。

2、sort

        ① 使用list中的sort,排的是升序,可以理解为底层传的是【小于 < 】的概念(仿函数less)。若想要升序们可以sort后使用reverse进行逆置,也可以在底层传【大于 > 】的概念(仿函数greater),这些仿函数都是类模板,支持任意类型的比较。如下代码所示:

void test05()
{list<int> lt1 = { 1,4,5,3,2 };list<int> lt2 = { 1,4,5,3,2 };//排升序lt1.sort();for (const auto& i : lt1){cout << i << " ";}cout << endl;//排降序//greater<int> gt;有名对象少用,匿名对象用得更多一点lt2.sort(greater<int>());for (const auto& i : lt2){cout << i << " ";}cout << endl;
}

        ② 使用vector进行排序,但vector并没有sort这个接口,需要调用算法库中的sort:

void test06()
{vector<int> v1 = { 1,4,5,3,2 };vector<int> v2 = { 1,4,5,3,2 };sort(v1.begin(), v1.end());//升序sort(v2.begin(), v2.end(), greater<int>());//降序}

        那为什么list不用算法库中的sort而要自己实现sort接口?因为list的迭代器不匹配算法库中sort函数参数中迭代器的类型。迭代器在功能角度有以下分类:

  • 单向迭代器只支持++;例如:forward_list(forword是向前的意思) / unoredered_xxx(哈希表系列);
  • 双向迭代器支持++ / --;例如:list / map / set(链表和树);
  • 随机迭代器支持++ / -- / + / - ;例如:string/vector。

        三种迭代器在某种程度上是继承关系随机迭代器是父类(范围最大)双向迭代器是随机迭代器的子类(父类的特殊类,范围略小)单向迭代器是双向迭代器的子类(子类的特殊类,范围最小);那么函数参数支持双向迭代器的,为随机迭代器的类也可以使用,但单向迭代器不可用。

        算法库中的sort函数参数接收的迭代器类型为随机迭代器(底层为快排,可以在c++的参考手册中查询得到函数参数接收的迭代器类型,或者在c++的参考手册中函数参数的标准命名就有暗示),而list迭代器的类型为双向迭代器,所以无法使用算法库中的sort函数,需要自己实现一个sort排序的接口。

        ③ 两种排序的效率对比:

        代码一:直接在vector和list进行sort对比

void test07()
{srand(time(0));//使随机值的种子每次运行都不同const int N = 1000000;list<int> lt1;list<int> lt2;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);v.push_back(e);}int begin1 = clock();sort(v.begin(),v.end());int end1 = clock();int begin2 = clock();lt1.sort();int end2 = clock();cout << "vector 排序时间:" << end1 - begin1 << endl;cout << "list   排序时间:" << end2 - begin2 << endl;}

        在release版本运行的结果:

8ddd62038ba24e388300116415671f16.png

        代码二:把 lt2 的代码传给vector进行排序再把数据传递回来 && 直接在sort进行排序进行对比

void test08()
{srand(time(0));//使随机值的种子每次运行都不同const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);lt2.push_back(e);}int begin1 = clock();vector<int> v1(lt1.begin(), lt1.end());//拷贝vectorsort(v1.begin(), v1.end());//vector排序lt1.assign(v1.begin(), v1.end());//拷贝回lt2int end1 = clock();int begin2 = clock();lt2.sort();//list直接排int end2 = clock();cout << "list copy vector sort copy list sort:" << end1 - begin1 << endl;cout << "list sort:" << end2 - begin2 << endl;
}

        在release版本运行的结果:

cb68e954fcae4523877e80c3bb804074.png

        总结:若是少量数据的话使用list的sort接口进行排序的效率与vector调用算法库的sort接口的效率差不多,但是有大量数据的话vector调用算法库的sort排序效率会快很多

三、list类模拟实现

        成员全部公有的类可以用关键字struct进行声明;成员公私有混合的类用关键字class进行声明。

#pragma once
#include<assert.h>
#include<iostream>
using namespace std;namespace zyb
{//成员全部公有的类可以用关键字struct进行声明//成员公私有混合的类用关键字class进行声明//【节点类模板】//调用该类模板的作用:创建一个有数据但无指向的结点template<class T>struct list_Node{T _date;//存放数据list_Node<T>* _next;//指向前一个结点的指针list_Node<T>* _prev;//指向后一个结点的指针//全缺省做默认构造,new Node()时会自动调用,实现节点的数据初始化//缺省值给T的匿名对象来接收多种类型;//T在list创建时就已经定下list_Node(const T& x = T()):_date(x), _next(nullptr), _prev(nullptr){}};//【节点指针为核心的迭代器类模板】//以【节点模板类的指针】为核心,进行一系列操作符重载,使其符合迭代器的行为template<class T>struct list_Iterator//不进行限定,因为list会不断用到该结构体的成员,也会像节点一样作为隐形封装{//给【节点模板类】另起一个名,意为节点类型using Node = list_Node<T>;//给【迭代器模板类】另起一个名,意为迭代器类型using Self = list_Iterator<T>;Node* _node;//成员变量,是节点类型的指针list_Iterator(Node* node)//使用【节点模板类指针】构造迭代器:_node(node){}T& operator*()//实现迭代器的正确解引用{retrun _node->_date;}//出了该函数_data不会消失,因为节点还存在;//使用引用可以用别名修改_data,达到和指针一样解引用了能够修改指向元素的数据//返回数据本身T* operator->()//实现迭代器的对复合数据类型的正确引用{retrun &_node->_date;//先与成员访问运算符结合,再与取地址符结合;返回的是数据的地址}//返回类型为T的指针类型Self& operator++()//前置++{_node = _node->_next;return *this;//节点还在,可以使用引用返回}Self operator++(int)//后置++{Self tmp(*this);//先备份一个原来位置的指向就行_node = _node->_next;return tmp;}Self& operator--()//前置--{_node = _node->_prev;return *this;//节点还在,可以使用引用返回}Self operator--(int)//后置--{Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s)//判断迭代器是否相等{return _node != s._node;}};//创建list类模板//链表,元素为节点,访问节点中的内容需要正确行为的迭代器template<class T>class list{//默认私有,节点模板类,意为节点类型using Node = list_Node<T>;public://迭代器模板类,意为迭代器类型using iterator = list_Iterator<T>;iterator begin()//定义迭代器的指向{return iterator(_head->_next);}iterator end()//定义迭代器的指向{return iterator(_head);}void empty_init()//对节点进行空初始化{_head = new Node();//new了一个结点的空间,并且调用了结点类模板的默认构造(有数据,无指向)_head->_next = _head;//修改指向_head->_prev = _head;//修改指向}list()//无参的默认构造{//调用节点的空初始化函数创造出一个指向自己的头结点,//数据为T类型的默认构造出来的数据empty_init();}void push_back(const T& x)//插入数据{Node* new_node = new Node(x);Node* tail = _head->_prev;new_node->_prev = tail;tail->_next = new_node;new_node->_next = _head;_head->_prev = new_node;}private:Node* _head;//指向头结点的指针(哨兵位指针)};
}

        以上内容仅供分享,若有错误,请多指正


http://www.ppmy.cn/ops/142623.html

相关文章

day 51 第十章 单调栈part02

第十章 单调栈part02 接雨水 接雨水这道题目是 面试中特别高频的一道题&#xff0c;也是单调栈 应用的题目&#xff0c;大家好好做做。 建议是掌握 双指针 和单调栈&#xff0c;因为在面试中 写出单调栈可能 有点难度&#xff0c;但双指针思路更直接一些。 代码随想录 84.…

AI智算-k8s部署大语言模型管理工具Ollama

文章目录 简介k8s部署OllamaOpen WebUI访问Open-WebUI 简介 Github&#xff1a;https://github.com/ollama/ollama 官网&#xff1a;https://ollama.com/ API&#xff1a;https://github.com/ollama/ollama/blob/main/docs/api.md Ollama 是一个基于 Go 语言开发的可以本地运…

大模型的构建与部署(3)——数据标注

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl1. 数据标注的重要性 1.1 增强数据可解释性 数据标注通过为原始数据添加标签或注释,显著增强了数据的可解释性。在机器学习和深度学习领域,模型的训练依赖于大量带标签的数据。这些标签不仅帮助…

旅游资源系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

跟着问题学19——BERT详解(2)

预训练策略 BERT模型的预训练基于两个任务&#xff1a; 屏蔽语言建模 下一句预测 在深入屏蔽语言建模之间&#xff0c;我们先来理解一下语言建模任务的原理。 语言建模 在语言建模任务中&#xff0c;我们训练模型给定一系列单词来预测下一个单词。可以把语言建模分为两类&…

parseInt 函数

parseInt 在Java中&#xff0c;parseInt()方法是Integer类的一个方法&#xff0c;用于将字符串参数解析为有符号的十进制整数。这个方法非常有用&#xff0c;当你需要从字符串中提取整数值时。例如&#xff0c;如果你有一个数字字符串&#xff0c;你想将其转换为整数以进行数学…

SpringBoot + minio + kkfile 实现文件预览

1、容器安装kkfileviewer 1.1 下载文件 这里以kkfile 4.4.0-beta版本为例 下载kkfile安装包及Dockerfile&#xff1a; https://codeup.aliyun.com/6254dee9a923b68581caaf50/kkfileviewer.git 1.2、构建镜像 git clone https://codeup.aliyun.com/6254dee9a923b68581caaf50…

centos 7.9 freeswitch1.10.9环境搭建

亲测版本centos 7.9系统–》 freeswitch1.10.9 一、下载插件 yum install -y git alsa-lib-devel autoconf automake bison broadvoice-devel bzip2 curl-devel libdb4-devel e2fsprogs-devel erlang flite-devel g722_1-devel gcc-c++ gdbm-devel gnutls-devel ilbc2