list的简单上手和模拟实现
😊本文为小邢原创,CSDN首发
📅发布时间:2022/4/29
🙌欢迎大家👍点赞❤收藏✨加关注
✒本文大约19000字左右
🙏笔者水平有限,如有错误,还望告诉笔者,万分感谢!
🚩有什么问题也可在评论区一起交流哦!
朋友们大家好,真的是我,我改名了😂
前久博主太忙了,就一直没更新,越不更新就越不想更新,这怎么行呢?还是得坚持,下面来看看博主这一篇博文吧!
一.list简单介绍
以下截自于cplusplus.com - The C++ Resources Network中对list的简单介绍:
里面大致的意思就是list是一个顺序容器,支持在任意位置O(1)的插入删除;实质是一个双向带头循环链表。
二.list的简单上手
1、构造一个list对象
·构造一个空对象
list<int> one;
·用元素来构造
比如,我们用10个1来构造一个list
list<int> two(10, 1);
·用一段迭代器区间构造
list<int> three(two.begin(), two.end());
·用一个list对象来构造
list<int> four(two);
2、push_back、push_front和迭代器
说明: const对象要使用const迭代器,该迭代器不支持修改元素值。
int main()
{
list<int> one;
//尾插
one.push_back(1);
one.push_back(2);
//头插
one.push_front(3);
one.push_front(4);
//正向const迭代器
list<int>::const_iterator cit = one.begin();
while (cit != one.end())
{
cout << *cit << " ";
cit++;
}
cout << endl;
//反向const迭代器
list<int>::const_reverse_iterator crit = one.rbegin();
while (crit != one.rend())
{
cout << *crit << " ";
crit++;
}
cout << endl;
//正向迭代器
list<int>::iterator it = one.begin();
while (it != one.end())
{
cout << *it+1 << " ";
it++;
}
cout << endl;
//反向迭代器
list<int>::reverse_iterator rit = one.rbegin();
while (rit != one.rend())
{
cout << *rit-1 << " ";
rit++;
}
cout << endl;
}
3.pop_front、pop_back
头删、尾删~~
int main()
{
list<int> one;
one.push_back(1);
one.push_back(2);
one.push_back(3);
one.push_back(4);
list<int>::const_iterator cit = one.begin();
while (cit != one.end())
{
cout << *cit << " ";
cit++;
}
cout << endl;
one.pop_back();
one.pop_front();
cit = one.begin();
while (cit != one.end())
{
cout << *cit << " ";
cit++;
}
cout << endl;
}
4、assign
该函数的功能是重置list中的内容,也就是用新的内容替换之前list中的内容,替换后list的大小可能会改变。
int main()
{
list<int> one;
one.push_back(1);
one.push_back(2);
one.push_back(3);
one.push_back(4);
for (auto e : one)
{
cout << e << " ";
}
cout << endl;
one.assign(6, 1);
for (auto e : one)
{
cout << e << " ";
}
cout << endl;
}
同样,assign也支持用一段迭代器区间来替换list中的旧内容,博主在这就不予演示了,大家可自行尝试。
5、reverse
逆置元素~~
int main()
{
list<int> one;
one.push_back(1);
one.push_back(2);
one.push_back(3);
one.push_back(4);
for (auto e : one)
{
cout << e << " ";
}
cout << endl;
cout << "After reverse:" << endl;
one.reverse();
for (auto e : one)
{
cout << e << " ";
}
cout << endl;
}
6、sort
排升序~~
int main()
{
list<int> first;
for (size_t i = 0; i < 10; i++)
{
int random = rand() % 10;
first.push_back(random);
cout << random << " ";
}
cout << endl;
first.sort();
cout << "After sort:" << endl;
for (auto e : first)
{
cout << e << " ";
}
cout << endl;
}
以上仅为list的一些基本接口,更多的接口大家可以去网站cplusplus.com - The C++ Resources Network中了解,博主仅在此做基本介绍,对于list的大量细节会在list的模拟实现中介绍。
三.list模拟实现
[声明:模拟实现参考SGI版本]
1、基本结构
为了区分库里的list,我们在List的命名空间里模拟list。list的成员变量是一个节点类型的指向头节点的指针。对于节点类型,他最开始是一个类模板,因为还不知道节点存放元素的类型,当节点实例化后,它里面有一个数值数据和两个指针。
namespace List
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _val;
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
//成员函数
private:
Node* _head;
};
}
对于list的模拟实现来说,与string和list最不同的就是list的迭代器,为了模拟迭代器,我们先把list的构造函数和push_back模拟实现了,其他的接口放在迭代器之后实现。
2、构造函数 and push_back
这里我们先实现默认的构造函数。
list()
{
_head = new Node();//这里的Node会调用默认的构造函数。
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
这里需要注意的是,如果我们直接运行这段代码会报一个错误,原因是我们没有显式书写Node的构造函数,在new Node(x)这里会出错。
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _val;
ListNode(const T& val = T())
:_next(nullptr), _prev(nullptr), _val(val)
{}
};
3、迭代器1.0
思考:对与一个迭代器,其最重要的是能支持遍历,在遍历的过程中,要能支持解引用(*)和 ++两个基本运算符操作。而我们知道,list在物理空间上是不连续的,故不能直接对迭代器++,那有什么办法能帮助我们解决该问题呢?
我们知道,对于链表来说,要访问前一个节点或是下一个节点,无非是得到前一个节点或是下一个节点的地址:
cur=cur->next;
cur=cur->prev;
回顾之前所学的知识,我们想到了可以用运算符重载来实现我们的需求,我们可以重载++,将上面访问节点的操作封装到运算符重载函数里去,当我们使用的时候,就真的好像在使用一段连续的空间似的。不光有++操作,还有像 *等其他的操作,为了更好的维护迭代器的有关操作,我们将这些操作封装到一个类里面去,自此list的迭代器就成为了一个类,而类里面的成员变量,是指向节点的指针,所以我们说,list的迭代器本质还是一个指针,只是我们把一些对指针的操作和该指针一起封装到一个类里面去了,这充分的体现了c++封装的特点。
·成员变量
template<class T>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T> self;
Node* _node;
};
为了代码的简洁,我们给一些复杂的类型名起了别名。
·构造函数
__list_iterator(Node* pnode)//注意,这里不能加const,因为_node可能不是const指针
:_node(pnode)
{}
· operator*
这里我们使用引用返回,不仅是因为但我们函数调用完后,空间还在,更重要的是,这里还要支持值的修改。
T& operator*()
{
return _node->_val;
}
·前置++ and 后置++
//++it
self operator++()
{
_node = _node->_next;
return *this;
}
//it++
self operator++(int)//占位参数
{
self tmp(*this);//默认构造函数
_node = _node->_next;
return tmp;
}
//我们可以发现,前置++比后置++开销要小一些。
这里要补充一点,对于list的迭代器,我们用不着显式的实现拷贝构造函数和析构函数,因为迭代器本质的一个指针,当我们用一个已有的迭代器拷贝构造一个迭代器时,是又创建了一个指针指向同一个节点,所以做的是浅拷贝,系统默认的拷贝构造就能满足需要。而对于析构函数,因为迭代器中没有申请额外的空间资源,所以用不着显式的定义析构函数,系统默认的即可。
有朋友会问,list的每一个节点是申请的空间呀,为什么不需要析构函数?举个例子,当我们遍历完list,迭代器用完了,调用析构函数的时候难道我们要把list销毁吗?更形象的说,list的空间归list管,不归迭代器管,list的空间释放归list来管理。
· operator!= and operator==
bool operator!=(const self& it) const
{
return _node != it._node;
}
到这里,一个简单的正向迭代器就实现了,让我们来测试一下:
namespace List
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _val;
ListNode(const T& val = T())
:_next(nullptr), _prev(nullptr), _val(val)
{}
};
template<class T>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T> self;
Node* _node;
__list_iterator(Node* pnode)
:_node(pnode)
{}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
T& operator*()
{
return _node->_val;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef __list_iterator<T> iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
private:
Node* _head;
};
void test()
{
list<int> lt;
for (size_t i = 1; i <= 4; i++)
{
lt.push_back(i);// 1 2 3 4
}
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
}
int main()
{
List::test();
}
成功遍历!
Node* pnode = _head->_next;
iterator it = _head->_next;
*pnode, *it;
++pnode, ++it;
另外,我们以该注意到,对于Node*和迭代器对象来说,他们俩占用的空间大小相同,都是4byte,并且值都相同,但是他们使用运算符的意义和结果是不相同的。
4、迭代器2.0
实现了正向的非const迭代器,接下来我们要实现正向的const迭代器!与刚才实现的非const迭代器相比,const的迭代器唯一的区别就在于operator*()的返回值上,一个是T&,一个是const T&。有的朋友说,这不简单,再写一个返回值为const T&版本的不就行了!
T& operator*()
{
return _node->_val;
}
//1.
const T& operator*()
{
return _node->_val;
}
//2.
const T& operator*()const
{
return _node->_val;
}
如上,一定是一些朋友们的想法,但其实这是有错的。首先对于第一种写法,虽然返回值改成了const T&,但是这与返回值为T&的operator*构成不了运算符重载,因为函数参数相同。
那改为第二种写法呢?虽然参数不同了,可以构成函数重载了,但是还有问题。对于一个list来说,我们可以用iterator遍历,遍历过程中可以修改list的值,我们亦可以使用const_iterator来遍历,遍历过程中不能对list进行修改。但是抱歉的是,对于第二种的写法,const_iterator是可以修改list的值的。原因我们来一张图:
我们可以看到,两个operator*函数的this指针的类型是不同的。而我们知道,当我们定义一个迭代器时,定义的都是非const的迭代器,要不然我们怎么遍历?所以在我们调用operator *()时,无论const_iterator还是Iterator调用的都是第一个operator *(),因为他参数最匹配。
这样一来,用const_iterator遍历list时,也能修改list的值了。
随机又出现了一个更致命的问题,对于const修饰的list,本应该只能const_iterator来遍历,这下好了,iterator也能遍历了。
怎么办,这也不行,那也不行,这里究竟要怎么设计才能满足我们的需求呢?
有朋友可能会说,在实现一个const_iterator类不就行了,这确实能实现,但是两段代码就operator*那里不一样,代码冗余,不符合C++的风格。
当我们实在想不通的时候,让我们看看STL源码是怎样做的。上源码!!
可以看到,源码在实现上对于迭代器类模板有三个模板参数,相信朋友们第一次看到这里和我一样,都是迷糊的!为什么有三个模板参数?当我看到list中的代码时,我恍然大悟!
[朋友们,你恍然大悟了吗?]
让我们先来解释前两个模板参数:
当我们是iterator时,我们在list里定义类型,传给模板参数的类型是<T, T&>,那迭代器那边在推演类型的时候,Ref就是T&;而当我们是const_iterator是,我们在list里定义类型,传给模板参数的类型是<T, const T&>,那迭代器那边在推演的时候,Ref就是const T&。Ref是什么?就是operator*的返回值呀!这样就很好的解决了问题。不得不说,STL更像是一个艺术品!
明白了这一点后,让我们来想一想第三个模板参数是干啥的?
我们知道,每一个容器都不知是存放内置类型,还可能存放自定义类型。对于迭代器,我们可以看作是一个指针,指针要访问自定义类型里面的元素,我们要用到的运算符是 ->,所以我们需要重载运算符 ->。
T* operator->()
{
return &_node->_val;
}
老问题,如果是const迭代器呢? 所以,就引出了第三个模板参数。
注意:我们这样实现 -> 重载,再用的时候本来应该是 it->->elem,但是这样代码的可读性较差,所以编译器在这里做了优化,省略了一个 -> ,且对于所有的->重载都会做这一优化。
明白了这两点,让我们浅浅的敲一遍代码吧~~ (以下代码添加了几个接口)
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
__list_iterator(Node* pnode)
:_node(pnode)
{}
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;
}
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &_node->_val;
}
bool operator==(const self& it)const
{
return _node == it._node;
}
bool operator!=(const self& it)const
{
return !(*this == it);
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
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);
}
}
5、迭代器3.0
实现了正向的迭代器,让我们进一步完善迭代器,实现一下反向的迭代器。对于反向迭代器,与正向迭代器的区别就在于++和–的方向不一样,所以我们可以用复用正向迭代器来实现反向迭代器。
但是,通过阅读源码,我发现源码中,使用了一个一劳永逸的方法——适配器。
什么是适配器呢?也就是,当一个容器的正向迭代器实现后,会自动适配出相应的反向迭代器。下面是适配器的实现:
namespace List
{
template<class Iterator, class Ref, class Ptr>
class reverse_iterator
{
typedef reverse_iterator<Iterator,Ref,Ptr> self;
public:
reverse_iterator(Iterator it)
:_it(it)
{}
self& operator++()
{
--_it;
return *this;
}
self operator++(int)
{
self tmp(_it);
--_it;
return tmp;
}
self& operator--()
{
++_it;
return *this;
}
self operator--(int)
{
self tmp(_it);
++_it;
return tmp;
}
Ref operator*()
{
Iterator prev = _it;
return *--prev;//--优先级更高
}
Ptr operator->()
{
return &operator*();
}
bool operator==(const self& it)
{
return _it == it._it;
}
bool operator!=(const self& it)
{
return !(_it == it._it);
}
private:
Iterator _it;
};
}
class list
{
typedef ListNode<T> Node;
public:
typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin());
}
}
这里的实现,与正向迭代器类似,只是这里的成员变量变成了一个迭代器模板,可以推演出传过来的迭代器。
此外,这里反向迭代的rbegin()和rend()与我们所想像的不同:
这里我们看到,STL可能相与正向迭代器来一个对称之美,所以正向迭代器的开始,就是反向迭代器的结束,正向迭代器的结束就是反向迭代器的开始。所以反向迭代器中的operator*中实现的时候要先 – 在 *。
6、迭代器4.0
上面是我们实现的反向迭代器,但是源代码里面反向迭代器模板参数只有一个迭代器模板。让我们一起来简单学习一下大佬的设计:
namespace List
{
template<class Iterator>
class reverse_iterator
{
typedef reverse_iterator<Iterator> self;
typedef typename Iterator::reference Ref;
typedef typename Iterator::pointer Ptr;
public:
Ref operator*()
{
Iterator prev = _it;
return *--prev;//--优先级更高
}
Ptr operator->()
{
return &operator*();
}
private:
Iterator _it;
};
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef Ref reference;
typedef Ptr pointer;
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &_node->_val;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
//typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
//typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef reverse_iterator<const_iterator> const_reverse_iterator;
typedef reverse_iterator<iterator> reverse_iterator;
}
}
这里主要做了这几个工作:一是在struct _ _list _iterator中定义了内嵌类型,主要是因为我们要拿到模板参数中的Ref和Ptr,而对于模板参数我们不能直接拿到,所以我们要定义内嵌类型。之后,我们在反向迭代器类模板中,再通过Iterator取出内嵌类型。我们会发现,有一个typename?他是干什么的?
由于该反向迭代器只有一个模板参数,实现的operator*等函数的返回值类型要去Iterator里面取。但是编译会出错,因为Iterator在未实例化之前,是一个类模板,所以取不到里面的内嵌类型。typename的作用就是当编译器编译到这里时,将这个类模板记下来,先跳过,当他实例化之后,再去取里面的内嵌类型。
但是,这样实现有一个致命的缺陷,就是若该代码用于vector等迭代器是原生指针的容器会出错,因为原生指针里面没有内置类型。
如何解决呢?STL里面用的时迭代器的萃取技术和模板特化技术,这两个技术比较复杂,感兴趣的朋友可以自行研究。
到此,list的迭代器简单的模拟出来了。
7、insert
这里的我们模拟的insert是在一个迭代器位置前面插入一个元素.
void insert(iterator pos, const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._node;
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
cur->_prev = newnode;
newnode->_next = cur;
}
8、push_back/push_front
对于这两个接口我们可以复用insert来实现。
void push_back(const T& val)
{
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
9、erase
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
这里需要说明的是,erase是在一个迭代器位置删除一个元素后,要返回下一位置的迭代器,是因为这里存在迭代器失效的问题。迭代器失效及迭代器所指向的位置不存在或是意义改变了。显然,当我们删除一个节点后,该节点所占用的内存空间被释放,即迭代器所指向的空间失效了,所以我们为了避免迭代器失效,我们在删除节点后,要返回下一节点的迭代器。
10、pop_back/pop_front
同样,我们可以复用erase来实现pop_back和pop_front。
void pop_back()
{
iterator tmp = end();
erase(--tmp);
}
void pop_front()
{
erase(begin());
}
11、clear
该函数的功能是清空list,也就是除了头节点全部删除。该函数也可用erase来实现。
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);//这里要用it来接收,不然迭代器会失效
}
}
12、析构函数
析构函数就是将所有空间释放,包括头节点。
~list()
{
clear();
delete _head;
_head = nullptr;
}
13、拷贝构造传统写法
list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (auto e : lt)
{
push_back(e);
}
}
14、赋值运算符重载传统写法
list<T>& operator=(const list<T>& lt)
{
if (this != <)
{
clear();
for (auto e : lt)
{
push_back(e);
}
}
return *this;
}
15、迭代器区间拷贝构造
template<class InputIterator>//命名暗示可以用任意迭代器访问
list(InputIterator first, InputIterator last)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);
}
}
这里有一个恶心的问题,就是该函数会和STL里另一个函数产生冲突:
list(size_t n, const T& val = T())
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
比如,我现在要用5个1来构造一个list对象,程序实际上调用的是第一个函数,也就是迭代器区间拷贝构造函数,且会给你报一个错。
list<int> lt(5, 1);
让我们来比较一下两个函数:
对于5来说,他是int类型的,当调用的函数是左边的函数时,模板参数会推演为int类型,而右边5传过去后还要隐式类型转换为size_t类型。在编译期看来,左边的函数更匹配,所以调用左边的。
如何解决呢?
我们可以重载一个参数为int的用n个值构造list的构造函数:
list(int n, const T& val = T())
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
由此,我们得出结论,在调用函数时,编译器会去寻找最匹配的函数:有类型转换的,编译器会去调用没有类型转换的;有现成的类型,不去调用需要类型推演的函数。
16、拷贝构造和赋值运算符现代写法
有了迭代其区间的拷贝构造,我们就可以来实现一下有一个对象来拷贝构造的现代写法。
list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
list<T> tmp(lt.begin(), lt.end());//tmp生命周期结束会自动调用析构函数
std::swap(_head, lt._head);//交换指针
}
list<T>& operator=(list<T> lt)//这里是传值传参,函数调用完lt会自动调用析构函数。
{
std::swap(_head, lt._head);
}
现代写法相对于传统写法确实要简洁一些,现代写法的核心思想就是“找人帮自己办事”。
其他的接口我们就不一一模拟实现了,我们只是将常用到的函数带大家来模拟实现以下,剩下的函数还有很多,大家可自行去查阅学习。
四.总结
本篇博文主要带大家来上手了list的使用,并把常用的几个接口模拟实现了一遍。其中最精彩的莫过于迭代器的模拟实现了,他充分的体现了C++的泛型编程之美,下望大家能反复体会。另外,对于迭代器的模拟实现与STL源码中的任有一些不同,因为STL源码中还用到了一些较为复杂的技术,比如迭代器萃取或是模板特化技术。由于较为复杂,就没有将其放在本文中介绍,想要挑战自己的朋友们可以自己去了解。还有就是,代码当中仍有一些不足的地方,比如我们的const_iteartor只能遍历const的容器,而STL中的const_iterator不仅可以遍历const容器,也可以遍历非const的容器,这是显而易见的事。所以我们的代码仍有改进优化的空间。
以下是完整的代码:
#pragma once
#include<iostream>
#include<list>
#include<assert.h>
#include"reverse_iterator.h"
using namespace std;
namespace List
{
void test2();
void test3();
void test4();
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _val;
ListNode(const T& val = T())
:_next(nullptr), _prev(nullptr), _val(val)
{}
};
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef Ref reference;
typedef Ptr pointer;
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
__list_iterator(Node* pnode)
:_node(pnode)
{}
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;
}
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &_node->_val;
}
bool operator==(const self& it)const
{
return _node == it._node;
}
bool operator!=(const self& it)const
{
return !(*this == it);
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
//typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
//typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef reverse_iterator<const_iterator> const_reverse_iterator;
typedef reverse_iterator<iterator> reverse_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);
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin());
}
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list(int n, const T& val = T())
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
list(size_t n, const T& val = T())
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
void insert(iterator pos, const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._node;
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = 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* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
void pop_back()
{
iterator tmp = end();
erase(--tmp);
}
void pop_front()
{
erase(begin());
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
//传统写法
/*list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (auto e : lt)
{
push_back(e);
}
}
list<T>& operator=(const list<T>& lt)
{
if (this != <)
{
clear();
for (auto e : lt)
{
push_back(e);
}
}
return *this;
}*/
//现代写法
list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
list<T> tmp(lt.begin(), lt.end());
std::swap(_head, lt._head);
}
list<T>& operator=(list<T> lt)
{
std::swap(_head, lt._head);
}
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);
}
}
private:
Node* _head;
};
}
好啦,如果你认真的读到了这里,还望给UP来个一键三联,你的支持就是UP最大的动力。
【本文为作者原创,未经允许禁止私自转载,抄袭,一经发现,将会受法律责任】