当前位置: 首页 > news >正文

list类

1. list的介绍及使用

    list和之前容器介绍的一样,第二个参数是内存池,它的底层是一个带头双向循环链表。它的使用很便捷,来看看构造:

有无参的、用n个val、迭代器区间、拷贝构造,发现和之前学过的容器的用法很相似。list的接口中没有resize、reserve这样的说法,因为它不涉及扩容了,不用提前开空间。list里面也提供了一些新的接口:

下面来简单使用一下list:

这里遍历不能用下标加方括号,因为不是连续的空间了,所以迭代器才是通用玩法。再看看insert,从string以后insert都变了,string部分是以下标去插入,现在不是下标,都给的是迭代器:

但它和vector还是有些差别的,比如想从第5个位置插入数据,如果是vector应该是这样写:v.insert(v.begin()+5,10),如果是list不能lt.insert(it.begin()+5,10)这样写。因为vector是连续的物理空间,可以通过加加到第5个位置,但它插入时的代价大,需要把数据都挪走;list物理空间不是连续的,虽然要想实现也可以支持,但代价太大了。list想要再第5个位置插入值可以这样写:

再如list想从3前面插入30,但不知道3在哪个位置,所以可以想到用find,但list没提供find,因为stl有两大组件,容器和算法,外部通常不能访问容器数据,因为是私有的,stl就通过迭代器把它们联合起来,这样公共的一些算法可以提取出来。迭代器没有暴露容器底部细节:

通过统一的方式访问容器,不需要关注底层实现。所以迭代器是优秀的设计,还有个细节是不是it<end而是!=,因为对不连续的空间没法用小于参考。因此算法写了通用的可以用算法中的:

可以在给的迭代器区间查val,算法中只要满足迭代器不管底层怎样都是支持的,而且迭代器设计是左闭右开,找不到返回last。下面体验一下:

那链表的insert有没有迭代器失效问题?没有,因为链表没有扩容野指针问题,也没有挪数据位置意义改变的问题。那erase有吗?

erase可以删除某个位置,删除一段区间。erase肯定有迭代器失效的问题,因为结点都没有了。那如果erase要持续的删除呢?通过接收返回值,返回值指向被删除元素的下一个位置(比如删偶数):

swap就是两个链表直接换,clear就是把结点都清了。链表的相关操作中单独针对性的提供了一些操作,有reverse,但这个接口库中也有,感觉实现了没有意义,反正逻辑实现是一样的:

再来看看sort,库中也有sort,用一下:

但编译的时候发现sort编译报错了:

跳转过去发现底层是ULast-UFirst,底层是快排,链表没法适应该场景。再单独看不同的算法,如上图的sort和reverse,发现模板参数名那有些差异。为什么呢?其实迭代器从功能角度讲是会分类的,分为单向、双向、随机迭代器。单向只能++,双向可以++/--,随机是可以++/--/+/-。如单链表是单向迭代器,双向链表是双向迭代器,vector、string是随机迭代器:

也意味着参数名字暗示了你适合用哪一个算法,如Bid适合用双向,Input适合用单向,Rand适合用随机。之前我们说的正向、反向迭代器是从遍历功能的角度来说,这里是从性质划分,和容器的底层结构有关。因为sort是随机迭代器,所以list调用的时候报错了。因此能否用一个算法要看容器的迭代器到底是哪一种,那我怎么知道是哪一种?文档中其实有说明迭代器类型是哪一种:

如list的迭代器是双向迭代器。它这不是完全按名字匹配的,(随机迭代器可以认为是一个特殊的双向迭代器)随机迭代器满足双向迭代器的性质,所以看到算法中的迭代器是InPut说明单向、双向、随机迭代器都可以用;看到Bid说明双向、随机迭代器可以用:

是上图这样的一种使用关系。所以链表这里写的sort算法是有意义的,但也可以认为除了方便外意义不是很大,因为实际中其实不用list排序,vector效率远高于list:

首先弄个伪随机数方便生成随机数,定义了vector(提前开好空间)和list,然后再两容器中插入相同的数据,对比它们的排序差异,vector用算法sort,链表用自己的sort,可以看到差距很大,list慢一些。再换一种方式,比如排list的数据,现在把list拷贝到vector,然后用vector排序,排完再拷贝回list:

