STL-list模拟实现
在使用list的时候,因为不怎么常用这个容器,里面的一些函数经常会忘记。为了加深对于list的容器的熟悉,于是我便开始了list的模拟实现。废话不多说,直接上代码。
//为了与STL的区分,我把代码都包进了自己的命名空间-
- 首先是节点的结构
点击查看代码
//节点
template<class T>
struct ListNode
{
ListNode<T> * _prev;//指向前一个节点
ListNode<T> * _next;//指向后一个节点
T _date;//存储的数据
ListNode(const T& date = T())//默认构造,
:_prev(nullptr)
,_next(nullptr)
,_date(date)
{}
};
- 下面就是list的主体了,list是双向带头循环的链表,所以带一个哨兵卫的节点,
点击查看代码
template <class T>
class list
{
typedef ListNode<T> Node;
public:
//构造
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
//析构
~list()
{
Node* prev= _head ->_next;
Node* next = prev->_next;
while (prev != _head)
{
delete prev;
prev = next;
next = next->_next;
}
delete _head;
}
private:
Node* _head;
};
- 然后是链表的一些基本插入删除操作,
点击查看代码
//自己实现尾插
//void push_back(const T& date)
//{
// Node* tail = _head->_prev;
// Node* nextNode = new Node(date);
// tail->_next = nextNode;
// nextNode->_prev = tail;
// nextNode->_next = _head;
// _head->_prev = nextNode;
//}
//复用insert
void push_back(const T& date)
{
insert(iterator(_head), date); //insert是在节点前面插入数据
}
// 尾删
void pop_back()
{
erase(iterator(_head->_prev));
}
//头插
void push_front(const T& date)
{
insert(iterator(_head->_next));
}
//头删
void pop_front()
{
erase(iterator(_head->next));
}
//pos位置前面插入
iterator insert(iterator pos, const T& x)
{
Node* tail = pos._node->_prev;
Node* nextNode = new Node(x);
pos._node->_prev = nextNode;
nextNode->_next = pos._node;
tail->_next = nextNode;
nextNode->_prev = tail;
return iterator(nextNode);//插入时给的是迭代器指向位置,返回也应该是一个迭代器,迭代器位置是新数据的节点
}
//删除pos位置节点
iterator erase(iterator pos)
{
assert(pos._node != _head);
iterator temp(pos._node->_next);
pos._node->_prev->_next = pos._node->_next;
pos._node->_next->_prev = pos._node->_prev;
delete pos._node;
return temp; //与插入同理,返回迭代器,位置是被删除的下一个节点
}
- 然后链表是最关键的迭代器了,相较于vector和string, list最不同就是迭代器不再是原生指针,所以,不能再像原生指针那样直接++或--的操作了,所以需要自己实现一个迭代器模板。
ps:在一开始查看文档时,我对于为何会有三个模板参数属实无法理解。但在实现完list之后,我对大佬写出来的代码便是更加佩服。
点击查看代码
//迭代器模板
template <class T, class Ref, class Ptr> //Ref控制*解引用操作时const对象重载,Ptr控制->操作时const对象重载
struct _list_iterator
{
typedef ListNode<T> Node;
typedef _list_iterator<T, Ref, Ptr> Self;
Node* _node;
//构造
_list_iterator(Node* x)
:_node(x)
{}
//++重载 返回还是迭代器
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self temp(*this);
_node = _node->_next;
return temp;
}
//--重载
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self temp(*this);
_node = _node->_prev;
return temp;
}
//*引用重载,返回的是解引用的数据 T
Ref operator*()
{
return _node->_date;
}
Ptr operator->()
{
return &_node->_date;
}
//!=
bool operator!=(const Self& it)
{
return _node != it._node;
}
// ==
bool operator==(const Self& it)
{
return _node == it._node;
}
};
- 有了迭代器模板,迭代器的操控就完全掌握在自己手里了,但是随之而来又出现了一个问题,const对象该如何重载,将const对象赋值给非const迭代器权限放大肯定是不行的,
const对象中主要是解引用操作可以修改对象的数据,那就重载一个const的调用const<T>& operator*() const
。一开始本以为这样就好了,但还是不行。
仔细一看,发现const的是容器,但是调用的it的迭代器,迭代器还是那个迭代器,原本的想法就只能再重写一份const_的迭代器,但是这样又会代码冗余,两份迭代器区别只有重载的返回值是否是const。
百思不得其解,我便去参考了list的源码,发现了迭代器的模板是iterator<T,Ref,Ptr>,仔细一研究,发现,重载中的返回值是Ref,然后在list中修改传参,如果是const,Ref就是const T&,返回值就是const。
还有一个Ptr参数,与之同理,是为了->重载准备,如果是const,Ptr就传const T,返回值也就是const;
点击查看代码
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);
}
最关键的迭代器完了后,就剩一些边边角角的补充
点击查看代码
//清空
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);//剩哨兵卫_head erase会自己调整链接关系
}
}
//迭代器区间构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);//复用尾插
++first;
}
}
//赋值重载
list<T>& operator=(list<T> lt)
{
swap(_head, lt._head);//lt不用引用传参,会调用拷贝构造,与this指针交换后,出作用域会自动调用析构
return *this;
}
//返回第一个数据
T& front()
{
return _head->_next->_date;
}
//const重载
const T& front() const
{
return _head->_next->_date;
}
//返回最后一个数据
T& back()
{
return _head->_prev->_date;
}
//const重载
const T& back() const
{
return _head->_prev->_prev;
}
这样一来,list容器也算是简单的完成了。list相较vector和string,最大区别就是迭代器要更加巧妙一点了。
最后,感谢大佬看到最后,欢迎评论改进。