C++初阶 | [九] list 及 其模拟实现

摘要:介绍 list 容器,list 模拟实现,list与vector的对比


list(带头双向循环列表)

导入:list 的成员函数基本上与 vector 类似,具体内容可以查看相关文档(cplusplus.com/reference/list/list/),这里不多赘述。以下对 list 的 Operations 部分的函数进行简单讲解。

Operations:

splice

Transfer elements from list to list (public member function)

remove

Remove elements with specific value (public member function)

remove_if

Remove elements fulfilling condition (public member function template)

unique

Remove duplicate values (public member function)

merge

Merge sorted lists (public member function)

sort

Sort elements in container (public member function)

reverse

Reverse the order of elements (public member function)

注意:list 没有扩容的概念,而是一份一份相对独立的节点串连起来的。

1)sort

  • #include<list> std::list::sort#include<algorithm> std::sort
    如上图,RandomAccessIterator 至少已经在名称上提示使用者,这个 sort 函数要求支持能够被随机访问的迭代器
    首先,list 的迭代器是双向迭代器;其次,从底层实现来看,std::sort 函数用到了迭代器相减,而 list 的地址是不连续的。所以 list 不支持使用 std::sort 函数。

  •  std::list::sort 的使用:该函数默认升序排列(底层是归并排序)
    如果要降序排序有如下代码以供参考:(std::greater<int>() 是一个 greater 类型的匿名对象,这种写法更常用)

    #include<functional>
    #include<list>int main()
    {std::list<int> lt;//在 lt 中插入一些数据之后std::greater<int> gt;lt.sort(gt);//or:lt.sort(std::greater<int>());return 0;
    }

  • std::list::sort 性能测试
    测试结果:
    ①在 Rlease 模式下, std::vector::sort 效率大约是 list 的 2 倍,并且数据量越大效率差距越大。(tip.性能测试要在 Rlease 模式下进行,Debug 模式下优化没有全开)
    ②通过 vector 给 list 排序:把 list 对象 → 拷贝数据到 vector 对象中 →对 vector 对象 sort → 把排序好的数据拷贝到 list 。这样对 list 排序,在数据量较大的情况下效率甚至比 list 直接排序要高。

sumlist 的 sort 在性能上没有什么优势,list 中的 sort 函数在对于数据量小的情况下可以使用,但平时能不用尽量不要频繁使用。

2)merge

归并两个 list 到一个 list(要先 sort 才可以 merge,实践中很少用)。

3)unique

去重,但也有要求——只能去除连续相同的,所以要先 sort 再 unique 才可以真正“去重”。

4)splice

转移(移动指针),如下图。

以上就是对 list 一些函数的简单介绍。 


list 的模拟实现

1)结构

如上图,list 中的每个节点是一个自定义类型 Node,对于双向链表,每个节点内包括自身储存的数据、前节点指针和后节点指针。
对于由一个一个节点组成的 list通过头节点来管理整个 list

代码示例

// List的节点类template<class T>struct ListNode{ListNode<T>* _pPre;ListNode<T>* _pNext;T _val;};//List类template<class T>class list{PNode _pHead;//注意:这里是一个内置类型(指针)	};

2)初始化_Constructor

对 list 的初始化首先是对头节点的初始化。

// List的节点类template<class T>struct ListNode{ListNode(const T& val = T()): _val(val), _pPre(nullptr), _pNext(nullptr){}ListNode<T>* _pPre;ListNode<T>* _pNext;T _val;};//List类template<class T>class list{typedef ListNode<T> Node;typedef Node* PNode;public:///// List的构造list(){CreateHead();}private:void CreateHead()//对头结点进行初始化{_pHead = new Node;//这里会去调用struct ListNode的构造函数_pHead->_pNext = _pHead;_pHead->_pPre = _pHead;}PNode _pHead;//注意:这里是一个内置类型(指针)	};

3)Iterator

class Iterator——Iterator类

  1. 成员变量:Node* _pNode
  2. 成员函数:operator* 、operator++ 、operator-- 、operator!= 、operator==(模拟指针的行为)——这里体现了“封装”。封装屏蔽底层差异和实现细节,提供统一的访问修改遍历方式。

代码示例

	//List的迭代器类template<class T>class ListIterator{typedef ListNode<T>* PNode;typedef ListIterator<T> Self;public://constructorListIterator(PNode pNode = nullptr):_pNode(pNode){}ListIterator(const Self& l)//copy constructor{_pNode = l._pNode;}//operationsT& operator*(){return _pNode->_val;}T* operator->(){return &_pNode->_val;}Self& operator++(){_pNode = _pNode->_pNext;return *this;}Self operator++(int){Self tmp = _pNode;_pNode = _pNode->_pNext;return tmp;}Self& operator--(){_pNode = _pNode->_pPre;return *this;}Self operator--(int){Self tmp = _pNode;_pNode = _pNode->_pPre;return tmp;}bool operator!=(const Self& l){return _pNode != l._pNode;}bool operator==(const Self& l){return _pNode == l._pNode;}PNode _pNode;};

