40 list类 模拟实现

news2024/12/14 7:19:18

目录

一、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容器的特点是它是一个带头双向循环链表,这意味着每个节点包含数据以及指向前一个节点和后一个节点的指针。具体声明如下:

       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 位置

        迭代器图解如下:

        注意:

        ① 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 };
    //LRU
    int 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版本运行的结果:

        代码二:把 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());//拷贝vector
    sort(v1.begin(), v1.end());//vector排序
    lt1.assign(v1.begin(), v1.end());//拷贝回lt2
    int 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版本运行的结果:

        总结:若是少量数据的话使用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.coloradmin.cn/o/2259183.html

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

相关文章

小程序开发常见问题

一、开发方案选择 1.小程序原生开发&#xff1a;需要live-player组件资质&#xff0c;对开发者的资质要求较高&#xff0c;开发难度较大&#xff1b; 2.小程序内嵌H5&#xff1a;因校验文档上限&#xff0c;目前平台已经不支持配置校验文件&#xff1b; 3.半屏小程序&#xff1…

02. Docker:安装和操作

目录 一、Docker的安装方式 1、实验环境准备 1.1 关闭防火墙 1.2 可以访问网络 1.3 配置yum源 2、yum安装docker 2.1 安装docker服务 2.2 配置镜像加速 2.3 启动docker服务 3、二进制安装docker 3.1 下载或上传安装包并解压 3.2 配置使用systemctl管理 3.3 配置镜像…

uniapp 开发小程序 准备工作

前言&#xff1a; 在本地使用vue2.0 实现网页版拍照功能&#xff0c;本地开发老是报错 NotFoundError: Requested device not found &#xff0c;原因是 浏览器主要用于处理网络协议&#xff08;如 HTTP、HTTPS&#xff09;相关的数据交互&#xff0c;它的运行环境被设计为相对…

vue3 封装request请求

vue3 原生请求封装 我这里用一个案例来解释 需求:把vue3原生的静态页 集成到 vue2 的若依项目 并且可以访问 vue2接口 在vue3 src 下的 utils 下 创建文件request.ts文件 import axios from "axios"; import { showMessage } from "./status"; // 引入状态…

Java安全—SpringBootActuator监控泄露Swagger自动化

前言 今天依旧是SpringBoot框架&#xff0c;估计还要一篇文章才能把它写完&#xff0c;没办法&#xff0c;Java安全的内容太多了。 Actuator SpringBoot Actuator模块提供了生产级别的功能&#xff0c;比如健康检查&#xff0c;审计&#xff0c;指标收集&#xff0c;HTTP跟踪…

质数的和与积

质数的和与积 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 两个质数的和是S&#xff0c;它们的积最大是多少&#xff1f; 输入 一个不大于10000的正整数S&#xff0c;为两个质数的和。 输出 一个整…

如何用VScode恢复误删文件-linux

如果你用vscode远程在服务器上办公&#xff0c;有一天你用了&#xff1a; rm -rf *然后你发现你的文件不见了&#xff0c;不要着急。 这种方法只适用于不多的几个文件。 这个时候你要做的是&#xff0c;查看右侧的1&#xff1a; 从1里面查找你删除的文件&#xff0c;然后点…

Linux文件操作基础

1.引入 在Linux第一章提到过&#xff0c; 在Linux中&#xff0c;一切皆文件&#xff0c;而文件由文件内容和文件属性组成&#xff0c;在C语言中可以 使用相应的接口打开文件&#xff0c;例如 fopen 函数 文件最开始在磁盘中&#xff0c;但是因为磁盘的速度远低于CPU的执行速度…

如何在 MacOS 上安装 Flutter ?

Flutter 是 Google 开发的一个功能强大的框架&#xff0c;它允许开发人员从单个代码库构建高质量的、本机编译的移动、web 和桌面应用程序。如果你是初次接触 Flutter&#xff0c;并希望在你的 macOS 系统上安装它&#xff0c;本指南将引导你一步一步地完成这个过程。 Step 1:…

