目录
1.前言
我们都知道C++中有一个list容器,有基础的人清楚它的底层是一个带头双向循环列表,但进一步的细节可能就不太了解,所以,本篇注重谈论list容器的底层细节。
——(护盾)【由于本篇注重于细节,所以不再会去解释基础语法的作用,因此本篇针对非小白学习者查阅,即有不错的面向对象知识体系和带头双向循环列表理解的群体】
2.实现list容器
2.1链表结构体
我们先分析一下list容器由几个类构成,首先最主题的肯定是list类
也就是这个,因为我们的增删查改的操作都是面向它来使用的,其次就要想一想了,成员变量是什么?我们知道它的底层是一个链表,所以我们的成员变量理应是一个链表结构体指针类型的变量。
这个链表结构体的实现并不难,一个前驱指针,一个后驱指针,一个数据变量,注意我们这里的变量都是指针和T类型数据,它本身没有对空间进行动态开辟等操作,所以走的都是浅拷贝,也就是说我们的析构都不用自己写,我们只需要写一个构造函数就可以了,我们采用构造列表的方式来给这些变量附上初始值。
链表结构体的内容其实主要就这些,接下来我们来看看它的迭代器。
2.2list迭代器
如果看过我前面两篇(vector,string)的同学可能知道之前的那两个迭代器我们都是用指针来实现的,也没啥门道,但是现在这个容器的迭代器就没有那么轻松了。
大家可以回顾一下,我们平时用迭代器类型的数据都进行过怎样的操作呢?是不是有++,--,==,!=,解引用,甚至还有->?接下来我们将对他们一一实现。
迭代器的成员变量
迭代器实际上操控的就是我们的链表结构体,所以我们的成员变量就只有一个结构体指针。
我们类模板定义的参数有三个,我们目前就只看T,后面两个参数用到了再讲,Node是我们的链表结构体,Self是我们的迭代器类型。
同样的对空间进行动态开辟的过程是交给list去干的,因此我们的迭代器类也不需要写析构,让它自动调用默认的就可以了。
迭代器的构造函数
就是这么简洁,只需要赋个值就可以了。
迭代器的++,--
我们分别实现了前置++,后置++,以及前置--和后置--。
这些都是老操作了,很好看懂。
迭代器的==,!=
这个操作就更简单了,我们直接来看最重要的解引用和->.
迭代器的解引用
里面的内容很好懂,返回链表结构体里的数据嘛,那这个Ref是什么呢?书接上回,我们迭代器类模板不是有三个参数嘛,他们的意思就是这个
就是带不带const的问题,为什么要这样子实现呢?因为倘若你不这样实现,他就得写两个几乎一模一样的迭代器类,这样代码就太冗余了。
看了这两个重命名大家就能知道Ref和Ptr的作用是什么了。
迭代器的->
->的用法主要是针对T类型是类的时候。
3.list类
实现了前面两个类之后,我们最后可以来实现list类了。(基础的我会一笔带过,难点我会细讲)
构造函数
list的构造函数需要我们创建节点,因此我们可以直接这样写:
析构函数
clear是用来清空数据但是一般不会释放内存空间,这里我们写一个不释放空间的版本。所以我们的析构函数就可以调用clear然后释放空间就可以了。
插入
插入我们的思路就是创建新节点,然后弄清前驱指针和后驱指针的对应顺序就可以了。
删除
删除也是差不多的思路,只需要理清前后指针,然后释放掉我们不要的链表结构体就可以了,注意这里要有一个返回值,这是为了避免迭代器因为释放空间而失效,所以我们要对它进行更新,不过在我们日常使用的时候最好的做法还是涉及到删除操作时我们的迭代器就不要再使用了,或者可以重新定义一下。
头插、尾插
头插和尾插我们都是复用insert的
头删、尾删
头删和尾删我们也是复用erase的
begin、end
size
empty
拷贝构造函数
这里我们可以采取2尾插的方式来进行拷贝构造。
赋值运算符重载
我们的参数写成这样就可以让它去自动调用拷贝构造函数,这样做的好处是不会影响到传过来的list类型的变量,而且出了这个函数它会自动调用析构函数不需要我们手动来释放。然后将l1与lt的数据进行交换就可以了。(lt就相当于中转站)