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

       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;//指向头结点的指针(哨兵位指针)
	};
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3监听横向滚动条的位置;鼠标滚轮滑动控制滚动条滚动;监听滚动条到顶端

1.横向取值scrollLeft 竖向取值scrollTop 2.可以监听到最左最右侧 3.鼠标滚轮滑动控制滚动条滚动 效果 <template><div><div class"scrollable" ref"scrollableRef"><!-- 内容 --><div style"width: 2000px; height: 100…

【大模型】ChatGPT 创作各类高质量文案使用详解

目录 一、前言 二、ChatGPT文案创作的优势 三、ChatGPT 各类文案创作操作实战 3.1 ChatGPT创作产品文案 3.1.1 ChatGPT创作产品文案基本思路 3.1.2 ChatGPT 创作产品文案案例一 3.1.2.1 操作过程 3.1.3 ChatGPT 创作产品文案案例二 3.2 ChatGPT 创作视频脚本 3.2.1 Ch…

每日一刷——二叉树的构建——12.12

第一题&#xff1a;最大二叉树 题目描述&#xff1a;654. 最大二叉树 - 力扣&#xff08;LeetCode&#xff09; 我的想法&#xff1a; 我感觉这个题目最开始大家都能想到的暴力做法就是遍历找到数组中的最大值&#xff0c;然后再遍历一遍&#xff0c;把在它左边的依次找到最大…

蓝桥杯刷题——day2

蓝桥杯刷题——day2 题目一题干题目解析代码 题目二题干解题思路代码 题目一 题干 三步问题。有个小孩正在上楼梯&#xff0c;楼梯有n阶台阶&#xff0c;小孩一次可以上1阶、2阶或3阶。实现一种方法&#xff0c;计算小孩有多少种上楼梯的方式。结果可能很大&#xff0c;你需要…

中粮凤凰里共有产权看房记

中粮凤凰里看房是希望而来&#xff0c;失望而归。主要是对如下失望&#xff0c;下述仅个人看房感受&#xff1a; 1. 户型不喜欢&#xff1a;三房的厨房和餐厅位置很奇葩 2. 样板间在25楼&#xff1a;湖景一言难尽和有工厂噪声 3. 精装修的交房质量:阳台的推拉门用料很草率 …

负载均衡和tomcat

一、负载均衡 1.相关概念 nginx的反向代理<-->负载均衡 负载均衡 将四层或者是七层的请求分配到多台后端的服务器上&#xff0c;从而分担整个业务的负载。提高系统的稳定性&#xff0c;也可以提供高可用&#xff08;备灾&#xff0c;其中的一台后端服务器如果发生故障…

电子电工一课一得

首语 在现代社会中&#xff0c;电子电工技术已经渗透到我们生活的方方面面&#xff0c;从家用电器到工业自动化&#xff0c;从通信设备到智能系统&#xff0c;无一不依赖于电子电工技术。因此&#xff0c;掌握电子电工的基础知识&#xff0c;不仅对理工科学生至关重要&#xf…

Pyside6 --Qt设计师--简单了解各个控件的作用之:Buttons

目录 一、BUttons1.1 包含1.2 不同按钮的解释 二、具体应用2.1 Push Button2.2 Tool Button2.3 Radio Button2.4 Check Box2.5 Command Link Button2.6 Dialog Button Box2.6.1 直接显示代码如下2.6.2 可以修改ok&#xff0c;cancel 的内容 今天学习一下工具箱里面的Buttons&am…

网络基础 - TCP/IP 五层模型

文章目录 一、OSI 参考模型中各个分层的作用1、应用层2、表示层3、会话层4、传输层5、网络层6、数据链路层7、物理层 一、OSI 参考模型中各个分层的作用 1、应用层 2、表示层 负责设备固有数据格式和网络标准数据格式间的转换 3、会话层 4、传输层 负责连接的建立和断开&…

【git】git回退到之前版本+拓展git命令

一、问题 git提交有时候会出错&#xff0c;想回退到之前的版本 1、命令git reset --soft <commit_id> commit_id【回退到的编号】 2、git push --force-with-lease origin <branch_name> branch_name【分支名】 二、拓展 1、git bash 1、进入任意磁盘 cd 磁盘…