发现list还是慢一些,所以从效率角度看list的sort意义不大。下面继续看,merge就是两个链表可直接进行归并,归并排序的前提是有序,先sort再merge:

unique是唯一的意思,它的本质是去重,去重的前提也是要排序:

因为不排序去重效率非常低。remove就是find+erase,remove这如果这个值不存在:

发现什么事都不干。还有splice,它是结合的意思,功能是把一个链表的内容转移到另外一个(把A链表的结点取下来直接接入到B链表):

接口(1)是把x链表转移到当前链表这个迭代器位置之前;(2)是把x链表的i位置的值转移到当前链表这个位置之前;(3)是把x链表的一部分转移到当前链表这个位置之前。

2. list的模拟实现

    下面来进行list的模拟实现,实现前先看一下源代码,这里就大概看一下:

这是链表结点结构,可以看出是一个双向链表(奇怪的是这是void*,不是结点类型指针)。下面看看链表的成员变量:

库中有个特点是很喜欢typedef,找不到可以右击速览定义(转到定义:跳到定义的地方):

可以看到它是一个list_node*,list_node也被typedef过。假设现在不知道list是带头双向循环链表:首先看到有个结点,然后看构造:

无参的构造函数初始化了一个结点,结点的next指向自己,prev指向自己,再速览一下get_node:

看到结果是空间配置器来的,释放结点调的是put_node.头插尾插调的是inster:

现在上手,首先定义一个list_node,这里用struct定义,如果用class里面要用public(因为结点不弄成公有一会很麻烦,除非弄各种友元)。在前面试用链表时我们也没有触碰到结点,所以结点公有时对别人也是隐藏的。下面实现一下:

在定义链表时,先typedef一下,因为类模板中类名不是类型,防止写错,然后写好成员变量:

下面快速写一个出现先用一用,先写个构造,new个结点,prev和next指向自己:

下面再来写push_back,该结构中找到尾就可以插入,tail是head的前一个,然后建立结点连接就可以了:

然后再补充一个构造函数:

这样一个浅浅的链表就出来了。下面就来搭建迭代器:

能不能像之前实现一样,直接Node*充当迭代器?不可以:

因为未来begin给it,it解引用不能得到想要的数据,它解引用后是个结点,并且it++后也不能到下一个位置,因为它不连续。此时运算符重载就要起作用了,再上手一个自定义类型来配合。因为用的时候是这样用:

根据用法结合来写。iterator是类里面typedef的,那迭代器是如何构造出来的?迭代器是一个自定义类型,这个自定义类型成员是一个结点的指针:

我本来也想用结点指针做迭代器,但无奈的是,不能像vector原生指针那样,因为底层结构有差异,但结构指针确实可帮我找到下一个,也可帮我取数据,内置类型不符合我们行为。因此C++可以用类去封装内置类型,然后重载运算符,此时就可以控制它的行为了。外部*it就是调用operator*,++就是调用operator++,运算符重载就变成我们来控制了,这样实现后用起来和之前感觉一样,但实际底层不一样。现在来完成,先实现begin和end:

(1.单参数构造函数支持隐式类型转换 2.返回对象)上图两种本质是一样的,都生成了匿名对象。然后实现解引用,*it去调operator*期望返回值,因为出了作用域结点还在,用引用返回可读可写:

再重载一个++,迭代器++返回的还是迭代器,然后走到node的next就可以:

再重载一个!=,用结点指针来进行相关比较:

下面测试一下:

发现!=有报错,因为end那里是传值返回,返回head的拷贝,是临时对象,具有常性,所以加const就没有问题了:

有了迭代器后范围for也就支持了,库也是直接替换:

看一下下图:

这里还发生了一件事情,这里调begin,通过拷贝构造把值给it,迭代器里目前没有写拷贝构造,默认生成的拷贝构造对内置类型完成浅拷贝,一个指针在这浅拷贝没啥问题。但多个对象指向同一结点为啥没有因多次析构崩溃?因为这个结点不属于迭代器,只是借助结点访问,不管释放什么的,链表里会释放。下面再看后置++,返回++之前的值;还有==,一起补充上:

这样迭代器就基本上完善了。下一个话题是如何设计const迭代器,可以这样设计const迭代器吗?