对 operator-> 的补充说明

我们知道,对于自定义类型,可以通过对其指针解引用 " *(pointer). " 和 " (pointer)-> " 来访问其成员。而 iterator 实际上是在模拟指针的行为,对于 operator-> 的使用编译器做出了优化。如下图。

3)Const_Iterator 

注意!const_iterator 不是用 const 修饰 iterator,如上 iterator 中的模拟实现可以看出,iterator 底层是原生指针,用 const 修饰 iterator 是使得指针本身不可修改,const_iterator 本身是要能被进行 ++ 和 -- 操作的,否则无法实现遍历;而 const_iterator 针对的是被 const 修饰的 list 的对象,即 const 修饰的是 list 的实例化对象本身(ps. list 对象是 const 的,那储存在节点中的数据肯定也是 const 的,即为 const T)

如上图,实际上我们需要实现两个不同的 iterator —— class ListIteratorclass ListConst_Iterator ,而对于 const 对象,begin 和 end 函数将会返回 const_iterator。

优化:使用类模板实现 List 的 Iterator 类

代码示例

	//List的迭代器类template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T>* PNode;typedef ListIterator<T, Ref, Ptr> Self;public://constructorListIterator(PNode pNode = nullptr):_pNode(pNode){}ListIterator(const Self& l)//copy constructor{_pNode = l._pNode;}//operationsRef operator*(){return _pNode->_val;}Ptr operator->(){return &_pNode->_val;}Self& operator++(){_pNode = _pNode->_pNext;return *this;}Self operator++(int){Self tmp = _pNode;_pNode = _pNode->_pNext;return tmp;}Self& operator--(){_pNode = _pNode->_pPre;return *this;}Self operator--(int){Self tmp = _pNode;_pNode = _pNode->_pPre;return tmp;}bool operator!=(const Self& l){return _pNode != l._pNode;}bool operator==(const Self& l){return _pNode == l._pNode;}PNode _pNode;};//list类template<class T>class list{typedef ListNode<T> Node;typedef Node* PNode;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T&> const_iterator;public:///// List的构造list(){CreateHead();}///// List Iteratoriterator begin(){return _pHead->_pNext;}iterator end(){return _pHead;}const_iterator begin() const{return _pHead->_pNext;}const_iterator end()const{return _pHead;}}

注意:同一个类模板,实例化参数不同,就是完全不同的类型,即对于 ListIterator<T, T&, T*> 和 ListIterator<T, const T&, const T&> 是两个不同的类型。(ps. iterator 和 const_iterator 都实现之后才可以支持使用范围 for)

4)其他成员函数

这些成员函数实现起来思路很简单,有问题建议去看数据结构的文章回顾一下。以下简略说明。

①insert

insert 之后 iterator 不失效,因为没有扩容的影响。

		// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){PNode cur = pos._pNode;PNode newnode = new Node(val);newnode->_pNext = cur;newnode->_pPre = cur->_pPre;cur->_pPre = newnode;newnode->_pPre->_pNext = newnode;return pos;}

②erase

erase 之后 iterator 失效,因为这个被 erase 的节点被释放了,那么指向它的 iterator 也就失效了。

		// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){if (!empty()){PNode next = pos._pNode->_pNext;pos._pNode->_pPre->_pNext = next;next->_pPre = pos._pNode->_pPre;delete pos._pNode;--_size;return next;}return _pHead;}

③push_back and push_front

复用 insert。

		// List Modifyvoid push_back(const T& val) { insert(end(), val); }void push_front(const T& val) { insert(begin(), val); }	

④pup_back and pop_front

复用 erase。

		// List Modifyvoid pop_back() { erase(--end()); }void pop_front() { erase(begin()); }

⑤clear

用 iterator 遍历,依次 erase 每个节点。

		void clear(){iterator it = begin();while (it != end()){it = erase(it);}}

⑥Destructor

clear → delete → nullptr,即清理 list,释放头节点,头结点指针指针置空。

		//destructor~list(){clear();delete _pHead;_pHead = nullptr;}

⑦Copy Constructor

范围 for 循环 push_back。(注意:使用范围 for 需要把 const_iterator 也实现了才能用)

		list(const list<T>& l)//copy constructor{CreateHead();for (auto e : l){push_back(e);}}

⑧赋值重载

		//assignlist<T>& operator=(list<T> l){if (_pHead != l._pHead){swap(l);return *this;}}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}

⑨其他构造函数重载

		list(int n, const T& value = T()){CreateHead();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();Iterator it = first;while (it != last){push_back(*it);++it;}}

