STL之list学习&模拟实现
Posted 玄鸟轩墨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL之list学习&模拟实现相关的知识,希望对你有一定的参考价值。
写在前面
到这里我们就知道知道STL的具体的框架了,里面的一些函数你会发现用法都一样。这个博客主要谈一下迭代器的分封装,前面我们蹙额的string和vector的迭代器都是原生指针,但是今天的却不一样。在这里你会发现既然我们的string和vector都可以支持下标访问,为何还要存在迭代器?今天的list你就会发现它不支持下标,我们的STL为了统一性,都支持了迭代器。
list认识
我们先来看一下list里面的函数,最关键的是里面的构造函数,这里面我们先看用法,先不用关心里面的原理.关于那些比较熟悉的函数这里我们就不谈了,后面实现的时候都会谈到的.
构造函数
这里面存在4个构造函数,其中我们吧比较常用的拿出来就可以了.
构造函数 | 接口说明 |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list() | 构造空的list |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
先测试一下构造的list中包含n个值为val的元素.
#include <iostream>
#include <list>
using namespace std;
int main()
list<int> l(10,1);
return 0;
再看一个不带参的构造函数.
#include <iostream>
#include <list>
using namespace std;
int main()
list<int> l;
return 0;
其余的我们这里都不演示了,前面我们学习的时候都用过,没必要浪费大家的时间了.
迭代器
这里我们还是首先首先把迭代器给大家分享了,这里还是正向迭代器等等那四种,用法上是没有新意的,这里我们就还是演示一种吧.
int main()
list<int> l(10, 1);
list<int>::iterator it = l.begin();
while (it != l.end())
cout << *it << " ";
++it;
return 0;
reverse()
这是一个逆置函数,就像我们之前实现的字符串翻转,我们这里来看一下就可以了。
#include <iostream>
#include <list>
using namespace std;
int main()
list<int> l;
for (int i = 0; i < 10; i++)
l.push_back(i);
l.reverse();
for (int& val : l)
cout << val << " ";
cout << endl;
return 0;
sort()
list他自己内置了一个sort函数,具体的用法倒是比较简单的,我们先使用一下,里面的比较器是对于自定类型的,这个到后面我们在谈.
#include <iostream>
#include <list>
#include <time.h>
using namespace std;
int main()
srand((unsigned int)time(NULL));
list<int> l;
for (int i = 0; i < 20; i++)
int ret = rand() % 100;
l.push_back(ret);
for (int& val : l)
cout << val << " ";
cout << endl;
l.sort();
for (int& val : l)
cout << val << " ";
cout << endl;
return 0;
list的sort函数的性能有点低,一般我们是不使用的,这博客面我会专门分享一下这个函数.
merge()
merge(()的本质就是合并两个有序链表,记住是有序,也就是我们在合并之前最好把这两个链表拍一下序.
int main()
srand((unsigned int)time(NULL));
list<int> l1;
list<int> l2;
for (int i = 0; i < 20; i++)
int ret = rand() % 100;
l1.push_back(ret);
for (int i = 0; i < 20; i++)
int ret = rand() % 100;
l2.push_back(ret);
l1.sort();
l2.sort();
l1.merge(l2);
for (int& val : l1)
cout << val << " ";
cout << endl;
return 0;
注意,当我们时候用merge函数,最为参数的哪个list已经被清空了,这一点知道就可以了.
int main()
srand((unsigned int)time(NULL));
list<int> l1;
list<int> l2;
for (int i = 0; i < 20; i++)
int ret = rand() % 100;
l1.push_back(ret);
for (int i = 0; i < 20; i++)
int ret = rand() % 100;
l2.push_back(ret);
l1.sort();
l2.sort();
l1.merge(l2);
cout << l2.size() << endl;
return 0;
unique()
这个函数是去重函数,不过我们还是需要把list排序,简单的去重这里就演示了,看看这个不排序的的情况.这个可以理解为我们只是去一个区域的重.
int main()
list<int> l;
l.push_back(1);
l.push_back(1);
l.push_back(2);
l.push_back(1);
l.push_back(3);
for (int& val : l)
cout << val << " ";
cout << endl;
l.unique();
for (int& val : l)
cout << val << " ";
cout << endl;
return 0;
sort性能
list里面的sort本质是归并排序,list的排序数据比较多的就是一个废物,我们在想一件事,我们STL里面的算法库里面不是存在一个排序算法吗,这里为何list还要内置一个.我们先来看看算法库里面.
#include <algorithm>
int main()
srand((unsigned int)time(NULL));
list<int> l;
for (int i = 0; i < 20; i++)
int ret = rand() % 100;
l.push_back(ret);
for (int& val : l)
cout << val << " ";
cout << endl;
std::sort(l.begin(),l.end());
for (int& val : l)
cout << val << " ";
cout << endl;
return 0;
迭代器分类
我们发现上面报了一堆的错误,这里也不要让大家思考了,我们这里可以观察到事情,我们把它称为随机迭代器,这里我们就有点疑惑了,难道迭代器还会分类吗?我们好象从没有见过,主要是我们之前淡化了了这个知识点.
我们们观察一下前面学的string和vector的迭代器种类,顺便把迭代器的种类拿出来.
迭代器分类
- 单项迭代器
- 双向迭代器
- 随机迭代器
单项迭代器
我们先来谈一下这个迭代器,单项迭代器是只能够++,其中这个的stl对应的是下面的几个.
双向迭代器
这里的我就不在截图了,双向迭代器支持++或者--,这种还是比较多的,比如list,map,set.一般情况下,如果是底层的物理结构不连续的话就是这样子的.
随机迭代器
随机迭代器是既支持++和--,又支持+,-.我们学的vector,string,加上我们后面的要学的适配器deque他们都是随机迭代器.
sort性能
从这里我们就可以看出,算法里面的sort是随机迭代器,但是我们的list只是双向迭代器,这是不适合的,我们可以把随机迭代器传给双向迭代器,但是反过来却不行,,这也是我们list内置sort的原因.
我们需要测试下sort的性能,某种意义上,list里面的sort就是废物.我们先来测试vector和list的排序比较.
int main()
srand((unsigned int)time(NULL));
const int N = 1024*1024;
list<int> l;
vector<int> v;
for (int i = 0; i < N; i++)
int val = rand();
l.push_back(val);
v.push_back(val);
int begin1 = clock();
l.sort();
int end1 = clock();
int begin2 = clock();
std::sort(v.begin(), v.end());
int end2 = clock();
cout << "list : " << (end1 - begin1) << endl;
cout << "sort: " << (end2 - begin2) << endl;
return 0;
我们发现如果数据量在1w以内,这样的话两者的性能还是相差不大的,但是随着数据量的扩大,两者的差异越来越大.
我们再测试一下,如果我们再vector中排序,后面在导list中去,看看他们他们的性能怎么样.你会发现我借助vector排序,排好了我在倒回去都比你厉害,那么请问你不是废物吗.
int main()
srand((unsigned int)time(NULL));
const int N = 1024 * 1024;
list<int> l1;
list<int> l2;
vector<int> v;
for (int i = 0; i < N; i++)
int val = rand();
l1.push_back(val);
l2.push_back(val);
// 排序 l1
int begin1 = clock();
l1.sort();
int end1 = clock();
// 先排序 再移动
int begin2 = clock();
v.reserve(N);
for (int& val : l2)
v.push_back(val);
std::sort(v.begin(), v.end());
// 重新填回去
l2.clear();
for (int& val : v)
l2.push_back(val);
int end2 = clock();
cout << "list : " << (end1 - begin1) << endl;
cout << "vector : " << (end2 - begin2) << endl;
return 0;
迭代器封装
到这里我们就可以看看迭代器的封装了,在之前我们都是用的原生指针,本质原因就是他们的物理空间是连续的,这里list可不是所谓的连续空间,他们可以把一个个节点.那么++和--就出现了问题,所以这里我们需要把迭代器单独拿出来.
我们先来把简陋的迭代器给写好,后面再完善.
template<class T>
struct _list_iterator
typedef _list_node<T> Node;
typedef _list_iterator<T> self; // 这个是需要的,后面我们会 修改这个,如果tepdef就只可以修改这一个就可以了
Node* _node; // 我们给指针 ,节点的空间可能 比较大
_list_iterator(Node* node)
:_node(node)
;
我们来想一下,所谓的++或者--不就是当前指针的节点指向 next或者prev吗,对应出的迭代器对象是不变的,只是里面的内容变了.
int main()
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_front(10);
list<int>::iterator it = l.begin();
while (it != l.end())
cout << &it << endl;
it++;
cout << l.size() << endl;
return 0;
迭代器实现
我们的大致结构就是下面的,我们把第一个元素当作begin,哨兵位当作end,至于为何这么做还是存在一定的理由,不过要到后面的一个博客分析了,这涉及到反向迭代器.
class list
public:
typedef _list_node<T> Node;
typedef _list_iterator<T> iterator;
public:
list()
// 首先 new 一个头节点
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
private:
Node* _head; // 哨兵位
begin() & end()
我们先把普通的给实现出来,后面const修饰的害需要单独讨论.
iterator begin()
return iterator(_head->_next);
iterator end()
return iterator(_head);
operator!= & operator==
到这里我们就知道了我们只是改变迭代器里面的指针指向,具体的不在说了.我们先把==和!=重载出来.这个只需要比较一下里面的指针就可以了.
bool operator!=(const self& t) const
return _node != t._node;
bool operator==(const self& t) const
return !(*this != t);
operator++
所谓得++就是把里面得指针指向下一个地址,本质上也没有什么看点
// 前置
self& operator++()
_node = _node->_next;
return *this;
// 后置
self operator++(int)
self cur = *this; // 调用 的 是拷贝构造
_node = _node->_next;
return cur;
operator--
既然++都是实现出来了,那么--也是比较容易的.
// 前置
self& operator--()
_node = _node->_prev;
return *this;
// 后置
self operator--(int)
self cur(*this);
_node = _node->_prev;
return cur;
operator*
到这里就开始出现问题了,这个运算符是解引用,也就是得到节点里面的数据,我们这里使用T作为返回值,或者是突破类域,其中这个突破类域的方法我们后面再谈,这是标准库里面的做法.
T operator*()
return _node -> _data;
operator->
既然我们提出了一个解决方法,这里先把运算符都写出来,后面遇到问题我们再修改.operator->算是编译器优化了一下,本来该&(iterator._node->\\_date)这样的.
T* operator->()
return &(_node->_data);
测试迭代器
现在我们就可以看看自己的写的迭代器怎么样了,这里我们把情况给测试一下,这里面最大的问题就是后面两个.
我们先来测试一下普通的迭代器,后面的const迭代器是存在些问题的.
int main()
bit::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.push_back(5);
l.push_back(6);
l.push_back(7);
l.push_front(10);
bit::list<int>::iterator it = l.begin();
while(it != l.end())
cout <<*it << endl;
it++;
return 0;
struct A
A(int a = 0,int b= 0)
a1 = a;
a2 = b;
int a1;
int a2;
;
int main()
bit::list<A> l;
l.push_back(A(1,2));
l.push_back(A(1,2));
l.push_back(A(1,2));
bit::list<A>::iterator it = l.begin();
while(it != l.end())
cout << it->a1 << " " << it->a2 <<endl;
it++;
return 0;
现在我们已经确定了普通的迭代器是正常使用的,我们现在需要看看看const修饰的迭代器了.这里我们存在两种方法,第一种是我们重新写一个类 ,不过这个方法重复造轮子了.我们不推荐.剩下的两种我们先暂时放一放,先来看卡我们遇到的问题.
#include <assert.h>
namespace bit
//// 这个 是 list 节点
template<class T>
struct _list_node
_list_node<T>* _next;
_list_node<T>* _prev;
T _data;
// 先看看 构造函数 要不要写
_list_node(const T& t = T())
:_next(nullptr)
, _prev(nullptr)
, _data(t)
;
template<class T>
struct _list_iterator
typedef _list_node<T> Node;
typedef _list_iterator<T> self;
Node* _node;
_list_iterator(Node* node)
:_node(node)
// 析构 拷贝 都不需要
bool operator==(const self& t) const
return !(*this != t);
<以上是关于STL之list学习&模拟实现的主要内容,如果未能解决你的问题,请参考以下文章