不可以,迭代器模拟的是指针的行为,指针有两种const指针,一种是const T* ptr1,一种是T* const ptr2,const迭代器是模拟ptr1的行为。如果按照上图那样设计模拟的是ptr2,因为这样设计是迭代器本身不能被修改,也就不可以++修改,我们的目的是让指向的内容不能修改。那如何控制指向数据不能修改?控制返回类型就可以了:

此时有(*it) += 1就会跑不过,因为解引用之后返回的是const对象。按一般的思路我们把类再拷贝一份,这个新类几乎所有的功能和原来是一样的,唯独有些返回值不一样,把__list_iterator的地方改为__list_const_iterator,然后list中再typedef为const_iterator,但这样设计太冗余了,怎么改进?这两个类只是有些接口上返回值不一样,可以通过一个类型去控制返回值就可以。那怎么控制返回值呢?可以去增加模板参数叫Ref:

operator*的返回值是Ref:

从类模板来说Ref是什么并不知道,对普通的iterator它是T引用,对const_iterator来说它是const T引用:

像下图:

返回这都用的类模板,加了模板参数后它们都要跟着改,所以typedef一下,避免又加模板参数后跟着都改:

有些地方喜欢定义成self,然后把__list_iterator<T>改为self。

上图看到,源码中还重载了一个->运算符,并且是三个模板参数。operator->的返回值是pointer,pointer就是模板参数,结果返回数据取地址。operator->有一点我们是可以理解的:指针除了有*解引用还有->解引用,->相比*而言,*是取指针指向的数据,如果是结构体指针就要用->,迭代器模拟指针的行为,什么时候模拟结构体指针呢?

默认返回T*。现在比如有A这样的类:

然后构造一些对象进去:

现在想对自定义类型进行访问:

给*it无法跑,因为*it是取里面的数据,里面的数据是A,A是自定义类型,自定义类型想走也可以,在A中重载流插入。那有没有其它方式可以访问?

可以像上图这样。但这里迭代器模拟的是一个指向结构的指针,要用箭头,所以可以这样写:

但这里有些古怪的地方:(*it)._a1中*it先转化为it.operator*(),返回的是一个A&,然后对象._a1是没啥问题的;it->a1中it->转化为it.operator->(),它的返回值是A*,A*直接跟_a1访问不了,这严格来说正确写法应该是it->->_a1才符合语法,因为运算符重载要求可读性,这可以认为编译器特殊处理省略了一个->。如果是const迭代器,operator->这应该是返回const T*,所以还是多加一个模板参数Ptr:

普通迭代器传T*,const迭代器传const T*,用Ptr代替operator->的T*(==和!=不修改成员函数+const):

链表的核心部分结束,下面完善一下链表:首先看insert,这里不用对pos检查,因为没有说不允许在哪个位置之前插入(反而erase需要检查pos!=end() )。定义cur为pos.node(要插入的结点)(也可以理解为啥迭代器设置为struct,因为全公有,私有还要友元一下),再找前一个位置,再弄一个新节点,然后改变三结点的链接:

删除也是找当前位置的结点,找到前一个与后一个,改变连接关系,删除结点。这里有迭代器失效的问题,因为pos指向的结点被我们释放了,所以返回下一个位置的迭代器(insert在库中返回的是新插入元素的位置):

尾插就是在哨兵位的前面插入,头插就是在第一个位置前面插入:

尾删就是删除end的前一个,顺便实现一下--,头删删除begin位置就可以:

还有个size,遍历计数:

但是如果比喜欢O(N)遍历,可以增加一个size成员,这样就直接return size,前面每次更新一下。clear要清除数据,但不清哨兵位(erase后不能++,所以接收返回值)(若用记录size的方法记得清零):

析构就是先clear,在deleete哨兵位:

现在还剩拷贝构造,拷贝构造也面临深浅拷贝的问题,先拷贝试一下:

运行崩了,原因肯定是析构两次。调式看到报错的地方在erase,实际不一定在这里,调试窗口有个调用堆栈,可看到调用的函数:

main函数调了Test_list3,list3调析构,析构调clear,clear调erase然后崩了。现在完成拷贝构造,首先把初始化做了(比如lt1拷贝构造lt2,lt2的哨兵位头结点好得要出来),然后遍历调push_back:

这里还可以优化一下,构造和拷贝构造初始化那里有些重复,所以有时候会写个empty_init,然后重复的地方复用就行:

下面写赋值,可用传统写法,这里用现代写法来写,拷贝构造出我想要的,然后一交换,顺便完善交换函数:

