概述
adapter在STL组件的灵活组合运用上,扮演者转换器的角色。adapter来源于一种适配器模式,其功能是:将一个class接口转换为另一个class的接口,使得原本因接口不兼容而不能合作的classes,可以一起运作
STL配接器可以分为三种:
- 改变仿函数functions接口的:function adapter
- 改变容器containers接口的:container adapter
- 改变迭代器iterators接口的:iterator adapter
iterator adapter
STL应用率很多应用于迭代器身上的配接器,包括insert iterator、reverse iterator、iostream iterator。C++标准规定它们的接口可以由<iterator>
获得,STL 将它们实际定义于std_iterator.h
insert iterator
是什么
所谓insert iterator
,可以将一般迭代器的赋值操作转换为插入操作。包括三种:
- 专用于尾插的back_insert_iterator
- 专用于头插的front_insert_iterator
- 可用于任意位置插入的insert_iterator
由于这三个insert iterator
的使用不直观,所以STL提供了三个便利函数:back_iterator()、front_inserter()、inserter()
实现
back_iterator实现
其主要观念是:
- 每一个insert iterator内部都维护由一个容器(必须由用户指定);容器当然有自己的迭代器,于是,当客户端对insert iertator做赋值操作时,就在insert iterator中被转换为对该容器的迭代器做insert操作。
- 也就是说,insert iterator的operator=操作符中调用底层容器的push_front或者push_back()或者insert函数
- 对于其他迭代器的行为比如operator++、operator–,没有提供这样的接口
front_inserter实现
inserter实现
reverse iterator
是什么
- 所谓reverse iterator,可以将一般迭代器的行进方向逆转,使得原本的operator++变为–,原本的operator–变为++。
- 如果stl算法接收的不是一般的正常的迭代器,而是这种逆转迭代器,它就会以从尾到头的方向来处理序列中的容器。比如:
// 将所有元素逆序拷贝到iter所指位置上
std::copy(id.rbegin(), id.rend(), iter)
实现
先来看看rbegin和rend
没有任何例外,只要双向序列容器提供了begin()、end(),它的rbegin()、rend()就是上面的形式。
#include <iterator>
#include <deque>
#include <algorithm>
#include <iostream>
#include <functional>
#include <stack>
using namespace std;
int main(){
int ia[] = {99, 1, 2, 3, 4, 100};
std::vector<int> vec(ia, ia + 6);
std::cout << *vec.begin() << "\t" << *vec.rbegin() << "\n"; //99 100
std::cout << *vec.end() << "\t" << *vec.rend() << "\n"; //dargget 133889 0
auto iter = std::find(vec.begin(), vec.end(), 3);
std::reverse_iterator<std::vector<int>::iterator> riter(iter);
std::cout << *iter << "\t" << *riter << "\n"; // 3 2
}
可以看出,当迭代器被逆转方向时,虽然真正的位置不变,但是其逻辑位置变了。换句话说,当我们将一个正向迭代器区间转换为一个逆向迭代器区间后,不必再有任何额外的处理,就可以让接受这个逆向迭代器区间的算法,以相反的元素次数来处理区间中的每一个元素
iostream iterator
是什么
所谓iostream iterator,可以将迭代器绑定到某个iostream对象上:
- 绑定到istream对象上的,叫做istream_iterator,用于输入功能
- 绑定到ostream对象上的,叫做ostream_iterator,用于输出功能
#include <iterator>
#include <deque>
#include <algorithm>
#include <iostream>
int main(){
std::ostream_iterator<int> outite(std::cout, " ");
int ia[] = {0, 1, 2, 3, 4, 5};
std::deque<int> id(ia, ia + 6);
std::copy(id.begin(), id.end(), outite); //0 1 2 3 4 5
std::cout << std::endl;
std::copy(id.begin(), id.end(), std::front_inserter(id));
std::copy(id.begin(), id.end(), outite); //5 4 3 2 1 0 0 1 2 3 4 5
std::cout << std::endl;
std::copy(id.begin(), id.end(), std::back_inserter(id));
std::copy(id.begin(), id.end(), outite); //5 4 3 2 1 0 0 1 2 3 4 5 5 4 3 2 1 0 0 1 2 3 4 5
std::cout << std::endl;
id = {1 , 2, 3, 4, 5};
std::copy(id.begin(), id.end(), outite); // 1 2 3 4 5
std::cout << std::endl;
std::copy(ia, ia + 2, std::inserter(id, std::find(id.begin(), id.end(), 5)));
std::copy(id.begin(), id.end(), outite); //1 2 3 4 0 1 5
std::cout << std::endl;
std::copy(id.rbegin(), id.rend(), outite); //5 1 0 4 3 2 1
std::cout << std::endl;
// std::istream_iterator<int> inite(std::cin), eos;
// std::copy(inite, eos, std::inserter(id, id.begin())); //5 1 0 4 3 2 1
// std::cout << std::endl;
}
实现
所谓绑定一个istream object,其实就是在istream iterator内部维护一个istream member,客户端对这个迭代器所做的operator++操作,都会被引导到调用迭代器内部所含的那个istream member的operator>>。如下
可以看出,只要客户端定义一个istream iterator并绑定到某个istream object,程序就立即停在istream_iterator<T>::read()
函数等待输入,这不是我们所预期的行为。所以,请在绝对必要的时候才定义istream iterator
function adapter
function adapter是所有适配器中数量最庞大的一个,非常灵活。这些配接操作包括bind、negate(否定)、compose(组合),以及对一般函数或者成员函数的修饰(使其称为一个仿函数)。需要使用的话请引入<functional>
function adapter能够事先对一个函数完成参数的绑定、执行结果的否定以及多方函数的组合
function adapter的作用:通过它们之间的compose、bind、修饰能力,几乎可以无限的创建出各种可能的表达式。比如需要找出某个序列中所有不小于3的元素个数。那么
cout<< count_if(vec.begin(), vec.end(),not1(bind2nd(less<int>(), 3)))<< endl;//>=3的个数,一个参数未定
是什么
我们可以知道:
- 容器是以class template完成
- 算法是以function template完成
- 仿函数是一种将operator()重载的class template
- 迭代器是一种将operator++和operator*等指针习惯行为重载的class template
- 配接器:
- 应用于容器和迭代器身上的配接器,都是一种class template
- 应用于仿函数的配接器,
每一个function adapters,内藏了一个memeber object,其型别等于它所要配接的对象。当function adapter有了完全属于自己的一份修饰对象(的副本)在手,他就成了该修饰对象(的副本)的主人,也就有资格调用该修饰对象(一个仿函数)并在参数和返回值上动手脚了
所谓pred,会返回true或者false
实现
对返回值进行逻辑否定:not1、not2
对参数进行绑定:bind1st、bind2nd
用于函数指针:ptr_func
这种适配器可以使我们能够将一般函数当做仿函数使用。
- 一般函数当做仿函数传给STL算法,就语言层面是可以的。
- 但是如果不适用这个配接器先封装,这个一般函数将无配接能力,也就是无法和其他配接器接轨
用于成员函数指针:mem_func、mem_func_ptr
这种适配器能够让成员函数当做仿函数来使用。当容器的元素类型是X&、X*时,而且又有虚函数作为仿函数,就可以借由泛型算法完成多态调用
#include <iterator>
#include <deque>
#include <algorithm>
#include <iostream>
#include <functional>
#include <stack>
using namespace std;
class Shape{
public:
virtual void display() = 0;
};
class Rect : public Shape{
public:
virtual void display(){
std::cout << "Rect" << "\n";
}
};
class Circle : public Shape{
public:
virtual void display(){
std::cout << "Circle"<< "\n";
}
};
int main(){
std::vector<Shape *> v;
v.push_back(new Rect);
v.push_back(new Circle);
for(auto i = v.begin(); i != v.end(); ++i){
(*i)->display();
}
std::cout << "\n";
for_each(v.begin(), v.end(), std::mem_fun(&Shape::display));
std::cout << "\n";
}
container adapter
STL提供两个容器stack、queue,其实是一种适配器,它们将deque修饰为另一种容器风貌
stack
stack的底层是deque。
stack封装了所有deque的对外接口,只提供了符合stack原则的几个函数
queue
queue的底层是deque
queue封装了所有deque的对外接口,只提供了符合queue原则的几个函数