前言:
前面的博客中我已经介绍了STL核心容器之一的list相关接口的使用,今天我们就从底层出发,来模拟实现一下list的那些核心接口函数。同时,也来感受一下list的双向迭代器到底与string和vector的随机迭代器有哪些区别?
list容器功能接口介绍:https://blog.csdn.net/Miun123/article/details/151685386?spm=1001.2014.3001.5502
废话不多说,我们直接进入今天的正题👇️👇️👇️
list容器深度剖析及模拟实现
我们想要模拟实现list容器,那就要理解list容器的底层结构。前面的博客已经提到,其本质就是一个双向链表,所以,成员变量就应该包含一个记录头节点的指针,以及记录有效节点个数的变量。同时,为了list容器可以满足不同类型的数据,我们将所有的类实现为类模板。

1、定义节点结构

struct创建的类默认所有的成员但是公开的,而节点结构就需要公开被list访问。
cpp
template<class T>
struct list_node {
// 成员变量
T _data;
list_node<T>* _next;
list_node<T>* _prev;
// 默认构造
list_node(const T& val = T())
:_data(val)
, _next(nullptr)
, _prev(nullptr)
{}
};
2、双向迭代器
我们先看一段 slt_list.h头文件中实现list迭代器的源码:(注释是本人自己加的)
cpp
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator; // 普通迭代器
typedef __list_iterator<T, const T&, const T*> const_iterator; // const迭代器
typedef __list_iterator<T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type; // 数据类型
typedef Ptr pointer; // 指针类型
typedef Ref reference; // 引用类型
typedef __list_node<T>* link_type; // 节点类型
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node; // 成员变量
__list_iterator(link_type x) : node(x) {} // 拷贝构造
__list_iterator() {} // 默认构造
__list_iterator(const iterator& x) : node(x.node) {} // 拷贝构造
bool operator==(const self& x) const { return node == x.node; } // 重载==
bool operator!=(const self& x) const { return node != x.node; } // 重载!=
reference operator*() const { return (*node).data; } // 重载------------解引用*
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); } // 重载->
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
self& operator++() { //重载前置++
node = (link_type)((*node).next);
return *this;
}
self operator++(int) { // 重载后置++
self tmp = *this;
++*this;
return tmp;
}
self& operator--() { // 重载前置--
node = (link_type)((*node).prev);
return *this;
}
self operator--(int) { // 重载后置--
self tmp = *this;
--*this;
return tmp;
}
};
和前面string和vector的迭代器不同,list的迭代器不仅自己封装成了一个单独的类模板,而且这个类模板的模板参数有三个。这是为什么呢❓️
我们知道,对于const对象和非const对象,如果将模板参数只有一个**<class T>,** 那么我们势必就要实现两个迭代器的类模板。而这两个类模板中,只有解引符号重载函数和箭头 ->重载函数由于返回值与其对应的类型有关(因为对于const对象,我们无法通过解引用和->改变const对象的值)而实现方式稍有不同;对于其他函数,在两个类模板中就重复了。所以,我们为了避免这种情况,就在类模板中引入了另外两个参数:Ref(T&/const T&------解引用操作符函数的返回值类型), Ptr(T*/const T*------ ->重载函数的返回值类型)。
可以看到,在源码中typedef用得非常多,接下来我们就自己实现一个迭代器的类模板出来:其中++,--,==,!=,*,->这些操作符都是对节点的操作,所以,我们迭代器的类模板中应该有一个记录节点的成员变量_node。
cpp
template<class T, class Ref,class Ptr>
struct list_iterator {
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
// Ref--T& / const T& ; Ptr--T* / const T*
Node* _node; // 成员变量
list_iterator(Node* node) // 拷贝构造
:_node(node)
{}
// ...
};
2.1、解引用
cpp
Ref& operator*()const {
return _node->_data;
}
2.2、->
cpp
Ptr operator->()const {
return &(_node->_data);
}
2.3、前置-- 后置--
cpp
// 前置--
Self& operator--() {
_node = _node->_prev;
return *this;
}
// 后置--
Self operator--(int) {
list_iterator<T> tmp(*this);
_node = _node->_prev;
return tmp;
}
2.4、后置++ 前置++
cpp
// 后置++
Self operator++(int) {
Self tmp(*this);
_node = _node->_next;
return tmp;
}
// 前置++
Self& operator++() {
_node = _node->_next;
return *this;
}
2.5、== 和 !=
cpp
bool operator==(const Self& s)const {
return _node == s._node;
}
bool operator!=(const Self& s)const {
return _node != s._node;
}
3、list容器功能接口
cpp
template<class T>
class list {
typedef list_node<T> Node;
public:
typedef list_iterator<T, T&, T*> iterator; // 普通迭代器
typedef list_iterator<T, const T&, const T*> const_iterator;// const迭代器
// ...
private:
Node* _head; // 头节点
size_t _size;// 有效节点个数
};
3.1、链表初始化
3.1.1、构造空链表
即创建头节点(哨兵位),并初始化

