40 list类 模拟实现

目录

一、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.rhkb.cn/news/489683.html

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

相关文章

迎接全新的 Kotlin 支持 – K2 模式:基本信息

K2 模式有什么作用&#xff1f; K2 模式是 IntelliJ IDEA 中 Kotlin 支持的新实现&#xff0c;它可以提高 IDE 的稳定性&#xff0c;同时也会为支持未来 Kotlin 语言功能奠定基础。 K2 模式与 Kotlin K2 编译器有什么区别&#xff1f; K2 编译器负责编译 Kotlin 语言 2.0 或…

openGauss开源数据库实战二十四

文章目录 任务二十四 openGaussss WAL管理和归档管理任务目标实施步骤一、WAL管理1.不能修改的WAL参数2.可以修改的WAL参数 二、配置openGauss工作在归档模式1.查看当前的归档设置2.停止openGauss数据库3.创建归档日志的保存目录4.修改启动参数文件5.重新启动openGauss数据库6.…

docker 安装mysql 5.7 详细保姆级教程

1. 安装mysql(5.7) docker pull mysql:5.7 若是拉取不了&#xff0c;可以配置下 docker 源 2. 查看是否安装成功 docker images 下图就是成功了 3.创建mysql专用目录、数据挂载目录、配置文件目录 &#xff0c;演示目录在于/home/下 //命令逐条执行cd /home/ mkdir mysql …

scale index的计算

scale index定义 基本实现 需要注意&#xff0c;scale index的提出者分别构建了MATLAB和R语言的实现方式。 但是&#xff0c;需要注意&#xff0c;经过我向作者求证。 MATLAB编写的代码已经“过时了”&#xff0c;为了拥抱时代&#xff0c;作者构建了R语言包&#xff0c;名称为…

The Rise and Potential of Large Language ModelBased Agents:A Survey---摘要、背景、引言

题目 基于大语言模型的Agent的兴起与发展前景 论文地址&#xff1a;https://arxiv.org/pdf/2309.07864.pdf 项目地址&#xff1a;https:/github.com/WooooDyy./LLM-Agent–Paper-List 摘要 长期以来&#xff0c;人类一直在追求等同于或超越人类水平的人工智能(A)&#xff0c;…

西门子200 smart PLC助力水处理企业自动化改造

摘要 西门子的200SMART PLC&#xff0c;以其强大的功能和灵活的应用性&#xff0c;正成为环保行业中不可或缺的一环。今天&#xff0c;我们就来看看这个小小的PLC是如何在处理环保问题中大显身手的。 不得不说&#xff0c;环保行业的痛点可不少。 比如污水处理&#xff0c;传…

运维实战:K8s 上的 Doris 高可用集群最佳实践

今天我们将深入探讨&#xff1a;&#xff1a;如何在 K8s 集群上部署 Compute storage coupled&#xff08;存算耦合&#xff09; 模式的 Doris 高可用集群&#xff1f; 本文&#xff0c;我将为您提供一份全面的实战指南&#xff0c;逐步引导您完成以下关键任务&#xff1a; 配…

意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功

意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功 我们在开发AI-Agent智能体时&#xff0c;通常会使用提示词工程设置场景的带入&#xff0c;在实际项目中会有很多场景&#xff0c;如果所有提示词都放一起就会超过Token限制&#xff0c;则不得不拆…

网管平台(基础篇):路由器的介绍与管理

路由器简介 路由器&#xff08;Router&#xff09;是一种计算机网络设备&#xff0c;它的主要作用是将数据通过打包&#xff0c;并按照一定的路径选择算法&#xff0c;将网络传送至目的地。路由器能够连接两个或更多个网络&#xff0c;并根据信道的情况自动选择和设定路由&…

开发者工具的模块化与可扩展性设计

文章目录 前言模块化设计的重要性可扩展性设计的重要性设计模式与技术实现实战代码插件管理器类&#xff1a;PluginManager注册插件方法&#xff1a;register_plugin执行插件方法&#xff1a;execute_plugin 插件实现插件 1&#xff1a;代码格式化插件插件 2&#xff1a;代码行…