【2025最新计算机毕业设计】基于SpringBoot+Vue社区医院挂号健康服务平台【提供源码+答辩PPT+文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

Docker安装MySQL5.5版本数据库(图文教程)

本章教程,介绍如何使用Docker安装MySQL低版本5.5版本的数据库。 一、拉取镜像 docker pull mysql:5.5二、启动容器 docker run -d --name mysql5.5 --restart=always

可视化建模与UML《部署图实验报告》

一、实验目的&#xff1a; 1、熟悉部署图的基本功能和使用方法。 2、掌握使用建模工具软件绘制部署图的方法 二、实验环境&#xff1a; window11 EA15 三、实验内容&#xff1a; 根据以下的描述&#xff0c;绘制部署图。 网上选课系统在服务器端使用了两台主机&#xff0c;一…

Python的3D可视化库【vedo】2-3 (plotter模块) 增删物体、控制相机

文章目录 4 Plotter类的方法4.3 渲染器内的物体操作4.3.1 添加物体4.3.2 移除物体4.3.3 渲染器的内容列表 4.4 相机控制4.4.1 访问相机对象4.4.2 重置相机状态4.4.3 移动相机位置4.4.4 改变相机焦点4.4.5 改变相机朝向的平面4.4.5 旋转相机4.4.6 对齐相机的上朝向4.4.7 缩放 pl…

07篇(附)--仿射变换矩阵

此篇献给某些 头铁 的小只因们&#xff0c;认真钻研下面的数学式吧 原理示例 首先我们以最简单的一个点的旋转为例子&#xff0c;且以最简单的情况举例&#xff0c;令旋转中心为坐标系中心O&#xff08;0&#xff0c;0&#xff09;&#xff0c;假设有一点P0(x0,y0)&#xff0…

Unity屏幕截图、区域截图、读取图片、WebGL长截屏并下载到本地jpg

Unity屏幕截图、区域截图、读取图片、WebGL长截屏并下载到本地jpg 一、全屏截图并保存到StreamingAssets路径下 Texture2D screenShot;//保存截取的纹理public Image image; //显示截屏的Imagepublic void Jietu(){StartCoroutine(ScrrenCapture(new Rect(0, 0, Screen.width…

【2025最新计算机毕业设计】基于SprintBoot+Vue乡村振兴民宿管理系统【提供源码+答辩PPT+文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

在Docker中运行MySQL的思考:挑战与解决方案

引言 在云计算和容器化技术日益普及的今天&#xff0c;Docker作为一种轻量级的容器化平台&#xff0c;已经成为开发和部署应用的首选工具之一。其提供的便携性、可扩展性和环境一致性对于无状态微服务来说无疑是巨大的福音。然而&#xff0c;并非所有应用都适合在Docker容器中…

【Linux网络】网络基础:传输层TCP协议(二)

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;Linux “ 登神长阶 ” &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 传输层UDP/TCP协议 确认应答机制超时重传机制连接管理机制理解 TIME_WAIT 状态 滑动窗口流量…

virtualbox 搭建ubuntu

环境&#xff1a;VirtualBox-6.1.32 1、下载安装virtualbox 略 2、新建ubuntu 3、配置ubuntu 选择虚拟盘 4、安装ubuntu 5、安装ssh sudo apt install openssh-server openssh-client 查看ip 6、安装samba sudo apt install samba 查看ssh启动状态 sudo systemctl stat…

智能人体安全防护:3D 视觉技术原理、系统架构与代码实现剖析

随着工业化程度的提高&#xff0c;生产安全已成为企业关注的重点。尤其是在一些存在禁区的工业厂区和车间&#xff0c;人员误入或违规进入将带来严重的安全隐患。为了解决这一问题&#xff0c;迈尔微视推出了智能人体安全检测解决方案&#xff0c;为企业提供全方位的人员安全监…