到这里我们就知道知道STL的具体的框架了,里面的一些函数你会发现用法都一样。这个博客主要谈一下迭代器的分封装,前面我们蹙额的string和vector的迭代器都是原生指针,但是今天的却不一样。在这里你会发现既然我们的string和vector都可以支持下标访问,为何还要存在迭代器?今天的list你就会发现它不支持下标,我们的STL为了统一性,都支持了迭代器。
我们先来看一下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
#include
using namespace std;
int main()
{
list<int> l(10,1);
return 0;
}
再看一个不带参的构造函数.
#include
#include
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;
}
这是一个逆置函数,就像我们之前实现的字符串翻转,我们这里来看一下就可以了。
#include
#include
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;
}
list他自己内置了一个sort函数,具体的用法倒是比较简单的,我们先使用一下,里面的比较器是对于自定类型的,这个到后面我们在谈.
#include
#include
#include
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(()的本质就是合并两个有序链表,记住是有序,也就是我们在合并之前最好把这两个链表拍一下序.
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;
}
这个函数是去重函数,不过我们还是需要把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;
}
list里面的sort本质是归并排序,list的排序数据比较多的就是一个废物,我们在想一件事,我们STL里面的算法库里面不是存在一个排序算法吗,这里为何list还要内置一个.我们先来看看算法库里面.
#include
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是随机迭代器,但是我们的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; // 哨兵位
}
我们先把普通的给实现出来,后面const修饰的害需要单独讨论.
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
到这里我们就知道了我们只是改变迭代器里面的指针指向,具体的不在说了.我们先把==和!=重载出来.这个只需要比较一下里面的指针就可以了.
bool operator!=(const self& t) const
{
return _node != t._node;
}
bool operator==(const self& t) const
{
return !(*this != t);
}
所谓得++就是把里面得指针指向下一个地址,本质上也没有什么看点
// 前置
self& operator++()
{
_node = _node->_next;
return *this;
}
// 后置
self operator++(int)
{
self cur = *this; // 调用 的 是拷贝构造
_node = _node->_next;
return cur;
}
既然++都是实现出来了,那么–也是比较容易的.
// 前置
self& operator--()
{
_node = _node->_prev;
return *this;
}
// 后置
self operator--(int)
{
self cur(*this);
_node = _node->_prev;
return cur;
}
到这里就开始出现问题了,这个运算符是解引用,也就是得到节点里面的数据,我们这里使用T作为返回值,或者是突破类域,其中这个突破类域的方法我们后面再谈,这是标准库里面的做法.
T operator*()
{
return _node -> _data;
}
既然我们提出了一个解决方法,这里先把运算符都写出来,后面遇到问题我们再修改.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
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);
}
T* operator->()
{
return &_node->_data;
}
T operator*()
{
return _node->_data;
}
bool operator!=(const self& t) const
{
return _node != t._node;
}
//前置
self& operator++()
{
_node = _node->_next;
return *this;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
// 后置
self operator++(int)
{
self cur = *this;
_node = _node->_next;
return cur;
}
self operator--(int)
{
self cur(*this);
_node = _node->_prev;
return cur;
}
};
template<class T> // 这个 就是一个模板
class list
{
public:
typedef _list_node<T> Node;
typedef _list_iterator<T> iterator;
typedef _list_iterator<const T> const_iterator;
// 要有 构造函数 作为 一个 标记位
list()
{
// 首先 new 一个头节点
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
// 插入 不做说明的话 我们 插入 节点的前面
iterator insert(iterator pos, const T& val)
{
// 记录 迭代器 对应的 节点指针
Node* ppos = pos._node;
Node* cur = new Node(val);
// 记录 前一个 节点
Node* prev = ppos->_prev;
cur->_next = ppos;
ppos->_prev = cur;
cur->_prev = prev;
prev->_next = cur;
return iterator(cur);
}
void push_back(const T& val)
{
insert(end(), val);
}
// 迭代器
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator end()
{
return iterator(_head);
}
private:
Node* _head;
};
}
void func(const bit::list<int>& l)
{
bit::list<int>::const_iterator it = l.begin();
while (it != l.end())
{
cout << *it << endl;
it++;
}
}
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);
func(l);
return 0;
}
我们学到了模板,这里的编译器报的错误就会变多了,这里我们看看高手是怎么控制的.高手给他们重新添加了一个模板,主要就是返回值作用.
template<class T, class Ref, class Ptr>
struct _list_iterator
{
typedef _list_node<T> Node;
typedef _list_iterator<T, Ref, Ptr> self;
Node* _node;
_list_iterator(Node* node)
:_node(node)
{
}
bool operator==(const self& t) const
{
return !(*this != t);
}
Ptr operator->()
{
return &_node->_data;
}
Ref operator*()
{
return _node->_data;
}
};
class list
{
public:
typedef _list_node<T> Node;
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
}
这样就可以了.关于list的迭代器我们到这里就放一段落,后面等我们谈过stack,在和大家谈反向迭代器
我们在C语言过程中学过部分数据结构,学了顺序表,也就是我们的vector,那么今天我们分享的本质就是链表,而且是带哨兵位的双向循环链表.我们先把这个节点给定义出来,一般这种节点我们都是用struct.
// 这个 是 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)
{
}
};
这里我们先把框架搭出来,我们最好把节点typedef一下.
template<class T>
class list
{
public:
typedef _list_node<T> Node;
public:
list()
{
// 首先 new 一个头节点
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
private:
Node* _head; // 哨兵位
};
我们先把insert给实现出来,后面的尾插和头插我们都可以复用这个代码,就不重复造轮子了.这里的迭代器大家行不用关心,后面我们会专门谈到这个迭代器封装,大家现在只需要记住,迭代器里面存在一个节点指针,这个指针就是当前节点地址.其中这里的返回值是插入元素的第一个节点的迭代器器,我是按照标准库来的
iterator insert(iterator pos, const T& val)
{
// 记录 迭代器 对应的 节点指针
Node* ppos = pos._node;
Node* cur = new Node(val);
// 记录 前一个 节点
Node* prev = ppos->_prev;
cur->_next = ppos;
ppos->_prev = cur;
cur->_prev = prev;
prev->_next = cur;
return iterator(cur);
}
代码复用就可以了.
void push_back(const T& val)
{
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
这里面需要测试一下,这样的话也可以证明前面的insrt是正确的.
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 << " ";
++it;
}
cout << endl;
return 0;
}
我们最后把删除函数给实现一下,其他的函数就不管了,主要是太简单了,没有什么挑战性,如果大家想看,等下我们把仓库的链接附在后面,里面又更多的函数实现.
iterator erase(iterator pos)
{
Node* ppos = pos._node;
// 不能 删哨兵位
assert(pos != _head);
Node* prev = ppos->_prev;
Node* next = ppos->_next;
prev->_next = next;
next->_prev = prev;
delete ppos;
return iterator(next);
}
我们这里还是复用一下吧.
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
注意,删除元素会有迭代器失效的情况,这里我们先用erase测试,像头删和尾删都会存在问题,标准库里面也是.
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())
{
it = l.erase(it);
}
cout << l.size() << endl;
return 0;
}
我们测试一下list中迭代器失效的问题,这里比较简单.我们先来看一下insert,这个是没有问题了的.
int main()
{
::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(2);
l.push_back(4);
l.push_back(4);
l.push_back(6);
l.push_back(7);
auto it = l.begin();
while (it != l.end())
{
if (*it % 2 == 0)
{
l.insert(it, *it * 10);
}
it++;
}
for (int val : l)
{
cout << val << " ";
}
return 0;
}
但是erase就会变成迭代器失效的问题,我们很容易理解erase,一般等我们删除数据这个就会变成一个野指针.
int main()
{
::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(2);
l.push_back(4);
l.push_back(4);
l.push_back(6);
l.push_back(7);
auto it = l.begin();
l.erase(it);
*it;
return 0;
}