list介绍
list即数据结构中的链表,STL中实现的是带头双向链表,它的物理地址不连续。相比于vector,由于其物理地址不连续的特点,它的模拟实现较为复杂。咱们还是先介绍,认识一下,再尝试模拟实现
| list() | 默认构造 |
| list (const list& x) | 拷贝构造 |
| list (InputIterator first, InputIterator last) | 通过任意容器的迭代器初始化 |
| list (size_type n, const value_type& val = value_type()) | 初始化为n个value值 |
| begin+end | 分别返回指向第一个数据的迭代器和指向最后一个数据的下一个位置的迭代器 |
| rbegin+rend | 分别返回指向最后一个数据的迭代器和指向第一个数据的前一个位置的迭代器 |
| empty | 判断是否为空 |
| size | 返回有效节点个数 |
| front | 返回第一个节点数据的引用 |
| back | 返回最后一个节点数据的引用 |
| push_front | 在首元素前插入数据 |
| pop_front | 删除首元素 |
| push_back | 尾插 |
| pop_back | 尾删 |
| insert | 指定位置插入数据 |
| erase | 指定位置删除数据 |
| swap | 交换两个list |
| clear | 清空list |
list模拟实现
首先我们要构建框架,因为是带头双向链表,我们List类中成员只需包含哨兵位(即链表的头),然后即可跟随链表结点中的next和prev遍历链表。按照C语言版的数据结构中的思路,List类中成员为头的地址,指向下一个结点的next指针,以及指向上一个结点的prev指针。STL则是将链表结点封装成了一个类(我命名为List_node),这样List 不用关心节点内部怎么存数据,只操作节点指针;新增 / 删除节点只需要创建 / 销毁List_node对象
因此,如下图所示我们的List类只有一个成员_head,其类型为List_node*即Node*
此外,List_node是struct类,因为无论其成员函数还是成员变量均可默认为public
由于list物理结构的特殊性,迭代器的实现不能跟vector等的那么简单,因为其前置++或后置++可能找到的不是当前结点的下一个结点,由此我们要想办法,结合前面的语法知识可以想到重载++以使他走到下一个结点(利用当前结点的_next)
(少实现了一个函数)
我们如此费力地实现了迭代器,我们可以不实现吗,答案是利大于弊
1、封装,通用的相似的遍历容器的方式,并且封装屏蔽容器结构的差异,和底层实现细节
2、通用/复用,实现算法时用迭代器函数模板方式实现,跟底层容器结构解耦
接下来就是const_iterator,无论是typedef还是函数重载都无法实现,因此我们可以考虑再封装一个const_List_iterator类,如下:
(少实现了一个函数)
有上面的铺垫后,我们在学习一下迭代器地两个模板类融合为一个类但能实现两个类的功能
为了List代码的可读性,做出如下处理
构造函数
empty_init函数(给list创建哨兵位)因为接下来的函数也要使用,封装成了函数
拷贝构造
利用初始化列表构造list
swap函数
赋值运算符重载
析构函数
clear函数
pop_back函数
push_front函数
pop_front函数
有关迭代器函数(利用匿名对象提高效率)
insert函数
push_back函数
这里实现较为复杂,是因为push_back函数的设计在insert之前,可以通过insert函数设计push_back函数,代码十分简洁
erase函数
size函数
如果list类中成员变量不含有效数据个数_size,此处size函数实现较为复杂,我们在list类中添加一个_size,size函数实现起来就简单多了