下面简单测试一下:

还有一个点,库里的赋值和我们写的有些不一样:

我们返回写的是类型,它写的是类名,对拷贝构造和赋值那块写类名也可以,语法上可识别,但不推荐这样写,降低了可读性。可以认为这里是特例,类模板在类里面写类名和类型都是可以的。

3.相关代码

//list.h#pragma oncenamespace yxx
{template<class T>struct list_node{list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}list_node<T>* _next;list_node<T>* _prev;T _val;};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;__list_iterator(Node* node):_node(node){}Ref& operator*(){return _node->_val;}Ptr operator->(){return &_node->_val;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_perv;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const self& it)const{return _node != it._node;}bool operator==(const self& it)const{return _node == it._node;}};template<class T>class list{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;//typedef const __list_iterator<T> const_iterator  erroriterator begin(){//return _head->nextreturn iterator(_head->_next);}iterator end(){return _head;//return iterator(_head);}const_iterator begin() const{//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{return _head;//return const_iterator(_head);}void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();/*_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;*/}//void push_back(const T& x )//{//	Node* tail = _head->_prev;//	Node* newnode = new Node(x);//	tail->_next = newnode;//	newnode->_prev = tail;//	newnode->_next = _head;//	_head->_prev = newnode;//}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}list(const list<T>& lt){/*_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;*/empty_init();for (auto& e : lt){push_back(e);}}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;return newnode;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;}size_t size(){return _size;}/*	size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;}*/private:Node* _head;size_t _size;};class A{public:A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;};void Test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;}void Test_list2(){list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){/*	cout << (*it)._a1 << " "; cout << (*it)._a2 << " ";*/cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;}void Test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int> lt1(lt);for (auto e : lt1){cout << e << " ";}}void Test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt1;lt1.push_back(5);lt1.push_back(6);lt1.push_back(7);lt1.push_back(8);for (auto e : lt1){cout << e << " ";}cout << endl;lt1 = lt;for (auto e : lt1){cout << e << " ";}}
}
//Test.cpp#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
#include <stdbool.h>
#include <assert.h>
using namespace std;#include "list.h"int main()
{yxx::Test_list4();return 0;
}

http://www.dtcms.com/a/318973.html

相关文章:

  • Spring中用到了哪些设计模式
  • 容器之王--Docker镜像的管理及镜像仓库的构建演练
  • W25Q64模块
  • 智慧园区系统:打造未来城市生活新体验
  • 从周末去哪儿玩到决策树:机器学习算法的生活启示
  • 机试备考笔记 7/31
  • 【数据结构】排序(sort) -- 交换排序(冒泡快排)
  • 接入免费的数字人API接口详细教程!!!——小甲鱼数字人
  • OpenCV的关于图片的一些运用
  • 一个基于 select 实现的多路复用 TCP 服务器程序:
  • Opencv-管理图片
  • 计算机视觉--opencv(代码详细教程)
  • ansible-playbook之获取服务器IP存储到本地文件
  • Spring事务失效场景?
  • 光纤滑环 – 光纤旋转接头(FORJ)- 杭州驰宏科技
  • 科技云报到:热链路革命:阿卡 CRM 的 GTM 定位突围
  • 芯谷科技--高效噪声降低解决方案压缩扩展器D5015
  • 全球化2.0 | 泰国IT服务商携手云轴科技ZStack重塑云租赁新生态
  • 安全守护,温情陪伴 — 智慧养老产品上新
  • Element Plus实现分页查询
  • 码头岸电系统如何保障供电安全?安科瑞绝缘监测及故障定位方案解析
  • Rust爬虫与代理池技术解析
  • NAS技术在县级融媒体中心的架构设计与安全运维浅析
  • VSCode ssh一直在Setting up SSH Host xxx: Copying VS Code Server to host with scp等待
  • 支付宝小程序商城怎么搭?ZKmall开源商城教你借力蚂蚁生态做增长
  • 【Agent】ReAct:最经典的Agent设计框架
  • 【pytorch(06)】全连接神经网络:基本组件认知,线性层、激活函数、损失函数、优化器
  • Django 表单:深度解析与最佳实践
  • 高性能分布式通信框架:eCAL 介绍与应用
  • 解锁高效开发:AWS 前端 Web 与移动应用解决方案详解