STL之list学习&模拟实现

Posted 玄鸟轩墨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL之list学习&模拟实现相关的知识,希望对你有一定的参考价值。

写在前面

到这里我们就知道知道STL的具体的框架了,里面的一些函数你会发现用法都一样。这个博客主要谈一下迭代器的分封装,前面我们蹙额的string和vector的迭代器都是原生指针,但是今天的却不一样。在这里你会发现既然我们的string和vector都可以支持下标访问,为何还要存在迭代器?今天的list你就会发现它不支持下标,我们的STL为了统一性,都支持了迭代器。

list认识

我们先来看一下list里面的函数,最关键的是里面的构造函数,这里面我们先看用法,先不用关心里面的原理.关于那些比较熟悉的函数这里我们就不谈了,后面实现的时候都会谈到的.

STL之list学习&模拟实现_#include

构造函数

这里面存在4个构造函数,其中我们吧比较常用的拿出来就可以了.

STL之list学习&模拟实现_迭代器_02

构造函数

接口说明

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;

STL之list学习&模拟实现_i++_03

再看一个不带参的构造函数.

#include <iostream>
#include <list>

using namespace std;

int main()

list<int> l;
return 0;

STL之list学习&模拟实现_#include_04

其余的我们这里都不演示了,前面我们学习的时候都用过,没必要浪费大家的时间了.

迭代器

这里我们还是首先首先把迭代器给大家分享了,这里还是正向迭代器等等那四种,用法上是没有新意的,这里我们就还是演示一种吧.

STL之list学习&模拟实现_迭代器_05

int main()

list<int> l(10, 1);
list<int>::iterator it = l.begin();
while (it != l.end())

cout << *it << " ";
++it;

return 0;

STL之list学习&模拟实现_#include_06

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;

STL之list学习&模拟实现_i++_07

sort()

list他自己内置了一个sort函数,具体的用法倒是比较简单的,我们先使用一下,里面的比较器是对于自定类型的,这个到后面我们在谈.

STL之list学习&模拟实现_迭代器_08

#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;

STL之list学习&模拟实现_#include_09

list的sort函数的性能有点低,一般我们是不使用的,这博客面我会专门分享一下这个函数.

merge()

merge(()的本质就是合并两个有序链表,记住是有序,也就是我们在合并之前最好把这两个链表拍一下序.

STL之list学习&模拟实现_i++_10

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;

STL之list学习&模拟实现_迭代器_11

注意,当我们时候用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;

STL之list学习&模拟实现_i++_12

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;

STL之list学习&模拟实现_#include_13

sort性能

list里面的sort本质是归并排序,list的排序数据比较多的就是一个废物,我们在想一件事,我们STL里面的算法库里面不是存在一个排序算法吗,这里为何list还要内置一个.我们先来看看算法库里面.

STL之list学习&模拟实现_#include_14

#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;

STL之list学习&模拟实现_i++_15

迭代器分类

我们发现上面报了一堆的错误,这里也不要让大家思考了,我们这里可以观察到事情,我们把它称为随机迭代器,这里我们就有点疑惑了,难道迭代器还会分类吗?我们好象从没有见过,主要是我们之前淡化了了这个知识点.

STL之list学习&模拟实现_#include_16

我们们观察一下前面学的string和vector的迭代器种类,顺便把迭代器的种类拿出来.

STL之list学习&模拟实现_#include_17

迭代器分类

  • 单项迭代器
  • 双向迭代器
  • 随机迭代器

单项迭代器

我们先来谈一下这个迭代器,单项迭代器是只能够++,其中这个的stl对应的是下面的几个.

STL之list学习&模拟实现_#include_18

双向迭代器

这里的我就不在截图了,双向迭代器支持++或者--,这种还是比较多的,比如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;

STL之list学习&模拟实现_i++_19

我们发现如果数据量在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;

STL之list学习&模拟实现_迭代器_20

迭代器封装

到这里我们就可以看看迭代器的封装了,在之前我们都是用的原生指针,本质原因就是他们的物理空间是连续的,这里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;

STL之list学习&模拟实现_i++_21

迭代器实现

我们的大致结构就是下面的,我们把第一个元素当作begin,哨兵位当作end,至于为何这么做还是存在一定的理由,不过要到后面的一个博客分析了,这涉及到反向迭代器.

STL之list学习&模拟实现_i++_22

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;

STL之list学习&模拟实现_迭代器_23

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;

STL之list学习&模拟实现_迭代器_24

现在我们已经确定了普通的迭代器是正常使用的,我们现在需要看看看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学习&模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

STL之list介绍及实现(list接口模拟实现list)

STL之list介绍及实现(list接口模拟实现list)

STL之list介绍及实现(list接口模拟实现list)

C++从青铜到王者第十三篇:STL之list类的模拟实现

C++STL之stackqueue的使用和模拟实现+优先级队列(附仿函数)+容器适配器详解

模拟实现STL库