容器适配器就是一种设计模式,该种设计模式就是将一个类的接口转换成需要的的另外一个接口 。也就是将容器进行封装,通过对应容器的成员函数,从而实现一些不同且类似的功能。
STL标准库中像栈和队列就是典型的采用容器适配器来实现的。虽然像栈和队列也可以通过像vector和list一样自己去实现,但是实际上根本就没必要自己实现,完全可以通过容器适配器模式来实现,就拿封装vector来说吧。
我们知道栈是后进先出的特点,也就是push和pop只能在栈的一端执行,而实现栈需要的功能都可以通过vector,list,deque来适配出来。
而我们STL中stack模版参数,第二个形参是有缺省值的,也就是当我们未传参适配器时,以deque作为默认容器
以上即为stl中stack的成员函数,而像这种通过容器适配器出来的类都是不存在迭代器的。
template>
class stack
{
public:
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_back();
}
T& top()
{
return _c.back();
}
const T& top()const
{
return _c.back();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Con _c;//这里会自动调用Con容器的构造函数
};
队列的数据存储特点是先进先出,也就是从队列的一端入数据,从队列另一端出数据,同样实现队列的功能也可以通过vector,list,deque来实现。
同样第二个形参是有缺省值的,也就是当我们未传参适配器时,以deque作为默认容器
以上即为stl中queue的成员函数,而像这种通过容器适配器出来的类都是不存在迭代器的。
template>
class queue
{
public:
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T& back()
{
return _c.back();
}
const T& back()const
{
return _c.back();
}
T& front()
{
return _c.front();
}
const T& front()const
{
return _c.front();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Con _c;//默认会调用Con容器的构造函数
};
优先级队列(Priority Queue)是一个抽象数据类型,它类似于队列或栈,每个元素都有各自的优先级。 优先级最高的元素最先得到服务;优先级相同的元素按照其在优先级队列中的顺序得到服务。 优先级队列往往用堆(Heap)数据结构来实现。
简单来说不管你的数据存放先后,最终出数据时都是默认形成大堆,也就是最大的数先出
以上为stl中优先队列的成员函数
我们知道优先队列默认出数据是从大到小的,所以我们在push元素的时候,实际上是要按照大堆的形式来实现数据的存放,而pop数据时就出堆顶的元素,然后再将最后一个数据换到堆顶采用向下调整的方式重行建堆。
而最关键的就是如果我们不想采用大根堆,也就是不想大的数字先出列,而是想让数据采用升序出列的话该咋整呢,难道是再次实现一遍类吗,而新实现的与原来的也仅仅只有堆的向下调整和向上调整大于小于符号相反。所以说不就是可以实现一个功能,在某种标志下是大的数到堆顶,另一种标志小是小的数到堆顶
那看看stl是什么样子:
优先队列中有三个模版参数,第一个毫无疑问就是存放的数据类型,第二个就是容器适配器,优先队列默认用vector来适配,而该模版还有第三个参数,而这就涉及到
仿函数又称为函数对象,仿函数就是定义一个类,类里面重载函数运算符(),将该类的对象作为函数的入参,那么在函数中同样能调用重载符()里面的方法 所以说,仿函数就是仿造的函数,它并不是一个真正意义上的函数。 它是一个类中的运算符()重载,但它具有函数的功能。实际上调用仿函数就是通过类对象来调用operator()来实现的,相当于是函数指针的作用。
//模拟实现优先队列
namespace cr
{
//实现仿函数(代替函数指针的作用)
template
struct Less
{
bool operator()(T x, T y)
{
return x < y;
}
};
template
struct Greater
{
bool operator()(T x, T y)
{
return x > y;
}
};
template,class compare=Less>//less就是默认建大堆
class priority_queue
{
public:
priority_queue()
{}
template
priority_queue(Iterator begin, Iterator end)
{
while (begin != end)
{
this->push(*begin);//向上调整,时间复杂度更高O(n*logn)
begin++;
}
}
void adjust_up()
{
compare com;//函数类创建对象,以便调用仿函数
int child = _con.size() - 1;
int parent = (child - 1) / 2;
while (child)
{
if (com(_con[parent],_con[child]))//调用com对象的运算符函数
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
void adjust_down()
{
compare com;
int parent = 0;
int child = parent * 2 + 1;//初次就以左孩子为标准
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
child++;
if (com(_con[parent] , _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up();
}
void pop()
{
swap(_con[_con.size() - 1], _con[0]);
_con.pop_back();
adjust_down();
}
const T& top()
{
return _con.front();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
回顾一下list的正向迭代器是如何创建的:因为list的迭代器是指向节点的指针,而节点又是一个struct类封装起来的,属于自定义类型,因此需要将list迭代器进行封装起来。然后在该迭代器类中将用到的运算符进行重载,一切就绪以后,但是const的正向迭代器并未实现,此时不用再写一个const的迭代器,而是先比较const与非const迭代器的实现区别其实就是部分返回值的类型发生了改变,而针对于类型变化的要求时,自然是联想到了模版。此时将需要到的像const T&和const T*进行模版传参就可以通过一个类即实现const迭代器也实现非const迭代器的功能。最后为了在主函数中调用迭代器时更符合库中的调用方式,以及实现在list中实现begin()和end()函数时书写简洁,所以就在list类中对迭代器类型(包括模版参数)进行typedef重命名成库中的格式
先看看正向迭代器的实现:
template
struct ListNode//链表节点类
{
ListNode(const T& x = T())
:val(x)
,pre(nullptr)
,next(nullptr)
{}
T val;
struct ListNode* pre;
struct ListNode* next;
};
template//模版参数列表
struct _List_iterator//迭代器封装
{
typedef ListNode Node;//
typedef _List_iterator self;//两种重命名皆为简洁代码
_List_iterator(Node* node)
:_pnode(node)
{}
ref operator*()
{
return _pnode->val;
}
ptr operator->()
{
return &_pnode->val;//一般用于存自定义类型对象时,会隐藏一个箭头
}
self operator++()
{
_pnode = _pnode->next;
return *this;
}
self operator++(int)
{
self tmp = *this;
_pnode = _pnode->next;
return tmp;
}
self operator--()
{
_pnode = _pnode->pre;
return *this;
}
self operator--(int)
{
self tmp = *this;
_pnode = _pnode->pre;
return tmp;
}
bool operator!=(const self& cmpnode)const
{
return _pnode != cmpnode._pnode;
}
Node* _pnode;
};
template
class list
{
typedef ListNode Node;//对类进行重命名
public:
typedef _List_iterator iterator;//实例化
typedef _List_iterator const_iterator;
const_iterator begin()const
{
//return iterator(_head->next);//匿名对象
return _head->next;
}
const_iterator end()const
{
//return iterator(_head);//匿名对象
return _head;
}
iterator begin()
{
//return iterator(_head->next);//匿名对象
return _head->next;
}
iterator end()
{
//return iterator(_head);//匿名对象
return _head;
}
.
.
.
private:
Node* _head;//自定义类型成员的指针
size_t _size;
};
反向迭代器的实现其实就是采用了容器适配器,还是老样子,如果我们就完整实现一个反向迭代器的话,我们不能发现其实反向迭代器中的绝多数功能其实和正向迭代器有着异曲同工之处,就那反向迭代器的operator++和operator--来说其实就是与正向迭代器反着来得,而对于operator*而言,我们可以直接通过反向迭代器去调用正向迭代器的operator*来实现,这样的好处甚至是不用考虑底层的源码实现,直接调用就行。
所以说一般对于类的实现时如果新旧的版本中部分旧接口还在被使用。需要保留旧的接口,改变少许不同的接口的底层实现的这种情况时就可以考虑到容器适配器。
话不多言直接看代码:
template
struct Reverse_iterator
{
typedef Reverse_iterator self;
Reverse_iterator(Iterator it)
:_it(it)
{}
self operator++()
{
--_it;
return *this;
}
self operator++(int)
{
self tmp = *this;
--_it;
return tmp;
}
self operator--()
{
++_it;
return *this;
}
self operator--(int)
{
Reverse_iterator tmp* this;
++_it;
return tmp;
}
bool operator!=(self it)
{
return _it != it._it;
}
ref operator*()
{
return *_it;
}
ptr operator->()
{
return _it.operator->();//这里需要显示调用运算符函数
}
Iterator _it;
};
list中:
typedef Reverse_iterator reverse_iterator;//实例化
typedef Reverse_iterator //const反此项迭代器就传const正向迭代器适配
以上即为全部内容