什么是适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该中模式是将一个类的接口转换成客户希望的另外一个接口。
故适配器就是接口转换装置,让我们能通过特定的方法去操作原本自己不能直接操作的数据。
举个生活中的例子:电脑一般都用标准的交流的插头(USB接口),但我们平时用的欧洲壁式插座一般都是三孔的插座,这个时候我们就需要提供一个交流电适配器将插座的接口转换成USB的接口供我们使用。
什么是容器适配器
容器是对类模板的封装,而容器适配器是对容器的一种再封装。不同的容器适配器提供不同的函数,使容器的功能得到全新的特定的扩展。(但它和容器是有区别的:容器适配器不支持迭代器和算法,使用起来限制比较大)。标准库提供了三种容器适配器:queue(先进先出),stack(后进先出)和priority_queue(通过自己制定的规则进行排列,默认“<”比较即大数优先极高)。
为什么将stack,queue和priority_queue称为容器适配器
虽然以上三种也可以存放元素,但我们并没有将其划分在容器的行列,这是因为每个容器在底层都有自己的实现方式,可是stack,queue和priority_queue只是在底层将其他容器进行了一个封装,并没有自己的实现方式。如下图所示:
诺,,不信你看,vector底层就没有其他容器的支持,所以vector是容器,而priority_queue是容器适配器!
容器适配的模拟实现
适配器可以通过封装vector,list,deque三种容器实现。
stack的模拟实现(后进先出)
可以封装vector/deque/list任一容器实现栈stack的相应接口,下面这个代码用封装vector的方式模拟了stack的实现过程
#include <stdio.h>
#include <vector>
#include <iostream>
using namespace std;
template<class T,class Container=vector<T>>
//同样可以用template<class T,class Container=deque<T>>
//同样可以用template<class T,class Container=list<T>>
class Stack
{
public:
Stack() {}
void Push(const T&x) { _c.push_back(x);} //栈的push可以看做vector的push_back
void Pop() { _c.pop_back(); }//栈的pop相当于vector的pop_back
T& Top() { return _c.back(); }
const T&Top() const { const return _c.back(); }
size_t Size()const { return _c.size(); }//统计接口可直接使用
bool Empty()const { return _c.empty(); }//判空接口可直接使用
private:
Container _c;
};
void Test1()
{
//Stack<int,vector<int>> s;
Stack <int>s; //模板参数给了缺省值,可以直接定义
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
s.Push(5);
cout <<"size: "<<s.Size() << endl;
cout <<"top: "<< s.Top() << endl;
s.Pop();
s.Pop();
while (!s.Empty())
{
cout << s.Top() << " ";
s.Pop();
}
cout << endl;
}
int main()
{
Test1();
return 0;
}
queue的模拟实现(先进先出)
可以封装deque实现队列queue的相应接口,下面这个代码模拟了queue的实现过程
#include <stdio.h>
#include <iostream>
using namespace std;
#include <deque>
template<class T,class Con=deque<T>>
class Queue
{
public:
Queue() {}
void Push(const T&x) { _c.push_back(x); }
void Pop() { _c.pop_front(); }
T& Back() { return _c.back(); }
const T&Back() const { const return _c.back(); }
T& Front() { return _c.front(); }
const T& Front()const { const return _c.front(); }
size_t Size()const { return _c.size(); }
bool Empty()const { return _c.empty(); }
private:
Con _c;
};
void Test2()
{
Queue<int> q;
q.Push(1);
q.Push(2);
q.Push(3);
q.Push(4);
cout << "size: "<<q.Size() << endl;
cout << "back: " << q.Back() << endl;
cout << "front: " << q.Front() << endl;
while (!q.Empty())
{
cout << q.Front() << " ";
q.Pop();
}
cout << endl;
}
priority_queue模拟实现(第一个元素总是它所包含的元素中最大的一个)
优先队列底层是用堆实现的,模拟实现的代码在上篇博客优先队列(priority_queue)里讲的很清楚了,有兴趣可以去了解。
在C++中,虽然三种容器都可以封装成适配器,但我们选择deque作为stack和queue的底层默认容器,原因主要是:
1、stack和queue不需要遍历(因此栈和队列都没有迭代器),只需要在固定的一端或者两端进行操作;
2、在stack元素增长时,deque比vector效率更高;queue中的元素增长时,deque不仅效率高,而且内存使用率高。
vector和list作为栈和队列的底层容器,各有优缺点,例如vector可以支持随机访问,连续内存数组缓存利用率高;但是头部插入删除效率低,扩容成本高;list的优缺点和vector正好相反。
针对以上list和vector的优缺点问题,利用deque(双端队列)可以折中的解决。
容器适配器的使用示例
一般来说,先定义一个容器,接着给该容器配备相应的容器适配器,然后就可以使用该容器适配器特有的函数处理数据了。这里可以省略掉容器定义那一步,容器适配器将自动分配容器。下面这个代码示范了栈容器适配器stack的用法。我们先定义一个vector容器,接着给该vector容器分配stack容器适配器,然后使用栈容器提供的函数自顶向下的输出所有元素。
#include <iostream>
#include <vector>
// 使用容器适配器必须包含相应的头文件
#include <stack>
using namespace std;
int main()
{
vector<int> v;
// 将0-9存入vector容器并打印
for (int i = 0; i<10; i++)
v.push_back(i);
cout << "输出Vector容器:" << endl;
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
cout << *it << " ";
cout << endl << endl;
// 给容器v配上容器适配器
stack< int, vector<int> > s(v);
// 使用栈容器适配器stack提供的函数处理数据
cout << "自顶向下输出栈( stack容器适配器 ):" << endl;
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
system("pause");
return 0;
}