通过list模拟实现学习模版使用以及迭代器的封装
一.list介绍
在数据结构中,有一种链表结构是带哨兵位的头节点,其每一个节点都有一个指向上一个节点的指针以及指向下一个节点的指针,从而形成一种环状结构。
STL中list就采用了带头双向循环链表的结构,弥补了(vector)连续空间的结构不适于对中间数据频繁插入删除的缺点。
二.list框架
先对list进行分析,可以把它分成几个部分梳理思路,分别实现:
1.list成员变量:节点实现
2.list基本成员函数:构造析构等
3.迭代器
4.list功能实现:增删查改
三.模拟实现
有了大的框架,我们可以开始处理细节问题,完成list的基本功能实现。
1.节点类处理:
链表的一个节点本质上分为三部分:两个指针以及数据值。
如果将这三者作为三个成员变量,当我们对一个节点进行操作(传参)时,需要同时传三个数据,这是非常不方便,所以考虑将其封装成类。
注意:对于T,不一定是内置类型,所以构造函数需要给缺省值调用类型本身的构造函数。
2.结构
我们已经知道,list是带头的双向循环链表,
明确了成员变量,list的基本结构就已经出来了:
3.迭代器的封装
迭代器(Iterator)是C++ 标准库中的一个重要概念,它提供了一种通用的方式来访问容器(如 std::vector 、 std::list 、 std::map 等)中的元素,同时隐藏了容器的内部实现细节。
个人认为,在list的实现里大部分成员函数都比较常规,只有迭代器的实现需要我们更多注意,其也是我们学习list时需要重点理解和掌握的。
在vector、string里,迭代器本质上是指针,通过对指针指向地址空间+-等行为得到数据。但对于list而言,是否可以直接将变量指针作为iterator?
但仔细一想就会发现,list的节点空间并不连续,如果这样定义iterator,当对其+-时指针显然成为了野指针。
所以我们迭代器的操作需要特殊处理:对运算符重载;而为了完成这些函数操作,我们需要再一次把迭代器封装:
注意:对于迭代器类不需要析构函数,因为当我们不再使用迭代器时,节点并不应该被销毁,节点空间应在list类中管理释放。
而拷贝构造同样不用我们显式去写,当用一个已存在的iterator去定义另一个iterator时,它们指向同一块空间(一个节点),那么只需要一个完成浅拷贝的拷贝构造--编译器自动生成的就可以使用。
有了普通迭代器,const的迭代器又该如何实现?
当使用const_iterator,不可修改节点内数据,也就是说,只要保证__list_iterator类中的函数返回值都是const类型,我们就无法对来自iterator操作中得到数据进行修改。
而为了满足const需求,再写一个类去实现几乎相同的工作(仅仅是返回类型不同)是很浪费的。
这时,模版又是一个很好的工具。
__list_iterator中,发现返回值分成四种:T&,self&,T*,bool
self是指向另一节点指针与数据无关,那么我们需要控制的只是T& ,和 T*。
所以,增加两个模板参数:Ref,Ptr分别代表T&,和T*
那么当需要const类型迭代器时:只需要给模板参数为:const T ,const T&,const T* 来实例化。
4.list尾插:
其他函数实现过程基本相同,在实现时要保证链表的完整性,以及迭代器失效的问题(删除节点后让iterator指向下一个节点)。
5.完整代码
头文件:#include<iostream>,#include<assert.h>