补充:list 的成员变量中可以加一个 size_t 类型的变量来记录节点个数,因为如果没有这个成员变量就需要遍历来获取有效数据个数,效率比较低。(提醒:如果增加了 size_t 类型的成员变量记得在 insert 和 erase 的函数实现中相应地做出调整)

5)补充:Print

针对于 list<int> / list<char> 等类型的打印函数很好实现,以下我们尝试写出更通用的打印函数。

打印 list<T> 而不只是针对某个具体的 T 类型

因为语法编译之前要先对模板进行实例化,对于 Btl::list<T>::const_iterator 由于模板没有被实例化,所以编译器不知道 const_iterator  list<T> 中的一个内嵌类型还是静态成员变量,这样的行为对于编译器是未知的。

所以,Btl::list<T>::const_iterator 前加 typename 来声明这是一个内嵌类型。代码如下。

template<typename T>
void print_l(const Btl::list<T>& _list)
{typename Btl::list<T>::const_iterator it = _list.begin();while (it != _list.end()){std::cout << *it;++it;}std::cout << std::endl;
}

打印任意容器

提醒:下列代码中要求 *it 支持流插入。

template<typename Container>
void print_l(const Container& _con)
{typename Container::const_iterator it = _con.begin();while (it != _con.end()){std::cout << *it;++it;}std::cout << std::endl;
}

回顾:vector模拟实现中涉及的深浅拷贝的问题

对于类似 vector<string> 而出现的深浅拷贝问题,因为 list 不涉及扩容的概念,所以不会出现深浅拷贝的问题。


list与vector的对比

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素的效率为O(1)不支持随机访问,访问某个元素的效率为O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素(挪动数据),时间复杂度为O(N),插入时有可能需要增容——开辟新空间,拷贝元素,释放就空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层结点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效

在插入元素时,要给所有迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效;删除时,当前迭代器需要重新给赋值否则会失效

插入元素不会导致迭代器失效;删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关系插入删除效率大量插入和删除操作,不关心随机访问

完整代码链接My_List/My_List/My_List.h · fantansy-13-07/Cpp - 码云 - 开源中国 (gitee.com)


END

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

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

相关文章

Linux中使用vim编辑器的时候提示:发现交换文件“XXX.swap“

目录 问题描述解决方案 问题描述 有时候vim编辑文件的时候 系统突然卡了 无奈只能强制退出关机 然后重启 这个时候想重新回来继续编辑刚刚的文件 注意:我这种操作方式 虽然之后可以继续正常编辑了 但是发现文件是没有保存的 vim XXX进去有如下提示 解决方案 注意:我这种操作…

基于python的变配电室运行状态评估与预警系统flask-django-nodejs-php

近年来,随着我国工业化、城镇化步伐的不断加快&#xff0c;城市配电网络取得令人瞩目的发展成果。变配电室是供配电系统的核心&#xff0c;在供配电系统中占有特殊的重要地位[1]。变配电室电气设备运行状态和环境信息缺乏必要的监测评估预警手段&#xff0c;如有一日遭遇突发情…

Linux-线程同步

文章目录 前言一、为什么要线程同步&#xff1f;二、线程同步pthread_cond_initpthread_cond_destroypthread_cond_wait、pthread_cond_signal和 pthread_cond_broadcast 三、示例代码 前言 上节课学习了线程互斥&#xff0c;这节课针对线程互斥内容在做进一步的补充和完善&am…

[C语言]一维数组二维数组的大小

对于一维数组我们知道取地址是取首元素的地址&#xff0c;二维数组呢&#xff0c;地址是取第一行的地址&#xff0c;sizeof(数组名)这里计算的就是整个数组的大小&#xff0c;&数组名 表示整个数组&#xff0c;取出的是整个数组的地址&#xff0c;显示的是数组的首元素 记…

网络工程师练习题2

网络工程师 将专用IP地址转换为公用IP地址的技术是&#xff08;&#xff09;。 A.ARPB.DHCPC.UTMD.NAT 【答案】D 【解析】概念题&#xff0c;NAT技术将源地址从内部专用地址转换成可以在外部Internet上路由的全局IP地址。 R1、R2是一个自治系统中采用RIP路由协议的两个相…

【计算机网络篇】物理层(4)信道的极限容量,信道复用技术

文章目录 &#x1f354;信道的极限容量&#x1f6f8;造成信号失真的主要因素⭐码元的传输速率 &#x1f6f8;奈氏准则&#x1f6f8;香农公式&#x1f388;练习 &#x1f5d2;️小结 &#x1f354;信道复用技术⭐常见的信道复用技术&#x1f388;频分复用FDM&#x1f388;时分复…

时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解

