一:list的节点和构造函数
<1>:_list_node
list的底层是带头双向循环链表,所以每个节点中除了存储val和next,还需要存储前一个节点prev
template < class T >
struct _list_node
{
T _val;
_list_node< T> * _next;
_list_node< T> * _prev;
_list_node ( const T& val = T ( ) )
: _val ( val)
, _next ( nullptr )
, _prev ( nullptr )
{ }
} ;
<2>: private成员与构造函数
因为list是带头双向循环链表,所以我们只用一个成员_head记录哨兵位的头就可以 所以构造函数我们只需要构造哨兵位的头节点,就可以完成list的构造,其余节点可以通过push_back,push_front,insert插入 当然我们还可以通过迭代器直接构造一个链表,这个我们等讲完迭代器再说
public :
list ( )
{
CreateHead ( ) ;
}
private :
void CreateHead ( )
{
_head = new node;
_head-> _next = _head;
_head-> _prev = _head;
}
node* _head;
二:迭代器(iterator)
回想一下,再模拟实现string和vector时,我们迭代器都是原生指针进行封装 即char*
和T*
,const迭代器就是const char*
,const T*
现在list还能用原生指针么,显然不行,光是++这个操作原生指针就不支持,_list_node不是连续的,++一下不知道跑哪去了,所以我们需要用一个类封装list的迭代器,并在里面重载++,–,!=,==,*,->等操作符 以*为例,可能你想这么做
template < class T >
struct _list_iterator
{
typedef _list_node< T> node;
typedef _list_iterator< T> self;
node* _pnode;
_list_iterator ( node* pnode)
: _pnode ( pnode)
{ }
T& operator * ( )
{
return _pnode-> _val;
}
} ;
这样做确实没错,那么const迭代器呢,像下面这样重载一个const版本?
template < class T >
struct _list_iterator
{
typedef _list_node< T> node;
typedef _list_iterator< T> self;
node* _pnode;
_list_iterator ( node* pnode)
: _pnode ( pnode)
{ }
T& operator * ( )
{
return _pnode-> _val;
}
const T& operator * ( ) const
{
return _pnode-> _val;
}
} ;
如果这么做,那就戳啦 后面的const修饰的是this指针,也就是迭代器自己,而不是修饰迭代器指向的值 所以这样不是const版本的迭代器,迭代器指向的值仍然能被修改,只是迭代器不能被修改 那我们就在封装一个类叫const _list_iterator,然后把返回值改成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* _pnode;
_list_iterator ( node* pnode)
: _pnode ( pnode)
{ }
self& operator ++ ( )
{
_pnode = _pnode-> _next;
return * this ;
}
self& operator -- ( )
{
_pnode = _pnode-> _prev;
return * this ;
}
self operator ++ ( int )
{
self tmp ( * this ) ;
_pnode = _pnode-> _next;
return tmp;
}
self operator -- ( int )
{
self tmp ( * this ) ;
_pnode = _pnode-> _prev;
return tmp;
}
bool operator != ( const self& s)
{
return _pnode != s. _pnode;
}
bool operator == ( const self& s)
{
return _pnode == s. _pnode;
}
Ref operator * ( )
{
return _pnode-> _val;
}
Ptr operator -> ( )
{
return & _pnode-> _val;
}
} ;
我们将模板参数改为3个,第一个还是T,后面两个一个是Ref,一个是Ptr 这样有啥用呢? 前面我们重载operator*的返回类型就可以写成Ref,而不是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) ;
}
对于const迭代器就传const T&,const T*,普通迭代器就传T&,T*,完美解决 而begin就直接返回哨兵位的下一个位置,end就更妙了,直接返回哨兵位,这是它循环的特性造成的,走到最后一个节点后就会走回哨兵位 至于具体如何实现这几个重载,就不赘述,代码十分简单,相信你一看就懂 唯一需要注意的是operator->,它的返回类型是T*
,而如果T是一个非内置类型 比如Date,我们想访问里面的year,是不是得要两个->,就像这样it->->year
,第一个->返回的是Date*
,我们还需要再一个->才能访问到year 编译器对此做了优化,实际上使用时我们只需要一个->就能访问到year
三:insert与erase
<1>: insert
insert通过传入迭代器来进行插入,注意的是它是在pos位置之前插入,这样头插因为有哨兵位存在也可以进行,而如果在pos位置之后插入,尾插就得特殊考虑了
void insert ( iterator pos, const T& x)
{
assert ( pos. _pnode) ;
node* cur = pos. _pnode;
node* prev = cur-> _prev;
node* newnode = new node ( x) ;
prev-> _next = newnode;
newnode-> _prev = prev;
newnode-> _next = cur;
cur-> _prev= newnode;
}
通过复用insert,很快就能实现push_back和push_front
void push_back ( const T& x)
{
insert ( end ( ) , x) ;
}
void push_front ( const T& x)
{
insert ( begin ( ) , x) ;
}
<2>: erase
erase的使用会使迭代器失效,我们需要返回删除后的节点位置保证迭代器仍然有效 注意:哨兵位的头不能删!
iterator erase ( iterator pos)
{
assert ( pos. _pnode) ;
assert ( pos != end ( ) ) ;
node* cur = pos. _pnode;
node* prev = cur-> _prev;
node* next = cur-> _next;
delete cur;
prev-> _next = next;
next-> _prev = prev;
return iterator ( next) ;
}
复用erase实现pop_back和pop_front
void pop_back ( )
{
erase ( -- end ( ) ) ;
}
void pop_front ( )
{
erase ( begin ( ) ) ;
}
四:拷贝构造与operator=
<1> 拷贝构造
有了迭代器就很好实现拷贝构造了,构造头结点后使用迭代器遍历另一个链表,将其每个节点push_back进当前链表
list ( const list< T> & lt)
{
CreateHead ( ) ;
for ( const auto & e : lt)
{
push_back ( e) ;
}
}
<2> operator=
list< T> & operator = ( list< T> lt)
{
swap ( _head, lt. _head) ;
return * this ;
}
<3> 利用iterator进行构造
这里不直接使用iterator作为参数类型,而是使用模板参数,是为了支持所有类型的迭代器
template < class Iterator >
list ( Iterator first, Iterator end)
{
CreateHead ( ) ;
while ( first != end)
{
push_back ( * first++ ) ;
}
}
五:析构和size
<1>: 析构函数
析构函数是先通过clear释放所有的节点,再释放哨兵位的头,完成析构
void clear ( )
{
iterator it = begin ( ) ;
while ( it != end ( ) )
{
it = erase ( it) ;
}
}
~ list ( )
{
clear ( ) ;
delete _head;
_head = nullptr ;
}
<2>:size
遍历链表进行计算节点个数,时间复杂度O(n),不推荐频繁调用
size_t size ( )
{
size_t sz;
iterator it = begin ( ) ;
while ( it != end ( ) )
{
sz++ ;
it++ ;
}
return sz;
}