杭州乘云联合信通院发布《云计算智能化可观测性能力成熟度模型》

原文地址&#xff1a;杭州乘云联合中国信通院等单位正式发布《云计算智能化可观测性能力成熟度模型》标准 2024年12月3日&#xff0c;由全球数字经济大会组委会主办、中国信通院承办的 2024全球数字经济大会 云AI计算创新发展大会&#xff08;2024 Cloud AI Compute Ignite&…

WordPress酱茄主题 开源版 博客资讯自媒体网站模板

一款免费开源的WordPress主题&#xff0c;主题专为WordPress博客、资讯、自媒体网站而设计 运行环境 支持WordPress版本&#xff1a;5.6 兼容Chrome、Firefox、Safari等主流浏览器 支持设备&#xff1a;响应式布局&#xff0c;不同设备不同展示效果 服务器环境建议&#x…

16-在线blog发布系统

【技术栈】 ①&#xff1a;架构: B/S、MVC ②&#xff1a;系统环境&#xff1a;Windows/Mac ③&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql ④&#xff1a;技术栈&#xff1a;Java、Mysql、SpringBoot、Mybatis、Vue、Redis 【功能模块】 ①&#xff1a;登…

从0到1实现项目Docker编排部署

在深入讨论 Docker 编排之前&#xff0c;首先让我们了解一下 Docker 技术本身。Docker 是一个开源平台&#xff0c;旨在帮助开发者自动化应用程序的部署、扩展和管理。自 2013 年推出以来&#xff0c;Docker 迅速发展成为现代软件开发和运维领域不可或缺的重要工具。 Docker 采…

1. 机器学习基本知识(2)——机器学习分类

1.4 机器学习分类 1.4.1 训练监督 1. 监督学习&#xff1a;已对训练数据完成标记 分类&#xff1a;根据数据及其分类信息来进行训练&#xff0c;使模型能够对新的数据进行分类 回归&#xff1a;给出一组特征值来预测目标数值 2. 无监督学习&#xff1a;没有对训练数据进行任…

快速将请求头构建成json结构

1.背景 有时候我们要爬虫(组包)请求一个资源数据,需要构建与原始请求一样的请求头,从浏览器复制过来的请求头,有很多,如果一个一个的配置成json有点慢,那么如何快速构建呢? 今天就使用正则表达式的方式实现 正则表达式实现快速将请求头构建成json结构 将冒号后边的换行符去掉…

【蓝桥杯备战】Day 1

1.基础题目 LCR 018.验证回文串 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s "A man, a plan, a canal: Panama…

在 Ubuntu 24.04.1 LTS (WSL) 中使用 openssl 生成 keybox.xml

看到“生成 keybox.xml”&#xff0c;大概率都会联想到 PIF 和 Tricky Store。这里就不多解释它们的用途了。最近在网上看到生成非 AOSP keybox 的教程&#xff0c;在这里做一些补充&#xff0c;并将代码打包成一个 Python 脚本。 参考自&#xff1a; Idea 提供者&#xff1a…

哈夫曼树选择题

1. 哈夫曼树的构建方法 哈夫曼树是通过不断合并权值最小的两个节点生成的&#xff1a; 将权值最小的两个节点合并成一个新节点&#xff0c;权值为这两个节点权值之和。将新节点加入队列&#xff0c;并从队列中移除已合并的两个节点。重复以上步骤&#xff0c;直到所有节点合并…

Java 实现给pdf文件指定位置盖章功能

Java 实现给pdf文件指定位置盖章功能 开发中遇到一个需求, 需要给用户上传的的pdf文件, 指定位置上盖公章的功能, 经过调研和对比, 最终确定实现思路. 这里是使用pdf文件中的关键字进行章子的定位, 之所以这样考虑是因为如果直接写死坐标的话, 可能会出现因pdf大小, 缩放, 盖章…