list 前言
1、list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2.、list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3、与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好,因为插入删除时不用挪动数据。
4、与其他序列式容器相比,list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息,比如存储前驱和后继指针。
list 的模拟实现
结点的定义
根据双向链表,我们不仅需要存储结点的值,还需要存储前驱指针(_prev)和后继指针(_next),前驱指针指向当前结点的前一个结点,后继指针指向当前结点的下一个结点。
template<class T>
struct ListNode
{
ListNode(const T& val = T())
:_prev(nullptr)
,_next(nullptr)
,_val(val)
{}
ListNode<T>* _prev;//前驱
ListNode<T>* _next;//后继
T _val;
};
链表的定义
构造函数:
链表中没有结点时,链表的哨兵位的前驱和后继指针都指向自己。
特别注意的是,哨兵位的_val不可以存储链表的结点的个数,因为我们不知道链表里面的数据是什么类型,假设是char类型的链表,char能表示的最大值是127,如果链表里面结点的个数超过127,即超出了char能表示的取值范围,则结点个数的记录没有意义。
template<class T>
class list
{
typedef ListNode<T> Node;//不放在public里面,避免暴露在类外面
public:
void empty_init()//用于初始化哨兵位
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();//复用
}
private:
Node* _head;//哨兵位
};
迭代器:
为什么要封装迭代器:
list 和 vector 的迭代器有所不同:
1、vector 的数据在底层是连续存储的,假设下面的 it 是vector 的迭代器,*it 便可以得到 it 位置的数据(迭代器的本质是指针,对指针解引用就可以拿到指针指向的内容),++it 就可以访问下一个结点(因为底层是连续的)
2、由于 list 的底层不是连续的,++it 不能够访问下一个结点,而且一个结点里面存储了结点的数据、前驱和后继指针,*it 不能直接拿到我们想要的数据,所以我们需要对 list 的迭代器进行封装,以满足我们的需求
list<int>::iterator it = l.begin();
while (it != l.end())
{
cout << *it<< " ";
++it;
}
迭代器的实现:
为什么不可以直接在 list 类里面对迭代器(指针,即 Node*)的 ++ 和解引用进行运算符重载,而是单独写迭代器的类?
因为 Node* (指针)是内置类型,内置类型不可以运算符重载,自定义类型才可以运算符重载,所以我们需要把 Node* 封装为 自定义类型,方便进行运算符重载。
其中特别关注 -> 的运算符重载,返回的是结点的_val 的地址。
正向迭代器:
template<class T,class Ref,class Ptr>//Ref为引用(T&),Ptr为指针(T*)
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;//把迭代器重命名为self
Node* _node;//把迭代器指向的结点封装起来
__list_iterator(Node* x)
:_node(x)//方便隐式类型转换
{ }
self& operator++()//前置++,返回迭代器,返回值带引用,减少拷贝
{
//return _node->_next;
//这个写法不行,没有满足前置++的先++后使用,即_node没有指向下一个结点
_node = _node->_next;
return *this;//this指针指的是迭代器(包含结点)
}
Ref operator*()//解引用返回结点的_val的引用
{
return _node->_val;
}
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;//this里的node往前走
return tmp;
}
Ptr operator->()//箭头返回返回结点的_val的地址
{
return &_node->_val;
}
bool operator==(const self& s)
{
return _node == s._node;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
};
反向迭代器:
反向迭代器和正向迭代器在结构上具有对称性。
我们可以复用正向迭代器来实现反向迭代器。
为了让正向迭代器和反向迭代器在下面的 while 的使用规则上一致,反向迭代器的 ++it 实际上是走向当前结点的前驱。
*it 中,我们需要先创建变量 tmp,因为反向迭代器是从 _head 开始的,哨兵位的值不是我们想要的值,我们需要 -- tmp ,使 tmp 指向当前结点的前驱,再去访问 tmp 里面的值
list<int>::iterator it = l.begin();
while (it != l.end())
{
cout << *it<< " ";
++it;
}
template<class Iterator,class Ref,class Ptr>
class Reverse_Iterator
{
//反向迭代器复用迭代器的类
Iterator _it;
typedef Reverse_Iterator<Iterator, Ref, Ptr> self;
//同样把反向迭代器重命名为 self
Reverse_Iterator(Iterator it)
:_it(it)
{ }//传一个迭代器,拷贝出一个迭代器
self& operator++()
{//++it,反向迭代器的++是走前驱,即访问前一个元素
--_it;
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!=(self& s)
{
return _it != s._it;
}
Ref operator*()
{//解引用
Iterator tmp(_it);
return *(--tmp);
//根据反向迭代器的结构,需要先--指向当前位置的前一个结点
//再复用正向迭代器的解引用,访问节点的值
}
Ptr operator->()
{
//return &(*(_it));
return &(operator*());
}
};
迭代器相关的实现:
重命名时,把模板实例化,Ref 实例化为 T&,Ptr 实例化为 T*。
typedef __list_iterator<T, T&, T*> iterator;//要放在public里面,iterator才可以在类外面访问
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef Reverse_Iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_Iterator<iterator,const T&,const T*> const_reverse_iterator;
reverse_iterator rbegin()
{
return _head;
}
reverse_iterator rend()
{
return _head->_next;
}
const_reverse_iterator rbegin()const
{
return _head;
}
const_reverse_iterator rend()const
{
return _head->_next;
}
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;//最后一个结点的下一个结点
}
const_iterator begin()const//记得写参数后面的const
{
return _head->_next;//权限缩小
}
const_iterator end()const
{
return _head;
}
插入 insert:
假设要插入的结点为 newnode,根据图示可以看出,我们需要修改pos、pos的前一个结点和newnode 的前驱和后继指针,便可以完成插入操作。由于把迭代器封装起来,访问 pos 的前一个结点时,先用 . 操作符访问 pos 迭代器指向的结点,进而获得 pos 位置的前一个结点 prev。insert 函数的返回值为新插入结点的迭代器(隐式类型转换)。
iterator insert(iterator pos, const T& val)//在pos之前插入
{
Node* prev = pos._node->_prev;
//因为函数参数用了迭代器这个类,而不是像之前直接访问pos位置的指针
//我们需要用.来访问迭代器pos里面的成员_node
Node* newnode = new Node(val);
newnode->_next = pos._node;
newnode->_prev = prev;
pos._node->_prev = newnode;
prev->_next = newnode;
return newnode;
}
尾插 push_back:
void push_back(const T& val)
{
insert(end(), val);
}
头插 push_front:
void push_front(const T& val)
{
insert(begin(), val);
}
删除 erase:
erase 的函数返回值为 pos 位置的下一个节点。删除结点的时候需要修改 posprev 的后继和posnext 的前驱指针,否则链表的连接会出现问题。
iterator erase(iterator pos)
{
assert(pos != end());
Node* posprev = pos._node->_prev;
Node* posnext = pos._node->_next;
posprev->_next = posnext;
posnext->_prev = posprev;
delete pos._node;
return posnext;
}
尾删 pop_back + 头删 pop_front:
由于 end() 是指向链表最后一个结点的下一个结点,需要 --end() 才可以访问链表的最后一个结点。
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
拷贝构造:
拷贝构造的参数要传引用,如果不传引用,传参后又调用拷贝构造来形成形参,形参又调用拷贝构造,因此造成无穷递归。
list(list<T>& l)//拷贝构造要传引用,否则会无穷递归
{
//拷贝之前需要把先处理哨兵位
empty_init();
for (const auto& e : l)
{
push_back(e);//用迭代器遍历并尾插
}
}
析构函数:
void clear()
{
//clear不处理哨兵位
iterator it = begin();//迭代器已经封装好了,不用像上面那么写
while (it != end())
{
it = erase(it);//需要迭代 it
}
}
~list()
{
clear();//复用
delete _head;//处理哨兵位
_head = nullptr;//delete只是清掉内容,但是指针还是在的,指针也要处理
}
其他函数:
= 的运算符重载函数不可以传引用,传了引用之后,= 左右的链表就交换了,不符合 = 的原则。
void swap(list<T>& s)
{
std::swap(_head, s._head);
}
list<T>& operator=(list<T> s)
{//list1 = list2
swap(s);
return *this;
}
全部代码合并:
template<class T>
struct ListNode
{
ListNode(const T& val = T())
:_prev(nullptr)
,_next(nullptr)
,_val(val)
{}
ListNode<T>* _prev;//前驱
ListNode<T>* _next;//后继
T _val;
};
template<class T,class Ref,class Ptr>//Ref为引用,Ptr为指针
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
__list_iterator(Node* x)
:_node(x)
{ }
//写构造函数,在 iterator end(){ return _head; }时才可以隐式类型转换,否则返回值和返回值类型不匹配
self& operator++()//前置++,返回迭代器,返回值带引用,减少拷贝
{
//return _node->_next;
_node = _node->_next;
return *this;//this指针指的是迭代器
}
self operator++(int)//后置++
{
//return _node->_next;
/*Node* tmp = _node;
_node = _node->_next;
return tmp;*/
self tmp(*this);//调构造函数
_node = _node->_next;
return tmp;
}
self& operator--()//前置--
{
//return _node->_prev;
_node = _node->_prev;
return *this;
}
self operator--(int)//后置--
{
//return _node->_prev;
/*Node* tmp = _node;
_node = _node->_prev;
return tmp;*/
self tmp(*this);
_node = _node->_prev;//this里的node往前走
return tmp;
}
Ref operator*()//解引用返回结点的_val的引用
{
return _node->_val;
}
Ptr operator->()//箭头返回返回结点的_val的地址
{
return &_node->_val;
}
bool operator==(self& s)
{
return _node == s._node;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
};
template<class Iterator,class Ref,class Ptr>
class Reverse_Iterator
{
//反向迭代器复用迭代器的类
Iterator _it;
typedef Reverse_Iterator<Iterator, Ref, Ptr> self;
Reverse_Iterator(Iterator it)
:_it(it)
{ }//传一个迭代器,拷贝出一个迭代器
self& operator++()
{//++it,反向迭代器的++是走前驱,即访问前一个元素
--_it;
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!=(self& s)
{
return _it != s._it;
}
Ref operator*()
{//解引用
Iterator tmp(_it);
return *(--tmp);
//根据反向迭代器的结构,需要先--指向当前位置的前一个结点
//再复用正向迭代器的解引用,访问节点的值
}
Ptr operator->()
{
//return &(*(_it));
return &(operator*());
}
};
template<class T>
class list
{
typedef ListNode<T> Node;//不放在public里面,避免暴露在外面
public:
typedef __list_iterator<T, T&, T*> iterator;//要放在public里面,iterator才可以在类外面访问
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef Reverse_Iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_Iterator<iterator,const T&,const T*> const_reverse_iterator;
reverse_iterator rbegin()
{
return _head;
}
reverse_iterator rend()
{
return _head->_next;
}
const_reverse_iterator rbegin()const
{
return _head;
}
const_reverse_iterator rend()const
{
return _head->_next;
}
void empty_init()
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;//最后一个结点的下一个结点
}
const_iterator begin()const//记得写参数后面的const
{
return _head->_next;//权限缩小
}
const_iterator end()const
{
return _head;
}
iterator insert(iterator pos, const T& val)//在pos之前插入
{
Node* prev = pos._node->_prev;
//因为函数参数用了迭代器这个类,而不是像之前的pos位置的指针
//我们需要用.来访问迭代器pos里面的成员_node
Node* newnode = new Node(val);
newnode->_next = pos._node;
newnode->_prev = prev;
pos._node->_prev = newnode;
prev->_next = newnode;
return newnode;
}
void push_back(const T& val)
{
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* posprev = pos._node->_prev;
Node* posnext = pos._node->_next;
posprev->_next = posnext;
posnext->_prev = posprev;
delete pos._node;
return posnext;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
void clear()
{
//clear不处理哨兵位
//怎么调用迭代器?
//list<T>::iterator it = begin();
//while (it != end())
//{
//pop_front(it); 头删里面不用传参
//++it;
//}
iterator it = begin();//迭代器已经封装好了,不用像上面那么写
while (it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;//delete只是清掉内容,但是指针还是在的,指针也要处理
}
list(list<T>& l)//拷贝构造要传引用,否则会无穷递归
{
//拷贝之前需要把先处理哨兵位
empty_init();
for (const auto& e : l)
{
push_back(e);
}
}
void swap(list<T>& s)
{
std::swap(_head, s._head);
}
list<T>& operator=(list<T> s)
{//list1 = list2
swap(s);
return *this;
}
private:
Node* _head;//哨兵位的头结点
};