目录
前言
上篇文章我们学习使用了list的常用接口,这篇文章主要来模拟实现list;主要难点是关于list迭代器的实现;这部分内容可能具有挑战;
一、list的基本框架
我们将我们自己模拟实现的list封装进我们的命名空间中,防止于stl库中的list发生命名冲突,引起命名污染;注意以下代码,我们不仅构建了一个list类,我们还构建了一个结点的类,这个lilst类的成员变量就是哨兵位头节点指针;
namespace MySpace
{
template<class T>
struct _list_node
{
// 结点的默认构造
_list_node(const T& data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
// 结点的成员变量
_list_node<T>* _next;
_list_node<T>* _prev;
T _data;
};
template<class T>
class list
{
// 给结点进行重命名
typedef _list_node<T> Node;
public:
private:
Node* _head;
};
}
二、构造与析构函数
list的所有构造函数都必须先构建一个哨兵位头节点,因此,我们将构建初始化哨兵位头节点封装在一个empty_init函数中;
// 初始化链表(头节点的创建)
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
我们在设计这么多构造函数时,可采用现代写法,是代码更加整洁,提高代码复用性;这里重载了一个int版本的构造n个相同的data的构造函数,是为了让当两个参数都为int型时,不会匹配到迭代器那个版本;
// 默认构造
list()
{
empty_init();
}
// 用n个data构造
list(size_t n, const T& data = T())
{
empty_init();
for (size_t i = 0; i < n; i++)
{
Node* new_node = new Node(data);
// 链接新节点
Node* prev = _head->_prev;
Node* next = _head;
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = next;
next->_prev = new_node;
}
}
// 用n个data构造(重载int版本,与vector一样,可能会匹配到迭代器那个函数)
list(int n, const T& data = T())
{
empty_init();
for (size_t i = 0; i < n; i++)
{
Node* new_node = new Node(data);
// 链接新节点
Node* prev = _head->_prev;
Node* next = _head;
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = next;
next->_prev = new_node;
}
}
// 迭代器区间构造
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
// 初始化链表(哨兵位头节点)
empty_init();
// 依次尾插
while (first != last)
{
Node* new_node = new Node(*first);
Node* prev = _head->_prev;
Node* next = _head;
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = next;
next->_prev = new_node;
++first;
}
}
// 拷贝构造
list(const list& lt)
{
empty_init();
// 这里我就直接复用后面实现的push_back了(这里迭代器也未实现,实现迭代器后这段代码才能成功运行)
for (auto& e : lt)
{
push_back(e);
}
}
// 赋值重载(现代写法)
list& operator=(list lt)
{
// 这里的swap也是后面专门为list实现的swap
swap(lt);
return *this;
}
关于析构函数,我们要先将所有保存数据的结点依次释放后,再来释放哨兵位头节点;关于clear函数,我们后面会实现,这里先暂时复用一下;
// 析构函数
~list()
{
// 复用后面实现的清空链表的成员函数
clear();
delete _head;
_head = nullptr;
}
三、迭代器(list实现的核心知识点)
关于list的迭代器的实现,这部分知识稍微偏难,list的迭代器并不能仅仅用typedef重定义,因为list储存数据的空间并不是连续的,而vector储存数据的空间是连续的,当我们对typdef重定义的vector的迭代器+1时,他会指向下一个数据,而如果list而是单纯typedef重定义时,我们对其+1,并不一定会指向下一个数据的位置,因为储存的空间不是连续的,因此,这里我们用一个类对其封装起来,通过运算符重载,对其+1行为进行重定义;使我们通过迭代器访问数据时,可以像访问数组一样,对其+1或-1;
// 迭代器(与前面string和vector不同,这里的迭代器不能仅仅只使用typedef来实现)
// 因为list储存数据不是一段连续的物理空间,因此我们需要对++等运算符重载
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef _list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
// 构造函数
__list_iterator(Node* node = Node)
:_node(node)
{}
// 运算符重载
Ref operator*() const
{
return _node->_data;
}
Ptr operator->() const
{
return &(_node->_data);
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
// 浅拷贝即可,使用系统默认生成的拷贝构造即可
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
// 浅拷贝即可,使用系统默认生成的拷贝构造即可
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator==(const self& it) const
{
return _node == it._node;
}
bool operator!=(const self& it) const
{
return _node != it._node;
}
// 成员变量
Node* _node;
};
上述接口值得一提的是->的重载;这个运算符的重载可能会让很多人感到蒙圈;这里实际上省略了一个->;我们可通过下面一段代码理解;
struct A
{
A(int a = 0, int b = 0)
:_a(a)
,_b(b)
{}
int _a;
int _b;
};
int main()
{
MySpace::list<A> lt1;
lt1.push_back(A(1, 1));
lt1.push_back(A(2, 2));
lt1.push_back(A(3, 3));
// 当list的成员为自定义类型,且我们想访问其成员变量时,有如下方法;
// 方法一:通过 *().访问成员_a
auto it = lt1.begin();
while (it != lt1.end())
{
cout << (*it)._a << ":" << (*it)._b << endl;
it++;
}
cout << endl;
// 方法二:通过重载后 -> 访问成员
it = lt1.begin();
while (it != lt1.end())
{
// 这里实际省略了一个->
cout << it->_a << ":" << it->_b << endl;
// 不省略是这么写
cout << it.operator->()->_a << ":" << it.operator->()->_a << endl;
it++;
}
return 0;
}
而在list类中,我们对这个迭代器类型进行重定义;并且提供一些对外必要的函数接口;如begin、end等;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
//迭代器相关成员函数
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
四、容量与访问相关接口
这一类接口实现起来就没有这么困难了;以下为实现代码;
// capacity
size_t size() const
{
size_t sz = 0;
const_iterator it = begin();
while (it != end())
{
sz++;
it++;
}
return sz;
}
bool empty()
{
return _head->_next == _head;
}
// access
T& front()
{
return (_head->_next)->_data;
}
const T& front() const
{
return (_head->_next)->_data;
}
T& back()
{
return (_head->_prev)->_data;
}
const T& back() const
{
return (_head->_prev)->_data;
}
五、修改相关接口
最后就是一些修改相关接口了,主要实现一些比较核心的修改接口;接口之间可以相互复用;
// modify
void push_back(const T& data)
{
//Node* new_node = new Node(data);
//Node* prev = _head->_prev;
//Node* next = _head;
//prev->_next = new_node;
//new_node->_prev = prev;
//new_node->_next = next;
//next->_prev = new_node;
insert(end(), data);
}
void push_front(const T& data)
{
insert(begin(), data);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
// 某个位置插入一个值
iterator insert(iterator pos, const T& data)
{
Node* new_node = new Node(data);
Node* prev = pos._node->_prev;
Node* cur = pos._node;
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
return iterator(new_node);
}
// 删除某个位置上的值
iterator erase(iterator pos)
{
assert(pos._node != _head);
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos;
pos = next;
return iterator(pos);
}
// 清空链表所有数据(除哨兵位头节点外)
void clear()
{
iterator it = begin();
while (it != end())
{
Node* del = it._node;
it++;
delete del;
}
}
// 交换两个链表的头节点
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
六、本文源码
本文源码依旧上传至了gitee中,可通过下方链接免费获取;