时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解 目录 时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.CEEMDAN方法的分解效果取决于白噪声幅值权重(Nstd)和噪声添…

【刷题】滑动窗口入门

送给大家一句话&#xff1a; 那脑袋里的智慧&#xff0c;就像打火石里的火花一样&#xff0c;不去打它是不肯出来的。——莎士比亚 滑动窗口入门 认识滑动窗口Leetcode 209. 长度最小的子数组题目描述算法思路 Leetcode 3. 无重复字符的最长子串题目描述算法思路 Leetcode 1004…

部署应用到K8s集群(未完)

&#xff08;等熟悉一番再来写&#xff0c;因为按小时结算的。。。&#xff09; 1 、 kubectl run 启动 nginx 应用 kubectl run nginx --imagenginx:latest 2、将本地机器的80端口转发到集群中名为nginx的Pod的80端口 kubectl port-forward --address 0.0.0.0 pod/nginx 80:8…

AHI对MySQL性能的影响

MySQL中出现很多latch锁&#xff0c;而这个很大程度上和MySQL自适应hash索引有关。 AHI概述 MySQL InnoDB存储引擎的自适应哈希&#xff08;Adaptive Hash Index&#xff0c;下简称AHI&#xff09;功能 若用户的访问模式基本都是类似KV操作的点查询&#xff08;point select&…

es bulk批量操作简单实例

&#xff08;1&#xff09;定义 bulk允许在单个步骤中进行多次create、index、update或delete请求。 bulk与其他的请求体格式稍有不同&#xff0c;如下所示&#xff1a; { action: { metadata }}\n { request body }\n { action: { metadata }}\n { request body …

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博主所有FPGA工程项目-->汇总目录本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收OSD动态字符叠加输出应用本方案的SDI接收HLS…

【CSS】html滚动条相关

1.滚动条样式 ::-webkit-scrollbar {width: 10px;height: 10px;z-index: 101; } ::-webkit-scrollbar-thumb {border-radius: 5px;background: #cecece; } ::-webkit-scrollbar-track {// background: #f5f5f5be;background: rgba(33, 85, 163, 0); } ::-webkit-scrollbar-but…

python汽车租赁系统的设计与实现flask-django-php-nodejs

困扰公司的许多问题当中,汽车租赁管理一定是公司不敢忽视的一块。但是管理好汽车租赁又面临很多麻烦需要解决,例如有几个方面:第一,公司往往汽车数量都比较多,如何保证能够管理到每一汽车;第二,如何在工作琐碎,记录繁多的情况下将汽车租赁的当前情况反应给公司领导相关部门决策…

Python之Web开发中级教程----ubuntu中下载安装Postman

Python之Web开发中级教程----ubuntu中下载安装Postman PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件&#xff0c;可以直接去对我们写出来的路由和视图函数进行调试&#xff0c;作为后端程序员是必须要知道的一个工具。 查看ubuntu系统中是否已经安装了…

常用小知识点总结

1. pc可以跑通&#xff0c;但是安卓编译死循环&#xff0c;可能是函数声明了返回类型&#xff0c;但是没有真正返回 2. ubuntu下根据关键词杀死所有相关进程。ps -ef | grep code | grep -v grep | cut -c 10-16 | xargs kill -s 9 top和ps基本作用都是显示系统进程状况&…

matplotlib绘图一例

网上外卖用户规模及使用率import matplotlib.pyplot as plt import matplotlib.dates as mdates import pandas as pd# 数据源自www.cnnic.net.cn。 l [["2015/12", "1.14亿", "16.5%"], ["2016/6", "1.50亿", "21.1%…

electron-builder 打包问题,下载慢解决方案

目录 问题说明设置下载源 &#xff1f;解决方案思路下载Electron下载winCodeSign下载nsis下载nsis-resources 总结 问题说明 项目使用了Electron&#xff0c;在第一次打包时会遇见下载慢&#xff0c;导致打包进度几乎停滞不前&#xff0c;甚至可能直接报错 其实这是因为Electr…

套的-流量分析

目录 1、流量分析2、寻找flag3、总结 1、流量分析 把流量包下载下来进行分析&#xff0c;得到下面这些信息 通过追踪HTTP流&#xff0c;我们可以很明显的感觉到是对42.193.4.49进行目录爆破 追踪流给出的信息是不完整的&#xff0c;我们只是用来推测大概的过程&#xff0c;了解…

在电脑桌面上怎么制作可视化工作计划待办清单?

对于忙碌的上班族来说&#xff0c;每天都需要处理大量的工作任务&#xff0c;如何在繁杂的工作中保持高效和有序&#xff0c;成为了一个亟待解决的问题。在电脑桌面上制作可视化的工作计划待办清单&#xff0c;就是一个非常实用的方法。通过将工作任务以清单的形式展现在桌面上…