一、operator++()和operator++(int)
这次认真看了一下list中的重载运算,发现了对operator++()和operator++(int)进行了重载,一时没有搞清楚这两者的区别,其实就是分别重载++前缀和++后缀,正好就以list中迭代器的的源码作为例子,分析一下++前缀和后缀有什么区别
_Self& operator++() {
this->_M_incr();
return *this;
}
_Self operator++(int) {
_Self __tmp = *this;//创建临时变量存储当前值,并最后返回
this->_M_incr();//_M_incr函数就是找到list的下一个节点
return __tmp;
}
可以看到++前缀是先将当前变量的值保存在一个临时变量中,然后+1,但返回的是那个临时变量,所以出现了先赋值再加1的效果
而++后缀则是将当前变量直接+1,并返回,所以出现先加1再赋值的效果。
二、list数据结构
stl中的list数据结构就是平时自己写的那种list,源码如下;
struct _List_node_base { //前驱后驱指针
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template <class _Tp>
struct _List_node : public _List_node_base {
_Tp _M_data; //数据,并且继承了前驱后驱指针
/*每一个list类成员中都只有一个成员变量,就是链表头*/
class _List_base //list的父类
{
...//其他内容省略
protected:
_List_node<_Tp>* _M_node;//头结点,不存放真实数据
}
};
在创建一个list的时候,在构造函数中会创建这个头结点,并且让前驱和后继指针指向自己。源码如下:
_List_base(const allocator_type& __a) : _Base(__a) {
_M_node = _M_get_node();
_M_node->_M_next = _M_node;
_M_node->_M_prev = _M_node;
}//这是list的父类的够赞函数
如下图所示,注意stl中list是环形双向链表,所以在空链表中前驱和后继指针指向自己。
三、list的迭代器
list的迭代器是属于bidirectional_iterator迭代器,拥有++,–,*,->操作符,但是没有+n,-n这样的操作符,所以就是比Random access iterator弱的地方。
并且这个迭代器中有存储的是_List_node_base类型的指针,也就是list中的前驱和后继结构体。
看了两个容器中的迭代器以后,我觉得迭代器最重要的就是几个点
- 迭代器的类型,不同类型的迭代器代表拥有不同的操作运算符,其中Random access iterator所拥有的操作运算符最多,具体可以看前面的博客。
- 迭代器中存储的数据,比如list迭代器中就存储的不是list指针,而是_List_node_base指针,不过其实还是传入的是_List_node指针,只是转换成了父指针,我认为是避免在迭代器类中对数据进行误操作。
四、list中关键算法
①在list的操作函数中,最主要的函数是三个,insert,erase和transfer。其他函数都是对这个函数的封装。
iterator insert(iterator __position, const _Tp& __x) {//在指定位置插入结点,结点数据为__x
_Node* __tmp = _M_create_node(__x);//新建一个结点,然后插入
__tmp->_M_next = __position._M_node;
__tmp->_M_prev = __position._M_node->_M_prev;
__position._M_node->_M_prev->_M_next = __tmp;
__position._M_node->_M_prev = __tmp;
return __tmp;//这里__tmp是_Node类型指针,也就是_List_node类型,但是返回值是一个迭代器,
//个人认为由于迭代器中也就是一个_List_node_base类型的指针,所以_List_node指针可以安全地强转为迭代器类型。
}
iterator erase(iterator __position) {//删除__position位置的元素
_List_node_base* __next_node = __position._M_node->_M_next;//保存待删除结点的前一个结点
_List_node_base* __prev_node = __position._M_node->_M_prev;//保存待删除结点的后一个结点
_Node* __n = (_Node*) __position._M_node;//获取迭代器所指向的结点
__prev_node->_M_next = __next_node;//连接待删除结点的前一个结点和后一个结点
__next_node->_M_prev = __prev_node;
_Destroy(&__n->_M_data);//调用析构函数
_M_put_node(__n);//删除结点
return iterator((_Node*) __next_node);//返回指向该迭代器
}
void transfer(iterator __position, iterator __first, iterator __last) {
if (__position != __last) {
//把first到last的部分移动到position前面
// Remove [first, last) from its old position.
//改变后驱指针
__last._M_node->_M_prev->_M_next = __position._M_node;
__first._M_node->_M_prev->_M_next = __last._M_node;
__position._M_node->_M_prev->_M_next = __first._M_node;
// Splice [first, last) into its new position.
//改变前驱指针
_List_node_base* __tmp = __position._M_node->_M_prev;
__position._M_node->_M_prev = __last._M_node->_M_prev;
__last._M_node->_M_prev = __first._M_node->_M_prev;
__first._M_node->_M_prev = __tmp;
}
}
过程如图所示
②list中一些成员函数的介绍
这些函数都是依靠上面三种函数的封装,就不一一介绍了,但是对这些函数不熟悉,所以介绍一下使用方法
/*将值为value的所有元素删除*/
template <class _Tp, class _Alloc> void list<_Tp, _Alloc>::remove(const _Tp& __value)
/*将list __x和使用merge函数的list进行融合,并且要求两个list都是按递增顺序排列,因为融合的时候也会按照递增顺序融合元素*/
template <class _Tp, class _Alloc> void list<_Tp, _Alloc>::merge(list<_Tp, _Alloc>& __x)
/*整个链表__x插入到__position前面*/
void splice(iterator __position, list& __x)
/*把参数list中__i位置的元素移动到__position的位置*/
void splice(iterator __position, list&, iterator __i)
③list的sort成员函数
STL算法有提供一个sort通用函数,但是只接受Random access iterator迭代器,所以list不能使用,只能使用自己定义的sort函数,像vector就可以使用。
list中的sort函数使用了非递归快排思想,具体过程可以参考这篇博客,讲解非常清晰https://blog.csdn.net/chenhanzhun/article/details/39337331
注意:vector迭代器可能因为扩容换存储空间,而失效,但是list的迭代器不存在这种情况,除非是删除节点,才有可能失效。