Tomcat项目本地部署

不依赖idea部署本地项目&#xff0c;这里使用哈米音乐为例。 哈米音乐项目为聚合项目&#xff0c;ham-parent为父模块&#xff0c;其余为子模块。 ham-console:后台模块 ham-core:公共模块 ham-file:图片模块 ham-portal:前台模块 需要将这些模块进行打包&#xff0c;点击右侧…

【数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;实现二路归并算法。 测试说明 平台会对你编写的代码进行测试&#xff1a; 测试输入示例&#xff1a; 11 18 2 20 34 12 32 6 16 5 8 1 (说明&#xff1a;第一行是元…

东方明珠生成式人工智能媒体融合创新平台荣获AI Cloud轻量云典型案例

近日&#xff0c;由全球数字经济大会组委会主办&#xff0c;中国信息通信研究院&#xff08;以下简称“信通院”&#xff09;、中国通信企业协会承办的2024全球数字经济大会云AI计算国际合作论坛在北京成功召开。会上隆重发布了2024年“AI Cloud助力大模型场景化和工程化落地”…

高阶数据结构--B树B+树实现原理B树模拟实现--Java

目录 一、B-树概念 二、B-树插入分析 1.用序列{53, 139, 75, 49, 145, 36, 101}构建B树的过程如下&#xff1a; 2.插入过程总结 三、B树插入实现 四、B树 1.B树概念 2.B树的特性 五、B树应用 1.索引 2.Mysql索引 3.InnoDB 一、B-树概念 1970 年&#xff0c; R.Bayer 和…

优选算法——分治(快排)

1. 颜色分类 题目链接&#xff1a;75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09; 题目展示&#xff1a; 题目分析&#xff1a;本题其实就要将数组最终分成3块儿&#xff0c;这也是后面快排的优化思路&#xff0c;具体大家来看下图。 这里我们上来先定义了3个指针&…

Edge SCDN的独特优势有哪些?

强大的边缘计算能力 Edge SCDN&#xff08;边缘安全加速&#xff09;是酷盾安全推出的边缘集分布式 DDoS 防护、CC 防护、WAF 防护、BOT 行为分析为一体的安全加速解决方案。通过边缘缓存技术&#xff0c;智能调度使用户就近获取所需内容&#xff0c;为用户提供稳定快速的访问…

「Mac玩转仓颉内测版45」小学奥数篇8 - 排列组合计算

本篇将通过 Python 和 Cangjie 双语讲解如何计算排列与组合。这道题目旨在让学生学会使用排列组合公式解决实际问题&#xff0c;并加深对数学知识和编程逻辑的理解。 关键词 小学奥数Python Cangjie排列与组合 一、题目描述 编写一个程序&#xff0c;计算从 n 个不同元素中取…

基于Q-Learning的机器人栅格地图路径规划,可以更改地图大小及起始点,可以自定义障碍物,MATLAB代码

基于Q-learning算法的栅格地图路径规划是一种利用强化学习技术来解决路径规划问题的方法。 状态空间定义&#xff1a;在路径规划任务中&#xff0c;状态通常代表机器人或智能体在环境中的位置。状态空间可以是离散的&#xff0c;如网格地图上的特定位置。 动作空间定义&#x…

中电金信携手中远海科,共启贸易金融数智新篇章

在数智化转型成为驱动经济社会高质量发展的新引擎背景下&#xff0c;“数智方案”栏目聚焦金融等国计民生重点行业场景&#xff0c;依托中电金信“源启筑基咨询引领应用重构”的产品及服务体系&#xff0c;输出市场洞察和行业解决方案、应用案例&#xff0c;旨在全面推动行业IT…

抗DDOS设备

0x00 定义: 抗DDOS设备顾名思义&#xff0c;就是防御DDoS攻击的设备&#xff0c;通常包含三个部分&#xff1a;检测中心、清洗中心和管理中心 检测中心主要负责对流量进行检测&#xff0c;发现流量异常后上报管理中心&#xff0c;由管理中心下发引流策略至清洗中心&#xff0…