- 配接器(adapters)在 STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。Adapter这个概念,事实上是一种设计模式(design pattern)。 «Design Patterns)) 一书提到23个最普及的设计模式,其中对odopter样式的定义如下:将 一个class的接口转换为另一个class的接口,使原本因接口不兼容而不能合作的classes,可以一起运作。
8 . 1 配接器之概观与分类
- S T L所提供的各种配接器中,改变仿函数(functors)接口者,我们称为functionadapter,改变容器(containers)接口者,我们称为container adapter,改变迭代器 fi (iterators)接口者,我们称为 iterator adapter
8.1.1 应 用 于 容 器 ,container adapters
- S TL提供的两个容器queue和 s ta c k ,其实都只不过是一种配接器。它们修 饰 deque的接口而成就出另一种容器风貌。这两个container adapters已于第4章介绍过。
8.1.2 应 用 于 迭 代 器 ,iterator adapters
- S T L 提供了许多应用于迭代器身上的配接器,包 括 insert iterators, reverse iterators, iostream iterators. C++ Standard 规定它们的接口可以藉由 <iterator>获得,SGI S T L则将它们实际定义于 <stl_iterator.h>
Insert Iterators
- 所 谓 insert iterators, 可以将一般迭代器的赋值(assign)操作转变为插入 (insert}-操作。这样的迭代器包括专司尾端插入操作的back_insert_-iterator, 专司头端插入操作的fro n t_ in se rt_ ite ra to r,以及可从任意位置执行插入操作的 insert_i te ra to ro 由于这三个iterator adapters的使用接口不是十分直观,给一般用户带来困扰,因此,STL更提供三个相应函数: back_inserter()、front_inserter()、 inserter(), 如图8-1所示,提升使用时的便利性。
Reverse Iterato rs
- 所谓reverse iterators,可以将一般迭代器的行进方向逆转,使原本应该前进的 operator++ 变成了后退操作,使原本应该后退的 operator-- 变成了前进操作。 这种错乱的行为不是为了掩人耳目或为了欺敌效果,而是因为这种倒转筋脉的性质运用在“从尾端开始进行”的算法上,有很大的方便性• 稍后我有一些范例展示
lO S tream Iterato rs
- 所 谓 iostream iterators,可以将迭代器绑定到某个iostream对象身上。绑定 到 istream 对 象 (例 如 std::cin)身上的,称 为 istream _ iterator,拥有输入功 能 ; 绑 定 到 ostream 对 象 (例 如 std::cout ) 身 上 的 ’称为 ostream _iterator,拥有输出功能。这种迭代器运用于屏幕输出,非常方便。以它为蓝图,稍加修改,便可适用于任何输出或输入装置上.例如,你可以在透彻了解 iostream Iterators的技术后,完成一个绑定到Internet Explorer cache身上的迭代器1 , 或是完成一个系结到磁盘目录上的一个迭代器2。
- 请注意,不像稍后即将出场的仿函数配接器(functoradapters)总以仿函数作为参数,予 人 以 “拿某个配接器来修饰某个仿函数”的直观感受,这里所介绍的迭代器配接器(iterator adapters)很少以迭代器为直接参数% 所谓对迭代器的修 饰,只是一种观念上的改变(赋值操作变成插入操作啦、前进变成后退啦、绑定到特殊装置上啦…) 。你可以千变万化地写出适合自己所用的任何迭代器• 就这一点而言,为了将S T L 灵活运用于你的日常生活之中,iterator adapters的技术是非常重要的。
#include <iostream>
#include <algorithm>
#include <iterator>
#include <set>
#include <vector>
#include <functional>
#include <deque>template<class T>
struct display{void operator()(const T&x){std::cout << x << ' ';}
};struct even{bool operator()(int x)const{return x%2 ? false : true;}
};int main(int argc,char* argv[]){//将 outite绑定到cout 每次对outite指派一个兀素,就后接一个 ""std::ostream_iterator<int>outite(std::cout," ");int ia[] = {0,1,2,3,4,5};std::deque<int>id(ia,ia+6);//将所有元素拷贝到outite (那么也就是拷贝到cout)std::copy(id.begin(),id.end(),outite); //输出 0 1 2 3 4 5std::cout << std::endl;// 将 ia []的部分元素拷贝到id 内。使 用 front_insert_iterator<>// 注意,front_insert_iterqtor 会将 assign 操作改为 push_front 操作// vector不支持push_front (),这就是本例不以vector为示范对象的原因。std::copy(ia+1,ia+2,std::front_inserter(id));std::copy(id.begin(),id.end(),outite); //输出 1 0 1 2 3 4 5std::cout << std::endl;//将 ia []的部分元素拷贝到i d 内。使 用 back_insert_iterator.std::copy(ia+3,ia+4,std::back_inserter(id));std::copy(id.begin(),id.end(),outite); //输出 1 0 1 2 3 4 5 3std::cout << std::endl;//搜索元素 5 所在的位置std::deque<int>::iterator ite = std::find(id.begin(),id.end(),5);//将 ia[]的部分元素拷贝到i d 内。使 用 insert_iteratorstd::copy(ia+0,ia+3,std::inserter(id,ite));std::copy(id.begin(),id.end(),outite); //输出 1 0 1 2 3 4 0 1 2 5 3std::cout << std::endl;//将所有的元素逆向拷贝//rbegin ()和 rend ()与 reverse_iterator有关,见稍后源代码说明std::copy(id.rbegin(),id.rend(),outite);std::cout << std::endl;//以下,将inite绑定到cin.将元素拷贝到inite,直到eos出现std::istream_iterator<int>inite(std::cin),eos;//eos :end-of-streamstd::copy(inite,eos,std::inserter(id,id.begin()));// 由于很难在键盘上直接输入end-of-stream (end-of-file)符号// (而且不同系统的eof符号也不尽相同),因此,为了让上一行顺利执行,// 请单独取出此段落为一个独立程序,并准备一个文件,例 如 int.dat,内置 // 32 26 99 (自由格式),并 在 console之下利用piping方式执行该程序,如下:// c :\>type int.dat I thisprog / / 意思是将type int .dat的结果作为thisprog的输入std::copy(id.begin(),id.end(),outite);return 0;
}
8.1.3 应用于仿函数 , functor adapters
- unctor adapters (亦称为function adapters) 是所有配接器中数量最庞大的一个族群,其配接灵活度也是前二者所不能及,可以配接、配接、再配接。这些配接操作包括系结(bind)、否 定 (negate), 组 合 (compose)、以及对一般函数或成员函数的修饰(使其成为一个仿函数)。 Standard规定这些配接器的接口可由<functional>获得,SGI S T L 则将它们实际定义于 <stl_function.h>
- function adapters的价值在于,通过它们之间的绑定、组合、修饰能力,几乎 可以无限制地创造出各种可能的表达式(expression), 搭配STL算法一起演出
- 例如,我们可能希望找出某个序列中所有不小于12的元素个数.虽然, “不小于”就是 “大于或等于”,我们因此可以选择STL内建的仿函数greater_equal,但 如果希望完全遵循题目语意(在某些更复杂的情况下,这可能是必要的),坚持找出 “不小于” 12的元素个数,可以这么做:
- 请注意,所有期望获得配接能力的组件,本身都必须是可配接的(adaptable) 换句话说,一元仿函数必须继承自unary_function (7.1.1节 ),二元仿函数必须继承自binary_function (7.1.2 节 ) ,成员函数必须以mem_fun处理过,一般函数必须以ptr_fun处理过。一个未经ptr_fun处理过的一般函数,虽然也可以函 数指针(pointer to function)的形式传给STL算法使用 却无法拥有任何配接能力。
void print(int i){std::cout << i << std::endl;
}class Int{
public:explicit Int(int i) : m_i(i){};//这里有个既存的成员函数(稍后希望于STL体系中被复用)void print1() const {std::cout << '[' << m_i << ']';}private:int m_i;
};int main(int argc,char* argv[]){//将 outite绑定到cout 每次对outite指派一个兀素,就后接一个 ""std::ostream_iterator<int>outite(std::cout," ");int ia[6] = {2,21,12,7,19,23};std::vector<int>iv(ia,ia+6);//以下将所有元素拷贝到 outite. 有数种办法std::copy(iv.begin(),iv.end(),outite);//1,以函数指针搭配STL算法std::for_each(iv.begin(),iv.end(), print);//2,以修饰过的一般函数搭配STL算法std::for_each(iv.begin(),iv.end(),std::ptr_fun(print));Int t1 (3), t2 (7), t3 (20),t4(14), t5(68);std::vector<Int> Iv;Iv.push_back(t1);Iv.push_back(t2);Iv.push_back(t3);Iv.push_back(t4);Iv.push_back(t5);//3,以下,以修饰过的成员函数搭配STL算法std::for_each(Iv.begin(),Iv.end(),std::mem_fun_ref(&Int::print1));return 0;
}
8.2 container adapters
8.2.1 stack
stack的底层由deque构成。从以下接口可清楚看出stack与deque的关系:
- C++ standard 规定客户端必须能够从<stack>中获得stack的接口,SGI STL 则把所有的实现细节定义于的<stl_stack.h>内,请参考4.5节。class stack封住了所有的deque对外接口,只开放符合stack原则的几个函数,所以我们说 stack是一个配接器,一个作用于容器之上的配接器。
8.2.2 queue
- queue的底层由deque构成。从以下接口可清楚看出queue与deque的关系:
- 规定客户端必须能够从 <queue> 中获得queue的接口,SGI STL 则把所有的实现细节定义于<stl_queue.h>内,请参考4.6节。class queue封住了所有的deque对外接口,只开放符合queue原则的几个函数,所以我们说 queue是一个配接器,一个作用于容器之上的配接器。
8.3 iterator adapters
- 本章稍早已说过iterator adapters的意义和用法,以下研究其实现细节
8.3.1 insert iterators
- 下面是三种insert iterators的完整实现列表。其中的主要观念是,每一个 insert iterators内部都维护有一个容器(必须由用户指定):容器当然有自己的迭代器,于是,当客户端对insert iterators做赋值(tm i敢)操作时,就在insert iterators中被转为对该容器的迭代器做插入(insert)操作,也就是说,在 insert iterators的operator =操作符中调用底层容器的push_front ()或push_back ()或 insert () 操 作 函 数 。
- 至 于 其 它 的 迭 代 器 惯 常 行 为 如 operator++, operator++ (int) operator*都被关闭功能,更 没 有 提 供 operator-- (int)或 operator--或 operator->等功能(因此被类型被定义为output„iterator_tag)
- 换句话说,insert iterators的前进、后退、取值、成员取用等操作都是没有意义的,甚至是不允许的。
8.3.2 reverse iterators
- 所谓reverse iterator,就是将迭代器的移动行为倒转。如果STL算法接受的不 是一般正常的迭代器,而是这种逆向迭代器,它就会以从尾到头的方向来处理序列中的元素。例如:
- 首先我们看看rbeginO和 rend().任何STL容器都提供有这两个操作, 我在第4, 5两章介绍各种容器时,鲜有列出这两个成员函数,现在举个例子瞧瞧:
- 没有任何例外!只要双向序列容器提供了 begin(), end(),它 的 rbegin、rend() 就是上面那样的型式。单向序列容器如slist不可使用reserve iterators. 有些容器如 stack、queue、priority .qu eue 并不提供begin(), end(), 当然也就没有 rbegin/ rend()
- 为什么 “正向迭代器”和 “与其相应的逆向迭代器”取出不同的元素呢?这并不是一个潜伏的错误,而是一个刻意为之的特征,主要是为了配合迭代器区间的“前闭后开”习 惯 (1.9.5节 ) . 从图8-3的 rbeginO和 end()关系可以看出, 当迭代器被逆转方向时,虽然其实体位置(真正的地址)不变,但其逻辑位置(迭代器所代表的元素)改变了(必须如此改变):
- 唯有这样,才能保持正向迭代器的一切惯常行为。换句话说,唯有这样,当我们将一个正向迭代器区间转换为一个逆向迭代器区间后,不必再有任何额外处理,就可以让接受这个逆向迭代器区间的算法,以相反的元素次序来处理区间中的每一个元素.例如(以下出现于8.1.2节实例之中):
- 有了这些认知,现在我们来看看reverse_iterator的源代码
- 这是一个迭代器配接器(iter己tor adapter), 用来将某个迭代器逆反前进方向
- 使前进为后退,后退为前进
8.3.3 stream iterators
- 所 谓 streamfterotors,可以将迭代器绑定到一个stream (数据流)对象身上。 绑 定 到 istrea m对象 (例如 std::cin ) 者,称 为 istream_iterator ,拥有输入能力;
- 绑定到ostream 对 象 (例 如 std:: cout) 者,称为 拥有输出能力。两者的用法在8.1.2节的例子中都有示范
- 乍听之下真神奇。所谓绑定一个i stream object,其实就是在isiream iterator内部维护一个istream member,客户端对于这个迭代器所做的operator++操 作 ,会 被 导 引 调 用 迭 代 器 内 部 所 含 的 那 个 istream member的输入操作(operator») 。这个迭代器是个Input Iterator,不具备operator--o下面的源代码和注释说明了一切。
8,4 function adapters
- 一般而言,对于C++ template语法有了某种程度的了解之后,我们很能够理解或想象,容器是以class templates完成,算 法 以 function templates完成,仿函数是一种将operator() 重 载 的 class template,
- 迭代器则是一种将 operator++和 operator*等指针习惯常行为重载的class template.然而配接器呢?应用于容 器身上和迭代器身上的配接器,已于本章稍早介绍过,都是一种class template.可 应用于仿函数身上的配接器呢?如 何 能 够 “事先”对一个函数完成参数的绑定、
执行结果的否定、甚至多方函数的组合?请注意我用“事先” 一词。我的意思是,最后修饰结果(视为一个表达式,expression) 将被传给STL算法使用,STL算法 才是真正使用这表达式的主格。而我们都知道,只有在真正使用(调用)某个函数(或仿函数)时,才有可能对参数和执行结果做任何干涉。
8.4.1 对返回值进行逻辑否定:not1,not2
- 以下直接列出源代码。源代码中的注释配合先前的概念解说,应该足以让你彻底认识这些仿函数配接器。源代码中常出现的pred 一词,是 predicate的缩写, 意指会返回真假值(bool)的表达式。
#include <iostream>
#include <algorithm>
#include <iterator>
#include <set>
#include <vector>
#include <functional>
#include <deque>class Shape{
public:virtual void display() = 0;
};class Rect : public Shape{
public:virtual void display() {std::cout << "Rect ";}
};class Circle : public Shape{
public:virtual void display() {std::cout << "Circle ";}
};class Square : public Shape{
public:virtual void display() {std::cout << "Square ";}
};int main(int argc,char* argv[]){std::vector<Shape*>Iv{};Iv.push_back(new Rect);Iv.push_back(new Circle);Iv.push_back(new Square);Iv.push_back(new Circle);Iv.push_back(new Rect);//打印 输出for (int i = 0; i < Iv.size(); ++i) {(Iv[i])->display();}std::cout << std::endl;std::for_each(Iv.begin(),Iv.end(),std::mem_fun(&Shape::display));std::cout << std::endl;return 0;
}