手写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函数等,不过核心的接口都已经实现,后续可完善。