手写STL之List
1.List概述
C++标准模板库(STL)中的list是一个双向链表容器,提供了在链表中高效存储和访问数据的功能。它是一个模板类具有以下特性:
- 链表结构:list是由节点组成的链表,每个节点包含数据以及指向前一个节点和后一个节点的指针。这种结构使得在list中插入、删除和移动元素非常高效,因为不需要像动态数组那样进行内存的移动和重分配。
- 双向访问:list支持双向访问,可以从链表的开头或末尾快速访问元素。你可以使用迭代器进行正向或反向遍历,也可以使用front()和back()函数访问第一个和最后一个元素。
- 动态大小:list的大小可以动态增长或缩小,无需预先分配固定大小的内存。
- 插入和删除:在list中插入和删除元素非常高效。通过使用迭代器,可以在常数时间内在任意位置插入或删除元素。
- 不支持随机访问:list不支持通过索引进行随机访问,这意味着不能像数组那样使用索引来访问元素。如果需要随机访问,vector可能更适合。
- 没有连续存储:由于list是一个链表,它的元素在内存中不是连续存储的,这可能会对缓存性能产生一些影响。
- 不支持快速的随机访问、排序和二分查找:由于没有随机访问,对list进行排序和二分查找会比较低效。如果需要这些功能,你可以考虑使用vector或deque容器。
STL库中list的定义如下:
| template< | 
| T | 元素的类型。 T必须满足可复制赋值 (CopyAssignable) 和可复制构造 (CopyConstructible) 的要求。 | 
|---|
| Allocator | 用于获取/释放内存及构造/析构内存中元素的分配器。类型必须满足分配器 (Allocator) 的要求。 | 
|---|
| 成员函数 | |
|---|---|
| (构造函数) | 构造 list(公开成员函数) | 
| (析构函数) | 析构 list(公开成员函数) | 
| operator= | 赋值给容器 (公开成员函数) | 
| assign | 将值赋给容器 (公开成员函数) | 
| get_allocator | 返回相关的分配器 (公开成员函数) | 
| 元素访问 | |
| front | 访问第一个元素 (公开成员函数) | 
| back | 访问最后一个元素 (公开成员函数) | 
| 迭代器 | |
| begincbegin(C++11) | 返回指向起始的迭代器 (公开成员函数) | 
| endcend(C++11) | 返回指向末尾的迭代器 (公开成员函数) | 
| rbegincrbegin(C++11) | 返回指向起始的逆向迭代器 (公开成员函数) | 
| rendcrend(C++11) | 返回指向末尾的逆向迭代器 (公开成员函数) | 
| 容量 | |
| empty | 检查容器是否为空 (公开成员函数) | 
| size | 返回容纳的元素数 (公开成员函数) | 
| max_size | 返回可容纳的最大元素数 (公开成员函数) | 
| 修改器 | |
| clear | 清除内容 (公开成员函数) | 
| insert | 插入元素 (公开成员函数) | 
| emplace(C++11) | 原位构造元素 (公开成员函数) | 
| erase | 擦除元素 (公开成员函数) | 
| push_back | 将元素添加到容器末尾 (公开成员函数) | 
| emplace_back(C++11) | 在容器末尾就地构造元素 (公开成员函数) | 
| pop_back | 移除末元素 (公开成员函数) | 
| push_front | 插入元素到容器起始 (公开成员函数) | 
| emplace_front(C++11) | 在容器头部原位构造元素 (公开成员函数) | 
| pop_front | 移除首元素 (公开成员函数) | 
| resize | 改变容器中可存储元素的个数 (公开成员函数) | 
| swap | 交换内容 (公开成员函数) | 
2. 构造节点
list的数据结构有两种,一种是双向链表,结构如下:

在双向链表中,每一个节点为一个node,node包含一个指向上一个节点的prev指针和指向下一个节点的next指针,头节点的prev指针指向nullptr,尾节点的next指针指向nullptr。
一种是双向循环链表,和双向链表不同的就是,双向循环链表头节点的prev指针指向了尾节点,尾节点的next指针指向了头节点,由此组成了一个循环。

我们实现的list是基于双向链表的,list是一个C++的类,由很多个节点构成,因此先来定义一下节点的数据结构,node节点由两个指针和存储的数据组成,是一个模板类,定义如下:
| template <typename T> struct list_node | 
3.构造迭代器
在list的使用种,用两种类型的迭代器,一种是普通的iterator,还有一种就是不可修改的const_iterator,这里的不可修改是指节点的数据不可修改,迭代器构造如下,和vector的迭代器类似,需要去重载不同的操作符。
| template<class T, class Ref, class ptr> | 
在list_iterator内部维护了一个list_node<T>*类型的指针,用来指向list的每一个节点,这里解释一下为啥list_iterator模板类为啥有三个模板参数,那是为了方便通过typedef来定义iterator和const_iterator,在后面list类中可以如下定义:
| typedef list_iterator<T, T&, T*> iterator; | 
4.构造list类
首先list类中包含了如下的成员变量,除了一个node类型的指针还维护了一个_size,当对list容器进行操作时,需要对_size实现增减操作。由于采用循环链表实现,所以当初始化链表时只需要定义一个头节点,让head节点的prev和next指针都指向自己就可以了.
| template <typename T> class list{ | 
然后我们来定义一些和迭代器有关的函数,首先是begin()和end(),用来返回头节点指针和尾节点指针,这里我们定义_head->next为begin,end为_head,然后有两个重载版本。
| const_iterator begin() const | 
2.1 容量
和容量相关的接口比较简单,这里不赘述,代码如下:
| public: | 
2.2 修改器
在实现其他修改器之前,可先定义insert和erase函数,代码逻辑并不复杂,实现如下:
| //往pos位置插入元素T | 
其他的修改器就可基于insert和erase函数来实现:
| public: | 
2.3 元素访问
提供了访问头节点和尾节点数据的接口:
| public: | 
2.4 其他构造函数
| public: | 
5. 结语
list已完成,未进行测试,list是比较简单的,当然我们还有一些机制没有实现,比如逆向迭代器,assign函数等,不过核心的接口都已经实现,后续可完善。






