前言
这次我们来实现STL List的模拟实现,首先明白STL的list底层是双向带头循环链表。模拟实现list与之前一样先从成员变量,再到四大天选之子,最后到有各种功能成员函数, 最后再到迭代器,由于List的物理结构与前面vector和string不同,使得迭代器也完全不同。由于代码比较多,我把需要注意的地方在代码中以注释的形式写出来。
成员变量
因为list的物理结构不再是连续的物理空间,而是变成一个又一个的结点,导致成员变量从指针变成了类。
//结点定义
template <class T>
struct _list_node
{
_list_node<T>* _prev = nullptr;
_list_node<T>* _next = nullptr;
T _data;
//缺省值给T()考虑到模板参数不仅仅是内置类型,缺省值要给0不合适,不给缺省值编译器会报错。因此采用匿名对象,从而调用匿名对象的默认构造。
_list_node(const T& val = T())
:_data(val)
{}
};
//list的成员变量
private:
node* _head = nullptr;
天选之子
构造函数
//许多地方都需要调用初始化,因此我单独将其写为一个函数
void empty_init()
{
_head = new node;
_head->_prev = _head;
_head->_next = _head;
}
void swap(list<T>&tmp)
{
std::swap(_head, tmp._head);
}
//无参构造
list()
{
empty_init();
}
//区间构造
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
拷贝构造
现代写法:
list(const list<T>&x)
{
empty_init();
list tmp(x.begin(), x.end());
swap(tmp);
}
ps: 什么是现代写法
以往的写法里面开空间、拷贝等等一类操作都是由程序猿一步步来调用new memcpy等来完成的,而现代写法则是调用前面已经实现的函数从而实现,大大降低代码量, 一旦出现问题只需要修改一处,从而修复其他地方的问题。
赋值重载
现代写法:
void swap(list<T>&tmp)
{
std::swap(_head, tmp._head);
}
list<T>& operator=(list<T>tmp)
{
swap(tmp);
return *this;
}
析构函数
//list有一个功能是clear,清除list除头结点的函数。析构函数只需要调用其函数,再将头结点释放掉就完成了。
//迭代器的问题后面提
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
迭代器
list的迭代器与string和vector的迭代器完全不同,且是list中我认为最精华的东西。由于list的底层并不是连续的物理空间,因此SGI版本的采用原生指针作为迭代器,变得行不通了。因此SGI中采用类去模拟指针的行为
实现方法如下`
//为什么模板类型会有什么Ref、Ptr
//原因是实现const_iterator,又需要将iterator CV一份,而改动的地方只有operator* operator->的返回值。这样代码显得十分冗余。因此通过模板来改变这样的情况,请看下面的图,能够更好的理解这一块。
template <class T, class Ref, class Ptr>
struct _list_iterator
{
typedef _list_node<T> node;
typedef _list_iterator<T,Ref, Ptr> self;
node* _node;
//由于成员变量为自定义类型,编译器会调用自定义类型构造函数,因此这里不需要给缺省值
_list_iterator(node* ptr)
:_node(ptr)
{}
_list_iterator(const self& v)
{
_node = v._node;
}
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;
}
Ref operator*()
{
return _node->_data;
}
bool operator!=(self& v)
{
return _node != v._node;
}
bool operator==(self& v)
{
return _node == v._node;
}
Ptr operator->()
{
return &_node->_data;
}
};
//list中的迭代器部分
template<class T>
class list
{
public:
typedef _list_node<T> node;
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
}
成员函数
每个成员函数要注意的 容易出错的地方都写在注释里
修改函数
//1、prev记录pos的prev、next记录pos结点
//2、创建新的结点
//3、两两结点连接起来
void insert(iterator pos, const T& val)
{
node* prev = pos._node->_prev;
node* next = pos._node;
node* new_node = new node(val);
//node* new_node = new node;
//new_node->_data = val;
new_node->_prev = prev;
prev->_next = new_node;
new_node->_next = next;
next->_prev = new_node;
}
//1、检查pos位置是否合法也就是不等于head
//2、记录pos位置的prev和next、pos的结点
//3、将pos的prev结点和next结点连接起来
//4、把pos结点释放掉
iterator erase(iterator pos)
{
assert(pos._node != _head);
node* prev = pos._node->_prev;
node* next = pos._node->_next;
node* current_node = pos._node;
prev->_next = next;
next->_prev = prev;
delete current_node;
return iterator(next);
}
//复用insert和erase即可
void push_back(const T& val)
{
insert(end(),val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase(--end());
}
总结
list的insert erase等功能函数实现起来还是比较常规,属于链表的增删问题。但是list的迭代器绝对是将模板和类玩到了新的高度,很值得我们去学习,以及学习这样的思想,不仅仅是停留在简单粘贴复制一份代码,再轻微的改动,这样使得代码显得十分冗余。