​ List容器其实就是一个双向链表:一个由节点组成的线性的数据结构,每个节点都存储一个元素,并且包含指向前一个和后一个元素的指针。

​ 这种独特的结构使得它区别于vector拥有一些独特的优缺点:插入/删除效率高——任意位置都为O(1),但是不支持随机访问——无法通过下标访问元素,不需要连续的存储——不需要移动元素,也不涉及扩容复制。

​ 常规操作如push_back,push_front,其原理就是创建一个新节点挂载到尾部的next或者头部的next,pop类似。

​ 而insert/erase则需要通过迭代器获取目标位置,再修改相邻的节点的指针来实现插入和删除,这种方式支持任意位置

​ 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#pragma once
#include <cstddef> // for std::size_t
#include <stdexcept> // for std::out_of_range

namespace kiri {

/**
* @brief 最小实现的双向链表 list(简化版 std::list)
* @tparam T 链表存储的数据类型
*/
template<typename T>
class list {
private:
/**
* @brief 链表结点结构体(双向)
*/
struct Node {
T data; // 节点存储的数据
Node* prev; // 指向前一个节点
Node* next; // 指向下一个节点

Node(const T& val) : data(val), prev(nullptr), next(nullptr) {}
};

public:
/**
* @brief 默认构造函数,初始化空链表
*/
list() : head_(nullptr), tail_(nullptr), size_(0) {}

/**
* @brief 析构函数,释放所有节点
*/
~list() {
clear();
}

/**
* @brief 在链表尾部插入一个元素
* @param val 要插入的值
*/
void push_back(const T& val) {
Node* node = new Node(val);
if (!tail_) {
// 空链表
head_ = tail_ = node;
} else {
// 链表非空,尾插
tail_->next = node;
node->prev = tail_;
tail_ = node;
}
++size_;
}

/**
* @brief 在链表头部插入一个元素
* @param val 要插入的值
*/
void push_front(const T& val) {
Node* node = new Node(val);
if (!head_) {
head_ = tail_ = node;
} else {
node->next = head_;
head_->prev = node;
head_ = node;
}
++size_;
}

/**
* @brief 移除链表尾部元素
*/
void pop_back() {
if (!tail_) return; // 空链表无操作
Node* to_delete = tail_;
tail_ = tail_->prev;
if (tail_) tail_->next = nullptr;
else head_ = nullptr;
delete to_delete;
--size_;
}

/**
* @brief 移除链表头部元素
*/
void pop_front() {
if (!head_) return;
Node* to_delete = head_;
head_ = head_->next;
if (head_) head_->prev = nullptr;
else tail_ = nullptr;
delete to_delete;
--size_;
}

/**
* @brief 获取链表头部元素(引用)
* @throws std::out_of_range 如果链表为空
*/
T& front() {
if (!head_) throw std::out_of_range("list is empty");
return head_->data;
}

/**
* @brief 获取链表尾部元素(引用)
* @throws std::out_of_range 如果链表为空
*/
T& back() {
if (!tail_) throw std::out_of_range("list is empty");
return tail_->data;
}

/**
* @brief 判断链表是否为空
*/
bool empty() const {
return size_ == 0;
}

/**
* @brief 获取链表元素数量
*/
std::size_t size() const {
return size_;
}

/**
* @brief 清空链表所有元素,释放内存
*/
void clear() {
Node* cur = head_;
while (cur) {
Node* next = cur->next;
delete cur;
cur = next;
}
head_ = tail_ = nullptr;
size_ = 0;
}

private:
Node* head_; // 指向链表头部的指针
Node* tail_; // 指向链表尾部的指针
std::size_t size_; // 当前链表中元素的数量
};

} // namespace kiri

实现的功能如下:

成员函数 功能描述
list() 默认构造函数,初始化一个空链表
~list() 析构函数,释放链表中所有节点
push_back() 在链表尾部插入一个元素
push_front() 在链表头部插入一个元素
pop_back() 移除链表尾部元素
pop_front() 移除链表头部元素
front() 返回头部元素的引用,若链表为空则抛异常
back() 返回尾部元素的引用,若链表为空则抛异常
empty() 判断链表是否为空(即 size == 0)
size() 返回当前链表中的元素个数
clear() 清空整个链表,释放所有节点并重置状态

运行测试:

list一代测试