cpp
void empty_init() {
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
3.1.2、默认构造
cpp
list(){
empty_init();
}
3.1.3、拷贝构造
cpp
list(const list<T>& lt) {
// 由于拷贝无法完成头节点初始化,所以先初始化头节点
empty_init();
for (auto e : lt)
push_back(e);
}
3.1.4、初始化列表
cpp
list(initializer_list<int> il)
{
empty_init();
for (auto& e : il)
push_back(e);
}
3.1.5、赋值重载
cpp
void swap(list<T>& lt) {
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=(list<T> lt) {
swap(lt);
return *this;
}
3.2、析构
cpp
~list() {
clear(); // 清理所有节点
delete _head; // 释放头节点
_head = nullptr;
}
3.3、迭代器
迭代器即可以理解为指针,我们用迭代器记录第一个有效节点和尾节点的位置。
cpp
iterator begin() {
return _head->_next;
}
iterator end() {
return _head;
}
const_iterator begin()const {
return _head->_next;
}
const_iterator end()const {
return _head;
}
3.4、插入
先找到指定位置之后的节点:pos->next;然后。连接pos节点,新节点newnode与指定位置之后的节点pos->next。注意:连接顺序,防止改变pos->next的指向。

3.4.1、指定位置插入
cpp
iterator insert(iterator pos, const T& x) {
Node* newnode = new Node(x); // 构造新节点
Node* pcur = pos._node;
Node* prev = pos._node->_prev;
// 连接:prev newnode pcur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pcur;
pcur->_prev = newnode;
++_size; // 有效节点个数加1
return pos; // 返回pos节点
}
3.4.2、头插
cpp
void push_front(const T& x)
{
insert(begin(), x);
}
3.4.3、尾插
cpp
void push_back(const T& x)
{
insert(end(), x);
}
3.5、删除节点
现在的指定位置节点之前的节点:pos->prev;指定位置之后的节点:pos->next。然后,连接pos->prev与pos->next。最后,释放指定位置节点。

3.5.1、删除指定位置节点
cpp
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
// 连接 prev next
prev->_next = next;
next->_prev = prev;
// 释放
delete pos._node;
pos._node = nullptr;
--_size;
return next; // 返回pos节点的下一个节点
}
3.5.2、头删
cpp
void pop_front()
{
erase(begin());
}
3.5.3、尾删
cpp
void pop_back()
{
erase(--end());
}
3.5.4、链表的清理
cpp
void clear() {
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
3.6、获取节点个数
cpp
size_t size() const
{
return _size;
}
3.8、判空
cpp
bool empty() const
{
return _size == 0;
}
4、总结
那么,本期的分享就到此结束,如果大家觉得写的还不错的话,点个小爱心❤️支持一下吧😘😘😘,我们下期再见🤗🤗🤗