配接器
实际上是一种设计模式:将一个class的接口转换为另一个class的接口,使原本因接口不兼容而不能合作的classes,可以一起运作。
container adapters
用于修改容器接口的配接器称为container adapter。
stl提供的两个容器queue和stack,其实都只不过是一种配接器。
iterator adapters
应用于迭代器身上的配接器叫做iterator adapters。包括insert iterators、reverse iterators和iostream iterators
insert iterators
1.迭代器的类型是output_iterator_tag
,是输出迭代器。
2.每一个insert iterators内部都维护有一个容器,以back_insert_iterator
为例:
template <class Container>
class back_insert_iterator {
protected:
Container* container;
...
}
这个容器可以由构造函数指定:
explicit back_insert_iterator(Container& x) : container(&x) {}
函数back_inserter
实际上也是调用了这个构造函数,返回一个back_insert_iterator
对象。
3.重载了=
符号,对插入迭代器进行赋值操作,实际上会调用Container的插入操作(push_back
、push_front
、insert
)。比如对于back_insert_iterator
:
operator= (const typename Container::value_type& value) {
container->push_back(value);
return *this;
}
4.*
、++
、++(int)
等符号也都重载,但是都是直接返回*this
。
5.insert_iterator常常用于copy
函数,比如这样使用:
copy(ia + 1, ia + 3, front_inserter(vec));
实际上会不断对vec容器使用push_front
操作,将前面输入迭代器的内容插入。
3和4中对=
符号和*
符号的重载共同实现了这种功能。因为copy
的源码中有:
*result = *first;
当这里的result是插入迭代器时,由4可知*result
得到的实际上还是result,于是变成了:
result = *first;
于是就会调用insert iterator的operator=()
函数,进行插入操作。
5.对于insert_iterator
类型,它有两个数据成员,除了container外,还保存一个指向容器的普通迭代器iter。因为在实现普通的插入操作时,还需要提供一个插入位置,这个iter就起这样的作用。
它的构造函数以及inserter
函数都接收两个参数。
reverse iterators
1.reverse iterators的内部有一个数据成员current,是普通迭代器。它的构造函数就将普通迭代器作为参数。base()
函数可以返回这个current。
2.reverse iterators的5种型别和current所属于的类别一样。
3.stl容器中,有正向迭代器的一般也有逆向迭代器(除了forward_list,没有逆向迭代器)。
typedef reverse_iterator<iterator> reverse_iterator;
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
...
可以看到,reverse iterators的尾后迭代器rend()
存的是begin()
,而rbegin()
存的是end()
。
3.重载*
函数:
reference operator*() const {
Iterator tmp = current;
return *--tmp;
}
也就是说,对reverse iterator解引用的时候,获得的是current的前一个迭代器(减1得到的)的值。
这也正好和容器的rbegin()以及rend()的规定匹配上了,rbegin()解引用取的是end()前一个迭代器的值,永远不会解引用end();而由于永远不会对rend()解引用,所以也永远不会对begin()进行--
操作。不会出现问题。
4.reverse iterators的前进(++
)操作,就是对current执行后退操作,reverse iterators的后退(--
)操作,就是对current执行前进操作。
类似于前进n步和后退n步的操作,都是这样逆向的操作的。
stream iterator
1.将迭代器绑定在一个stream对象上,绑定在istream上的叫做istream_iterator,绑定到ostream上的叫做ostream_iterator。
2.istream_iterator迭代器类型是Input Iterator,ostream_iterator迭代器类型是Output Iterator
3.istream_iterator的实现:
有三个数据成员。一个是指向istream对象的指针,一个是value,还有一个bool变量指明是否读到了eof。
有一个成员函数read()
,这个函数实际上就是通过<<
符号从stream对象中将一个值输入给value。
有两个构造函数。默认构造函数绑定cin对象,并且将bool变量赋值为false。另一个构造函数接收一个istream对象,并且要执行一次read()
,于是value会被赋值(或者会被阻塞)。
解引用操作,就是直接返回value。
++
操作,就是调用一次read()
,让输入流再输入一个值给value。
ostream_iterator的实现和istream_iterator类似。
4.ostream_iterator的实现:
有两个数据成员,一个是指向ostream对象的指针,一个是一个const char*类型的变量,名字叫做string。这个string的作用是作为间隔符。
有两个构造函数,一个只接受ostream对象,另一个还接受一个const char*类型变量。比如:
ostream_iterator<int> outiter(cout, ' ');
这个迭代器将数据输出到cout,每次间隔一个空格。
重载赋值操作=
,调用它时,实际上是将value输出到ostream对象,并且如果间隔符号不为空,还要输出一个间隔符号。
*
、++
等操作都什么也不做,直接返回*this
5.copy操作的实现。
比如outite是一个ostream_iterator,它会被当做copy
函数的第三个迭代器参数,也就是result参数。
在拷贝的过程中,会不断做这样的事情:
*result = *first;
对result解引用会直接返回result,于是实际上是result = *first
。调用赋值函数operator=
,于是就将value输出到ostream对象中。
在迭代的过程中,会不断对result进行++
操作,而它也直接返回result。
function adapters
1.container adapters在内部存一个容器,iterator adapters在内部存一个普通迭代器,同理,function adapters也在内部存仿函数的实例化对象。
2.以用于函数合成的compose1为例。
template <class Operation1, class Operation2>
class unary_compose
: public unary_function<typename Operation2::argument_type,
typename Operation1::result_type> {
...
}
将两个模板参数Operation1和Operation2,分别代表两个一元仿函数类型。
类继承一元仿函数unary_function。它的输入类型是Operation2的输入类型,输出类型是Operation1的输出类型。
protected:
Operation1 op1;
Operation2 op2;
public:
unary_compose(const Operation1& x, const Operation2& y)
: op1(x), op2(y) {}
两个数据成员分别是Operation1类型的对象和Operation2类型的对象。构造函数也是接受两个对象来分别初始化op1和op2。
typename Operation1::result_type
operator() (const typename Operation2::argument_type& x) const {
return op1(op2(x));
}
仿函数经典操作,重载()
符号,能看到参数类型是Operation2的参数类型,返回类型是Operation1的参数类型。而重载函数的实际过程也是,先计算op2(x),再将它的结果用op1计算,返回。
3.几种标准库内置的仿函数配接器:
-
not1
、not2
:对返回值进行逻辑否定。一个处理一元仿函数,一个处理二元仿函数。 -
bind1st
、bind2nd
:把二元仿函数处理成一元仿函数。它的构造函数会接受一个value参数,value绑定到二元仿函数的其中一个参数上,于是这个二元仿函数之后就只需要接受一个参数,表现得就像是一个一元仿函数。 -
compose1
、compose2
:一个仿函数的结果作为另一个仿函数的输入,第二个仿函数的输出作为最终的输出。 -
ptr_fun
:实现将函数指针当做仿函数来使用。将函数指针作为数据成员,重载的()
实际上就是直接调用函数指针。
注意函数指针的定义方式是:函数返回值类型 (* 指针变量名) (函数参数列表);
,比如书上所写的Result (*ptr) (Arg)
。 -
mem_fun
、num_fun_ref
:这两个配接器实现将成员函数当做仿函数来使用。它们都存一个类的成员函数的指针,构造函数都接受一个指针。两者的区别在于,前者重载的()
函数接受指针类型作为参数,而后者接受引用类型。
S (T::*pf) (A)
,T是这个成员函数属于的类,S是这个成员函数的返回值类型,A是这个成员函数的参数类型(或者为空,表示不接受参数),pf就是这个指针。
通过指针调用成员函数时,方法是return (p->*f)(x);
先对函数指针f解引用,然后通过指针p调用这个函数。
通过引用调用成员函数时,方法是return (p.*f(x));
,先对函数指针f解引用,然后通过引用类型p调用这个函数。
通过使用这两个配接器,可以在泛型算法中使用类的成员函数作为方法。