一、前言
二、结构分析
节点结构
作为链表,每个位置节点的结构应该设置为结构体:
template<class T>
struct list_node
{
list_node(const T& x = T())
: _next(nullptr)
, _prev(nullptr)
, _data(x)
{}
list_node<T>* _next;
list_node<T>* _prev;
T _data;
};
迭代器结构
我们知道迭代器模拟的是指针的行为,之前在模拟vector的时候,我们将原生指针直接定义为迭代器,因为vector内存连续。但list是链表,且空间不一定连续,没法像vector那样简单粗暴,需要我们对原生指针进行一定的封装。
其中三个类模板分别表示:
T 类型
Ref:T&或 const T&
Ptr :T* 或 const T*
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* n)
: _node(n)
{}
};
我们要模拟原生指针的行为,就要重载几个常用操作符:
*
我们通过拿数据的方式来理解一下为什么要重载 * 操作符以及如何重载 * 操作符
如图,对于类型T来说,我们迭代器即是模拟T* 拿到 T 的过程。
Ref operator*()
{
return _node->_data;
}
->
对迭代器使用->操作符,返回T类型对象的指针,假如这个对象是一个自定义类型,想要拿到这个对象中的数据就要完成下面的操作
- 先对迭代器->拿到指向这个对象的指针。
- 对拿到的这个指针->拿到指向空间的数据。
iterator->->_a1;
这样就会连续使用两次->为了可读性,只需要使用一次->即可读取到_a1
这是我认为这里->要返回_data的地址而不是返回具体数据的意义。
Ptr operator->()
{
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& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
List结构
我们发现在这里使用类模板参数会非常的方便构造迭代器和const迭代器。
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;
private:
node* _head;
}
构造、拷贝构造、赋值重载:
详细的操作与用C语言模拟实现链表几乎一致,忘记可以去翻看我之前讲C语言模拟的文章,唯一需要说明的是,为了与STL保持一致,我们创建的是一个带头双向循环链表,因此在操作前,需要有一个头节点,因此将开辟头节点的任务交给一个函数,保证代码简洁性。
void empty_init()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
node* tail = _head->_prev;
node* new_node = new node(x);
tail->_next = new_node;
new_node->_prev = tail;
new_node->_next = _head;
_head->_prev = new_node;
}
list()
{
empty_init();
}
//迭代器区间构造
template<class Iterator>
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
//赋值重载
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
迭代器:
迭代器这里需要注意,与vector和string的不一样,我们这里是自己定义的一个模仿指针行为的结构体,因此我们返回迭代器的时候需要经历这几个步骤:先使用指针构造一个迭代器对象,在返回时再拷贝构造一份作为返回值,因此我们可以使用匿名对象,编译器会帮助我们将两次构造优化为一次,即直接构造一份返回值。
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 iterator(_head);
}
insert和erase:
非常常规的操作,这里只给出代码:
void insert(iterator pos,const T& x)
{
node* prev = pos._node->_prev;
node* next = pos._node->_next;
node* newnode = new node(x);
newnode->_prev = prev;
newnode->_next = next;
prev->_next = newnode;
next->_prev = newnode;
}
void erase(iterator pos)
{
node* prev = pos._node->_prev;
node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
}
析构与clear():
析构时同样要与vector、string作好区分,list的迭代器在erase后,就会立刻失效,我们可以使用后置++巧妙解决这个问题,因为后置++返回的是一份迭代器的拷贝,真正的迭代器已经完成了++操作。
void clear()
{
iterator it = begin();
while (it != end())
{
//后置++,erase的是tmp,迭代器已经是下一个了
erase(it++);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
三、反向迭代器
template<class Iterator,class Ref,class Ptr>
struct ReverseIterator
{
typedef ReverseIterator<Iterator,class Ref, class Ptr> Self;
Iterator _cur;
ReverseIterator(Iterator it)
:_cur(it)
{ }
Ref operator*()
{
Iterator tmp = _cur;
--tmp;
return *tmp;
}
Self& operator++()
{
--_cur;
return *this;
}
Self& operator--()
{
++_cur;
return *this;
}
bool operator!=()
{
return _cur != s._cur;
}
};
四、结语
以上就是本篇文章的全部内容了,我们下次再见!