当前位置: 首页 > news >正文

STL之适配器(adapters)_下

STL之适配器adapters

  • container adapers
    • stack
    • queue
  • iterator adaptgers
    • insert iterators
    • reverse iterators
    • stream iterators
  • function adapters
    • 对返回值进行逻辑判断:not1,not2
    • 对参数进行绑定:bind1st, bind2nd
    • 用于函数合成:compose1,compose2
    • 用于函数指针 ptr_func
    • 用于成员函数指针:mem_fun,mem_fun_ref

container adapers

stack

stack的底层由dequeu构成。从以下接口可清楚看出stack与deque的关系:

template <class T, class Sequence = deque<T> >
class stack {
protected:Sequence c; 	// 底层容器...
};

C++ Standard规定客户端必须能够从<stack>中获得stack的接口,SGI STL则把所有的实现细节定义于<stl_stack.h>中,参考stack。class stack封住了所有deque对外接口,只开放符合stack原则的几个函数,所以我们说stack是一个适配器,一个作用于容器的适配器。

queue

queue的底层实现也是deque,从以下接口可以清楚看出queue与deque的关系:

template <class T, class Sequence = deque<T>>
class queue {protected:Sequence c; 		// 底层容器...
};

C++ Standard规定客户端必须能够从<queue>中获得queue的接口,SGI STL则把所有的实现细节定义于<stl_queue.h>内,参见queue。class queue屏蔽了所有deque对外的接口,只开放符合queue原则的几个函数,所以我们说queue是一个适配器,一个作用于容器之上的适配器。

iterator adaptgers

本章稍早已说过iterator adapters的意义和用法,以下研究其实现细节。

insert iterators

下面三种insert iterators的完整实现列表表。其中的主要观念是,每一个insert iterators内部都维护有一个容器(必须由用户指定):容器当然有自己的迭代器,于是,当客户端对insert iterators做赋值(assgin)操作时,就在insert iterators的operator=操作符中调用底层容器的push_front()或push_back()或insert()操作函数。至于其它的迭代器惯常行为如operator++,operator++(int),operator*都被关闭(只保留了可编译通过的语法,无实际意义),更没有提供operator- -或operator- -(int)或operator->等功能(因此类型被定义为output_iterator_tag)。换句话说,insert iterators的前进、后退、取值、成员取用等操作都没有意义的,甚至是不被允许的。
代码如下所示:
back_inserter

// 这是一个迭代器适配器,用来将某个迭代器的赋值操作
// 修改为插入操作--从容器的头端插入进去(所以称为front_insert)
template <class Container>
class front_insert_iterator {
protected:Container * container;
public:typedef ouput_iterator_tag  iterator_categroy;      // 注意型别typedef void                value_type;typedef void                difference_type;typedef void                pointer;typedef void                reference;// 下面这个ctor使front_insert_iterator与容器绑定起来explicit front_insert_iterator(Container& x) : container(&x) {}front_insert_iterator<Container>&operator=(const typename Container::value_type& value) {container->push_front(value);return *this;}// 以下三个操作符对front_insert_iterator不起作用(关闭功能)// 三个操作符返回的都是front_insert_iterator自己front_insert_iterator<Container>& operator*() {return *this; }front_insert_iterator<Container>& operator++() {return *this; }front_insert_iterator<Container>& operator++(int) {return *this; }
};// 这是一个辅助函数,帮助我们方便使用front_insert_iterator
template <class Container>
inline front_insert_iterator<Container> front_inserter(Container& x) {return front_insert_iterator<Container>(x);
}

front_inserter

// 这是一个迭代器适配器,用来将某个迭代器的赋值操作
// 修改为插入操作--从容器的头端插入进去(所以称为front_insert)
template <class Container>
class front_insert_iterator {
protected:Container * container;
public:typedef ouput_iterator_tag  iterator_categroy;      // 注意型别typedef void                value_type;typedef void                difference_type;typedef void                pointer;typedef void                reference;// 下面这个ctor使front_insert_iterator与容器绑定起来explicit front_insert_iterator(Container& x) : container(&x) {}front_insert_iterator<Container>&operator=(const typename Container::value_type& value) {container->push_front(value);return *this;}// 以下三个操作符对front_insert_iterator不起作用(关闭功能)// 三个操作符返回的都是front_insert_iterator自己front_insert_iterator<Container>& operator*() {return *this; }front_insert_iterator<Container>& operator++() {return *this; }front_insert_iterator<Container>& operator++(int) {return *this; }
};// 这是一个辅助函数,帮助我们方便使用front_insert_iterator
template <class Container>
inline front_insert_iterator<Container> front_inserter(Container& x) {return front_insert_iterator<Container>(x);
}

注意因为vector没有push_front()接口,所以front_inserter不适合vector
inert_iterator

// 这是一个迭代器适配器(iterator adapter),用来将某个迭代器的赋值操作
// 修改为插入操作--在指定的位置上进行,并将迭代器右移一个位置
// --如此便可很方便地连续执行”表面上是赋值而实际上是插入“的操作
template <class Container>
class insert_iterator {
protected:Container * container;typename Container::iterator iter;
public:typedef ouput_iterator_tag  iterator_categroy;      // 注意型别typedef void                value_type;typedef void                difference_type;typedef void                pointer;typedef void                reference;insert_iterator(Container& x, typename Container::iterator i) : container(&x), iter(i) {}insert_iterator<Container>&operator=(const typename Container::value_type& value) {iter = container->insert(iter, value);++iter;                                         // 注意此处有++操作,使insert iterator永远随目标贴身移动return *this;}// 以下三个操作符对insert_iterator不起作用(关闭功能)// 三个操作符返回的都是insert_iterator自己insert_iterator<Container>& operator*() {return *this; }insert_iterator<Container>& operator++() {return *this; }insert_iterator<Container>& operator++(int) {return *this; }
};// 这是一个辅助函数,帮助我们方便使用insert_iterator
template <class Container, class Iterator>
inline insert_iterator<Container> inserter(Container& x, Iterator i) {return insert_iterator<Container>(x, i);
}

reverse iterators

所谓revers iterator,就是将迭代器的移动行为倒转。如果STL算法接受的不是一般的正常迭代器。而是这种逆向迭代器,它就会以从尾到头方向来处理序列中的元素。例如:

// 将所有元素逆向拷贝到ite的所指位置上
// rbegin()和rend()与reverse_iterator有关
copy(id.rbegin(), id.rend(), ite);

看似单纯,实现时却大有文章。
首先我们看看rbegin()和rend()。任何容器都提供有这两个操作,我们在本专栏前面的内容中介绍了各种容器时,鲜有列出这两个成员函数,现在举例瞧瞧:

template <class T, class Alloc = alloc>
class vector{
public:typedef T value_type;typedef value_type* iterator;typedef reverse_iterator<iterator> reverse_iterator;reverse_iterator rbegin() {return reverse_iterator(end()); }reverse_iterator rend() {return reverse_iterator(begin()); }...
};

list和deque中的rbegin()及rend()的定义与vector中的定义极其类似。
没有任何例外!只要双向序列容器提供了begin(), end(),它的rbegin(),rend()就是上面那样的形式。单向序列容器slist不可使用reserve iterators.有些容器如stack,queue,priority_queue并不提供begin()和end(),当然也就没有rbegin(),rend().
现在我们实际看看rbegin()及rend()取出的值分别是什么。代码示例如下:

#include <deque>
#include <iostream>
using namespace std;int main() {int ia[] = {1, 2, 3, 4, 5, 6,7,8};deque<int>id(ia, ia+8);cout << *(id.begin()) << endl; // 1cout << *(id.rbegin()) << endl; // 8cout << *(id.end()) << endl; // 0 dangerouscout << *(id.rend()) << endl; // 0 dangerousreturn 0;
}

为什么“正向迭代器”和“与其相应的逆向迭代器”取出不同的元素呢?这并不是一个潜在的错误,而是刻意为之的特征,主要是为了配合迭代器区间的“前闭后开”习惯。如下图所示,当迭代器逆转时,虽然其实体位置(真正的地址)不变,但其逻辑位置(迭代器所代表的元素)改变了:
图1

唯有这样,才能够保持正向迭代器的一切惯常行为。换句话说,唯有这样,当我们将一个正向迭代器转换为一个逆向迭代器区间后,不必再有任何额外处理,就可以让接受这个逆向迭代器区间的算法,以相反的元素次序来处理区间中的每一个元素。例如:

    copy(id.begin(), id.end(), outite);   // 1 2 3 4 5 6 7 8cout << endl;copy(id.rbegin(), id.rend(), outite); // 8 7 6 5 4 3 2 1cout << endl;

注意,上述的id.rbegin()是个暂时对象,相当于:

reverse_iterator<deque<int>::iterator>(id.end());			// 指向本例的最后元素
deque<int>::reverse_iterator(id.end());					    // 指向本例的最后元素

其中deque::reverse_iterator是一种型别定义,稍早已经展现过。
以下是reverse_iterator的源码:

// 这是一个迭代器适配器,用来将迭代器逆反器逆反前进方向,
// 使前进为后退,后退为前进
template <class Iterator>
class reverse_iterator {
protected:Iterator current;           // 记录对应正向迭代器
public:// 逆向迭代器的5种型别(associated types)都和其对应正向迭代器相同typedef typename iterator_traits<Iterator>::iterator_category iterator_category;typedef typename iterator_traits<Iterator>::value_type value_type;typedef typename iterator_traits<Iterator>::difference_type difference_type;typedef typename iterator_traits<Iterator>::pointer pointer;typedef typename iterator_traits<Iterator>::reference reference;typedef Iterator iterator_type;                 // 代表正向迭代器typedef reverse_iterator self;                  // 代表逆向迭代器
public:reverse_iterator();explicit reverse_iterator(iterator_type x): current(x) {}reverse_iterator(const self& x) : current(x.current) {}iterator_type base() const { return current; }reference operator*() const {Iterator tmp = current;return *--tmp;// 以上为关键所在。对逆向迭代器取值,就是将“对应之正向迭代器”后退一格而后取值}pointer operator->() const { return &(operator*()); }  // 意义同上self& operator++() {--current;return *this;}self operator++(int) {self tmp = *this;--current;return tmp;}self& operator--() {++current;return *this;}self operator--(int) {self tmp = *this;++current;return tmp;}// 前进与后退方向完全逆转self operator+(difference_type n) const {return self(current - n);}self& operator+=(difference_type n) const {current -= n;return *this; }self operator-(difference_type n) const {return self(current + n);}self& operator-=(difference_type n) const {current += n;return *this; }// 注意,下面的第一个*(return后边)和+都会调用本例中的operator*和opertor+// 第二个(this前边)。(判断法则:完全看处理的型别是什么而定)refrence operator[](difference n) const { return *(*this + n); }
};

以下为测试代码

#include <deque>
#include <iostream>
using namespace std;int main() {int ia[] = {1, 2, 3, 4, 5, 6,7,8};deque<int>id(ia, ia+8);ostream_iterator<int> outite(cout, " ");deque<int>::reverse_iterator rite2(id.end());cout << *rite2 << endl;              // 8 队尾cout << *(rite2 += 3) << endl;       // 5 队尾后退三个位置cout << *(--rite2) << endl;          // 6 当前位置前进一个位置                              cout << *(rite2.base()) << endl;     // 7 回到正向迭代器cout << rite2[3] << endl;            // 3 后退三个位置取值return 0;
}

下图显示了上述介个操作队逆向迭代器所造成的移动。
图2
图中[3]的连线之所以为虚线,因为只是取值时迭代器发生了变更,rite2实际上没有发生变更。

stream iterators

所谓stream iterators,可以将迭代器绑定到一个stream对象身上。绑定到istream对象(例如:std::cin)者,称为istream_iterator,称为istream_iterator,拥有输入能力;绑定到ostream对象(例如std::cout)者,称为ostream_iterator,拥有输出能力。两者的用法在之前的例子中都有示范。
所谓绑定一个istream object,其实就是在istream iterator内部维护一个istream memeber,客户端对于这个迭代器所做的operator++操作,会被导引调用迭代器内部所含的那个istream member的输入操作(operator>>).这个迭代器是个Input Iterator,不具备operator- -。下面的源码和注释可说明一切。

// 这是一个input iterator,能够为“来自某一basic_istream"的对象执行
// 格式化输入操作。注意,此版本为旧有之HP规格,未符合标准接口:istream_iterator<T, charT, traits, Distance>
// 然后一般使用input iterators时都只是用第一个template参数,此时以下仍适用
// 注:SGI STL 3.3已实现出符合标准接口的istream_iterator。做法与版本大同小异,本版本可读性较高
template <class T, class Distance = ptrdiff_t>
class istream_iterator {friend bool operator == __STL_NULL_TMPL_ARGS ( const istream_iterator<T, Distance>& x,const istream_iterator<T, Distance>& y);
protected:istrem* stream;T value;bool end_marker;void read() {end_marker = (*stream) ? true : false;if (end_marker) *stream >> value;// 以上,输入之后,stream的状态可能改变,所以下面再判断一次以决定end_marker// 当读到eof或读到型别不符的内容,stream即处于false状态end_marker = (*stream) ? true : false;}istream_iterator() : stream(&cin), end_marker(false) {}istream_iterator(istream&s) : stream(&s) { read(); }// 以上两行的用法// istream_iterator<int> eos;        造成end_marker为false;// istream_iterator<int> initer(cin);    引发read(),程序至此会等待输入// 因此,下面两行客户端程序:// istream_iterator<int> initer(cin);       (A)// cout << "please input ..." << endl;      (B)// 会停留在(A)等待一个输入,然后才执行(B)出现提示信息。这是不合理的现象// 规避之道:永远在最必要的时候,才定义一个istream_iterator.referece operator*() const { return value; }pointer operator->() cosnt { return &(operator*()); }// 迭代器前进一个位置,就代表要读取一项istream_iterator<T, Distance>& operator++() {read();return *this;}istream_iterator<T, Distance>& operator++(int) {istream_iterator<T, Distance> tmp = *this;    read();return tmp;}
};

请注意,源代码清楚地告诉我们,只要客户端定义一个istream iterator并绑定到某个istream object,程序便立刻停在istream_iterator::read()函数,等待输入,这不是我们所预期的行为,因此,请在绝对必要的时候才定义所需要的istream iterator—这其实是C++程序员在任何时候都应该遵循的守则,但是很多人都忽略了。
ostream iterator,所谓绑定一个ostream object,就是在其内部维护一个ostream member,客户端对于这个迭代器所做的operator=操作,会被引导调用对应的(迭代器内部所含的)那个ostream member的输出操作(operator<<)。这个迭代器是个OutputIterator。下面是源码:

// 这是一个output iterator,能够将对象格式化输出到某个basic_ostream上
// 注意,此版本为旧有之HP规格,未符合标准接口:ostream_iterator<T, charT, traits>
// 然而一般使用output iterators时都只使用第一个template参数,此时以下仍适用
// 注:SGI STL 3.3已实现出符合标准接口的ostream_iterator。做法与版本大同小异,本版本可读性较高
template <class T>
class ostream_iterator {
protected:ostream* stream;const char* string;typedef output_iterator_tag iterator_category;typedef void                value_type;typedef void                difference_type;typedef void                pointer;typedef void                reference;ostream_iterator(ostream&s) : stream(&s), string(0) {}ostream_iterator(ostream&s, const char* c) : stream(&s), string(c) { }// 以上ctor的用法// ostream_iterator<int> outiter(cout, " "); 输出到cout,每次间隔一空格ostream_iterator<T> operator*()  { return *this; }ostream_iterator<T> operator++()  { return *this; }ostream_iterator<T> operator++(int)  { return *this; }ostream_iterator<T>& operator=(const T&value) {*stream << value;if (string) *stream << string;return *this;}
};

这两个迭代器,istream_iterator和ostream_iterator,非常重要,这两个迭代器的源码向我们展示了如何为自己量身定制一个迭代器,绑定于我们属意的装置上。正如在本章一开始的概览说明中提到过,以这种迭代器为蓝图稍加修改,便有非常大的应用空间,可以完成一个绑定到Internet Explorer cache身上的迭代器,也可以完成一个绑定到磁盘目录上的迭代器。也可以。。。,泛型世界无限宽广。

function adapters

适配器介绍已经对function adapters做了介绍,并举了一个颇为丰富的运用实例。从这里开始,我们就直接探索SGI STL function adapters的实现细节吧。
一般而言,对于C++ template语法有了某种程度的了解之后,我们很能够理解或想象,容器是以class templates完成,算法以function template完成,仿函数是一种operator()重载class template,迭代器则是一种将operator++和operator*等指针惯常行为重载class template。然后适配器内,应用于容器身上和迭代器身上的适配器,已于本章稍早介绍过,都是一种class template。可应用于仿函数身上的适配器呢?如何能够“事先”对一个函数完成参数的绑定、执行结果的否定、甚至多方函数的组合?请注意我用“事先”一词。我的意思是,最后修饰结果(视为一个表达式,expression)将被给STL算法使用,STL算法才是真正使用这表达式的主体。而我们都知道,只有在真正使用(调用)某个函数(或仿函数)时,才有可能对参数和执行结果做任何干涉。
这是怎么回事?
关键在于,就像本章先前所揭示,container adapters内藏了一个container member一样,或是像revers iterator(adapters)内藏了一个iterator member一样,或是像stream iterator(adapters)内藏了一个pointer to stream一样,或是像insert itertator(adapters)内藏了一个pointer to container(并因而得以取其iterator)一样,每一个function adapters也内藏了一个member object,其型别等同于它所要适配的对象(那个对象当然是一个“可适配的仿函数”,adapter function),下表是一份整理。当function adapter有了完全属于自己的一份修饰对象(的副本)在手,他就成了该修饰对象(的副本)的主人,也就有资格调用该修饰对象(一个仿函数),并在参数和返回值上面进行指定和结果返回了。

辅助函数(helper function)实际产生的适配器对象形式内藏成员的模式
bind1st(const Op&op, const T&x)bind1st<Op>(op, arg1_type(x))Op(二元仿函数)
bind2nd(const Op&op, const T&x)bind2nd<Op>(op, arg2_type(x))Op(二元仿函数)
not1(const Pred& pred)unary_negate<Pred>(pred)Pred放回布尔值的仿函数
not2(const Pred& pred)binary_negate<Pred>(pred)Pred放回布尔值的仿函数
compse1(const Op1&op1, const Op2 & op2)unary_compose<Op1, Op2>(op1,op2)Op1,Op2
compse2(const Op1&op1, const Op2 & op2), const Op3 &op3binary_compose<Op1, Op2, Op3>(op1,op2, op3)Op1,Op2,Op3
ptr_fun(Result(*fp)(Arg))pointer_to_unary_function<Arg,Result>(fp)Result(*fp)(Arg)
ptr_fun(Result(*fp)(Arg1, Arg2))pointer_to_binary_function<Arg1, Arg2,Result>(fp)Result(*fp)(Arg1, Arg2)
mem_fun(S (T::*f)())mem_func_t<S, T>(f)S (T::*f)()
mem_fun(S (T::*f)() const)const_mem_func_t<S, T>(f)S (T::*f)() const
mem_fun_ref(S (T::*f)())mem_ref_func_t<S, T>(f)S (T::*f)()
mem_fun_ref(S (T::*f)() const)const_mem_ref_func_t<S, T>(f)S (T::*f)() const
mem_fun1(S (T::*f)(A))mem_func1_t<S, T, A>(f)S (T::*f)(A)
mem_fun1(S (T::*f)(A) const)const_mem_func1_t<S, T, A>(f)S (T::*f)(A) const
mem_fun1_ref(S (T::*f)(A))mem_ref_func_t<S, T, A>(f)S (T::*f)(A)
mem_fun1_ref(S (T::*f)(A) const)const_mem_ref_func_t<S, T, A>(f)S (T::*f)(A) const

下图鸟瞰了count_if()搭配bind2nd(less(), 12))的例子,其中清楚显示count_if()的是怎么控制函数调用的。
图3

对返回值进行逻辑判断:not1,not2

以下直接列出源码。配合注释进行功能说明。源码中常出现的pred一词,是predicate的缩写,意指会返回真假值(bool)的表达式。
not1源码

// 以下适配器用来表示某个Adaptable Predicate的逻辑复制(logical negation)
template <class Predicate>
class unary_negate : public unary_function<typename Predicate::argument_type, bool> {
protected:predicate pred;
public:explicit unary_negate(const Predicate&x) : pred(x) {}bool operator() (const typename Predicate::argument_type& x) const {return !pred(x);}
};// 辅助函数
template <class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred) {return unary_negate<Predicate>(pred);
}

not2源码

// 以下适配器用来表示某个Adaptable Binary Predicate的逻辑复制(logical negation)
template <class Predicate>
class binary_negate : public binary_function<typename Predicate::first_argument_type,typename Predicate::second_argument_type, bool> {
protected:predicate pred;
public:explicit binary_negate(const Predicate&x) : pred(x) {}bool operator() (const typename Predicate::first_argument_type& x, const typename Predicate::second_argument_type& y) const {return !pred(x, y);}
};// 辅助函数
template <class Predicate>
inline unary_negate<Predicate> not2(const Predicate& pred) {return binary_negate<Predicate>(pred);
}

对参数进行绑定:bind1st, bind2nd

以下是源码:
bind1st源码

// 以下适配器用来将某个Adapter Binary function转换为Unary Function
template <class Operation>
class binder1st : public unary_function<typename Operation::second_argument_type,typename Operation::result_type> {
protected:Operation op;typename Operation::first_argument_type value;
public:binder1st(const Operation& x,const typename ::first_argument_type& y) : op(x), value(y) {}typename Operation::result_typeoperator() (const typename Operation::second_argument_type& x) const {return op(value, x);}
};// 辅助函数,让我们得以方便使用binder1st<Op>
template <class Operation, class T>
inline binder1st<Operation> bind1st(const Operation&op, const T&x) {typedef typename Operation::first_argument_type arg1_type;return binder1st<Operation>(op, arg1_type(x));// 以上,注意,先把x转型为op的第一参数型别
}

bind2nd源码

/ 以下适配器用来将某个Adapter Binary function转换为Unary Function
template <class Operation>
class binder2nd : public unary_function<typename Operation::first_argument_type,typename Operation::result_type> {
protected:Operation op;typename Operation::second_argument_type value;
public:binder1st(const Operation& x,const typename Operation::second_argument_type& y) : op(x), value(y) {}typename Operation::result_typeoperator() (const typename Operation::first_argument_type& x) const {return op(x, value);}
};// 辅助函数,让我们得以方便使用binder2nd<Op>
template <class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation&op, const T&x) {typedef typename Operation::second_argument_type arg2_type;return binder2nd<Operation>(op, arg2_type(x));// 以上,注意,先把x转型为op的第一参数型别
}

用于函数合成:compose1,compose2

以下直接列出源码。注意,本节的两个适配器并未纳入STL标准,SGI STL的私产品,但是颇有钻研价值,我们可以从中学习如何开发适合自己的,更复杂的适配器。
compose1源码

/// 已知两个Adapter Unary Function f(), g(),以下适配器用来产生新的f(),
// 使h(x) = f(g(x))
template <class Operation1, class Operation2>
class unary_compose : public unary_function <typename Operation2::argument_type,typename Operation1::result_type> { 
protected:Operation1 op1;Operation2 op2;
public:unary_compose(const Operation1& x, const Operation2& y) : op1(x), op2(y) {}typename Operator1::result_type operator() (const typename Operation2::argument_type& x) const {return op1(op2(x));}
};//辅助函数,让我们得以方便运用unary_compose<Op1, Op2>
template<class Operation1, class Operation2>
inline unary_compose<Operation1, Operation2>
compose1(const Operation1& op1, const Operation2& op2) {return unary_compose<Operation1, Operation2>(op1, op2);
}

compose2源码

// 已知两个Adapter Binary Function f()和两个Adapter Unary Function g1, g2
// 使h(x) = f(g1(x), g2(x)
template <class Operation1, class Operation2, class Operation3>
class binary_compose : public unary_function <typename Operation2::argument_type,typename Operation1::result_type> { 
protected:Operation1 op1;Operation2 op2;Operation3 op3;public:binary_compose(const Operation1& x, const Operation2& y,const Operation3&z) : op1(x), op2(y), op3(z) {}typename Operator1::result_type operator() (const typename Operation2::argument_type& x) const {return op1(op2(x), op3(x));}
};//辅助函数,让我们得以方便运用binary_compose<Op1, Op2, Op3>
template <class Operation1, class Operation2, class Operation3>
inline binary_compose<Operation1, Operation2, Operation3>
compose2(const Operation1& op1, const Operation2& op2, const Operation3& op3) {return binary_compose<Operation1, Operation2, Operation3>(op1, op2, op3);
}

用于函数指针 ptr_func

这种适配器使我们能够将一般函数当做仿函数使用。一般函数当做仿函数传给STL算法,就语言层面本来就是可以的,就好像原生指针可被当做迭代器传给STL算法那样。但如果不使用这里所说的两个适配器先做一番包装,我们所使用的那个一般函数将不具备适配性,也就无法和前数小节介绍的其他适配器接轨。
以下是源码:
ptr_unary_function

// 以下适配器其实就是把一元函数指针包起来
// 当仿函数被使用时,就调用该函数指针
template <class Arg, class Result>
class pointer_to_unary_function: public unary_function<Arg, Result> {
protected:Result (*ptr)(Arg);
public:pointer_to_unary_function() {}explicit pointer_to_unary_function(Result(*x)(Arg)) : ptr(x) {}Result operator() (Arg x) const { return ptr(x); }
};// 辅助函数,让我们得以方便运用pointer_to_unary_function
inline pointer_to_unary_function<Arg, Result>
ptr_fun(Result(*x)(Arg)) {return pointer_to_unary_function<Arg, Result>(x);
}

ptr_binary_function

/ 以下适配器其实就是把二元函数指针包起来
// 当仿函数被使用时,就调用该函数指针
template <class Arg1, class Arg2, class Result>
class pointer_to_binary_function: public binary_function<Arg1, Arg2, Result> {
protected:Result (*ptr)(Arg1, Arg2);
public:pointer_to_binary_function() {}explicit pointer_to_binary_function(Result(*x)(Arg1, Arg2)) : ptr(x) {}Result operator() (Arg1 x, Arg2 y) const { return ptr(x, y); }
};// 辅助函数,让我们得以方便运用pointer_to_binary_function
inline pointer_to_binary_function<Arg1, Arg2, Result>
ptr_fun(Result(*x)(Arg1, Arg2)) {return pointer_to_binary_function<Arg1, Arg2, Result>(x);
}

用于成员函数指针:mem_fun,mem_fun_ref

这种适配器使我们能够将成员函数(member function)当做仿函数来使用,于是成员函数可以搭配各种泛型算法。当容器的元素型别是X&或X*,而我们又以虚拟(virtual)成员函数作为仿函数,便可以藉由泛型算法完成多态调用(polymorphic function call)。这是 泛型(generating)与多态(polymorphism)之间的一个重要接轨。下面是一个实例:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;class Shape 
{public: virtual void display() = 0; };class Rect : public Shape
{public: virtual void display() {cout << "Rect ";} };
class Circle : public Shape
{public: virtual void display() {cout << "Circle ";} };
class Square : public Rect
{public: virtual void display() {cout << "Square ";} };int main() {vector<Shape*> v;v.push_back(new Rect);v.push_back(new Circle);v.push_back(new Square);v.push_back(new Circle);v.push_back(new Rect);// polymorphicallyfor (int i = 0; i < v.size(); ++i) (v[i])->display(); // Rect Circle Square Circle Rectcout << endl;// polymorphically and adapterfor_each(v.begin(), v.end(), mem_fun(&Shape::display));cout << endl; // Rect Circle Square Circle Rectreturn 0;
}

注意就语法而言,以下写法都是会导致编译失败的
for_each(v.begin(), v.end(), &Shape::display);
for_each(v.begin(), v.end(), Shape::display);
一定要以适配器mem_fun修饰member function,才能被算法for_each接受。
另一个必须主要的是,虽然多态可以对pointer或reference起作用,但很可惜的是,STL容器只支持“实值语意”,不支持引用语意,因此下面这样无法通过编译:
vector<Shape&> v;
以下是adapters for member function的源码。

// Adapter function objects: pointers to memeber functions.
// 这个族群一共有8个2^3个function objects
// (1) 无任何参数 vs 有一个参数
// (2) 通过pointer vs 通过reference调用
// (3) 通过const成员函数vs no-const成员函数// 无任何参数、通过pointer调用、non-const成员函数
template<class S, class T>
class mem_func_t : public unary_function<T*, S>{
public:explicit mem_func_t(S (T::*pf)()) : f(pf) {}S operator() (T* p) const {return (p->*f)();}
private:S (T::*f)();    //内部成员, pointer to function
};// 无任何参数、通过pointer调用、const成员函数
template<class S, class T>
class const_mem_func_t : public unary_function<T*, S>{
public:explicit const_mem_func_t(S (T::*pf)() const) : f(pf) {}S operator() (T* p) const {return (p->*f)();}
private:S (T::*f)() const;    //内部成员, pointer to function
};// 无任何参数、通过reference调用、non-const成员函数
template<class S, class T>
class mem_func_ref_t : public unary_function<T, S>{
public:explicit mem_func_ref_t(S (T::*pf)()) : f(pf) {}S operator() (const T& r) const {return (r.*f)();}
private:S (T::*f)();    //内部成员, pointer to function
};// 无任何参数、通过reference调用、const成员函数
template<class S, class T>
class const_mem_func_ref_t : public unary_function<T, S>{
public:explicit const_mem_func_ref_t(S (T::*pf)() const) : f(pf) {}S operator() (T& r) const {return (r.*f)();}
private:S (T::*f)() const;    //内部成员, pointer to function
};//  有一个参数、通过pointer调用、non-const成员函数
template<class S, class T, class A>
class mem_func1_t : public binary_function<T*, A, S>{
public:explicit mem_func1_t(S (T::*pf)(A)) : f(pf) {}S operator() (T* p, A x) const {return (p->*f)(x);}
private:S (T::*f)(A);    //内部成员, pointer to function
};// 有一个参数、通过pointer调用、const成员函数
template<class S, class T, class A>
class const_mem_func1_t : public binary_function<T*, A, S>{
public:explicit const_mem_func1_t(S (T::*pf)(A) const) : f(pf) {}S operator() (T* p, A x) const {return (p->*f)(x);}
private:S (T::*f)(A) const;    //内部成员, pointer to function
};// 有一个参数、通过reference调用、non-const成员函数
template<class S, class T, class A>
class mem_func1_ref_t : public binary_function<T, A, S>{
public:explicit mem_func1_ref_t(S (T::*pf)(A)) : f(pf) {}S operator() (const T& r, A x) const {return (r.*f)(x);}
private:S (T::*f)(A);    //内部成员, pointer to function
};// 有一个参数、通过reference调用、const成员函数
template<class S, class T, class A>
class const_mem_func1_ref_t : public binary_function<T, S>{
public:explicit const_mem_func1_ref_t(S (T::*pf)(A) const) : f(pf) {}S operator() (T& r, A x) const {return (r.*f)(x);}
private:S (T::*f)(A) const;    //内部成员, pointer to function
};

辅助函数

template <class S, class T>
inline mem_fun_t<S, T> mem_fun(S (T::*f)()) {return mem_fun_t<S, T>(f);
}
template <class S, class T>
inline const_mem_fun_t<S, T> mem_fun(S (T::*f)() const) {return const_mem_fun_t<S, T>(f);
}template <class S, class T>
inline mem_fun_ref_t<S, T> mem_fun_ref(S (T::*f)()) {return mem_fun_ref_t<S, T>(f);
}template <class S, class T>
inline const_mem_fun_ref_t<S, T> mem_fun_ref(S (T::*f)() const) {return const_mem_fun_ref_t<S, T>(f);
}// 注意:以下四个函数,其实可以采用和以上四个函数相同的名称(函数重载)。
// 事实上C++ Standard也的确这么做的
template <class S, class T, class A>
inline mem_fun1_t<S, T, A> mem_fun1(S (T::*f)()) {return mem_fun1_t<S, T, A>(f);
}
template <class S, class T, class A>
inline const_mem_fun1_t<S, T, A> mem_fun1(S (T::*f)() const) {return const_mem_fun1_t<S, T, A>(f);
}template <class S, class T, class A>
inline mem_fun1_ref_t<S, T, A> mem_fun1_ref(S (T::*f)()) {return mem_fun1_ref_t<S, T, A>(f);
}template <class S, class T, class A>
inline const_mem_fun1_ref_t<S, T, A> mem_fun1_ref(S (T::*f)() const) {return const_mem_fun1_ref_t<S, T, A>(f);
}

参考文档1


  1. 1:STL源码剖析/侯捷 ↩︎

相关文章:

STL之适配器(adapters)_下

STL之适配器adapters container adapersstackqueue iterator adaptgersinsert iteratorsreverse iteratorsstream iterators function adapters对返回值进行逻辑判断:not1,not2对参数进行绑定:bind1st, bind2nd用于函数合成&#xff1a;compose1,compose2用于函数指针 ptr_func…...

基于51单片机64位病床呼叫系统设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机病床呼叫系统设计( proteus仿真程序设计报告原理图讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0095 1. 主要功能&#xff1a; 基于51单片机的病床呼叫系统proteus仿…...

安装 Zookeeper 和 Kafka

注意&#xff1a;需要java环境 [roothcss-ecs-2a6a ~]# java -version java version "17.0.12" 2024-07-16 LTS Java(TM) SE Runtime Environment (build 17.0.128-LTS-286) Java HotSpot(TM) 64-Bit Server VM (build 17.0.128-LTS-286, mixed mode, sharing) [roo…...

操作系统输入输出系统知识点

I/O系统的功能、模型和接口 I/O系统的基本功能 隐藏物理设备的细节与设备的无关性提高处理机和I/O设备的利用率对1/0 设备进行控制确保对设备的正确共享 独占设备&#xff0c;进程应互斥地访问这些设备共享设备&#xff0c;在一段时间内允许多个进程同时访问的设备 错误处理 I…...

C语言控制语句与案例

控制语句与案例 1. 选择结构 1.1 if 语句 if 语句用于根据条件执行不同的代码块。最基本的语法形式如下&#xff1a; // 单分支 if (条件) {// 条件为真时执行的代码 }// 双分支 if (条件) {// 条件为真时执行的代码 } else {// 条件为假时执行的代码 }// 多分支 if (条件1…...

JVM的内存布局

Java虚拟机&#xff08;JVM&#xff09;的内存布局可以分为几个主要部分&#xff0c;每个部分都有特定的用途。以下是JVM内存布局的基本组成&#xff1a; 方法区&#xff08;Method Area&#xff09;&#xff1a; 方法区是所有线程共享的内存区域&#xff0c;用于存储已被虚拟机…...

aws codepipeline + github + sonarqube + jenkins实践CI/CD

https://blog.csdn.net/u011564831/article/details/144007981文章浏览阅读1.2k次&#xff0c;点赞31次&#xff0c;收藏21次。本文使用 Jenkins 结合 CodeBuild, CodeDeploy 实现 Serverless 的 CI/CD 工作流&#xff0c;用于自动化发布已经部署 lambda 函数。在 AWS 海外区&a…...

mistralai 部署笔记

目录 mistralai 部署笔记 mistralai 部署笔记 #! /usr/bin/env python3 import os import sys import torch os.chdir(os.path.dirname(os.path.abspath(__file__)))current_dir = os.path.dirname(os.path.abspath(__file__))paths = [os.path.abspath(__file__).split(scri…...

Java——异常机制(上)

1 异常机制本质 (异常在Java里面是对象) (抛出异常&#xff1a;执行一个方法时&#xff0c;如果发生异常&#xff0c;则这个方法生成代表该异常的一个对象&#xff0c;停止当前执行路径&#xff0c;并把异常对象提交给JRE) 工作中&#xff0c;程序遇到的情况不可能完美。比如…...

坐标系,向量_batch及向量点乘部分知识

坐标系 Unity所采用的是左手坐标系。 对于Vector3.forward ,其坐标值为&#xff08;0&#xff0c;0&#xff0c;1&#xff09;&#xff0c;为定值 而transform.forward 该值不固定&#xff0c;本地坐标正方向所在世界坐标系中的方向 向量 向量是终点位置减去起始点位置得…...

【计算机网络】期末速成(2)

部分内容来源于网络&#xff0c;侵删~ 第五章 传输层 概述 传输层提供进程和进程之间的逻辑通信&#xff0c;靠**套接字Socket(主机IP地址&#xff0c;端口号)**找到应用进程。 传输层会对收到的报文进行差错检测。 比特流(物理层)-> 数据帧(数据链路层) -> 分组 / I…...

【设计模式】结构型设计模式总结之代理模式、装饰模式、外观模式、享元模式

文章目录 代理模式示例结构分类动态代理 装饰模式示例结构使用场景与代理模式区别Context 外观模式结构示例使用场景Context 享元模式结构示例使用场景Message 代理模式 代理模式&#xff08;Proxy Pattern&#xff09; 是一种结构型设计模式&#xff0c;它提供了一个代理对象…...

11进阶篇:专业课论文阅读方向指南(2025版)

文章目录 第一个检索式:图情档核心期刊(北大 + CSSCI)发文情况研究方法类关键词研究主题类关键词论文阅读建议第二个检索式:川大公共管理学院在核心期刊(北大 + CSSCI)的发文情况研究方法类关键词研究主题类关键词特点关键词与2024年972(现815)两道题目的映射情况815信…...

watch里可以写异步吗

在Vue的 watch 中可以写异步&#xff0c;但通常不推荐。 原因 - 可维护性差&#xff1a; watch 的主要用途是响应式地监听数据变化。如果在里面写复杂的异步操作&#xff0c;会让代码逻辑变得难以理解和维护。例如&#xff0c;同时监听多个数据变化并触发不同异步操作时&am…...

基于 Spring Boot + Vue 的宠物领养系统设计与实现

引言 近年来&#xff0c;随着人们生活水平的提高&#xff0c;宠物逐渐成为许多家庭的重要成员。然而&#xff0c;宠物的流浪和弃养问题日益严重&#xff0c;这促使社会对宠物领养的需求不断增长。为解决宠物领养中信息不对称、领养流程复杂等问题&#xff0c;设计并实现一个基…...

leetcode399:除法求值

给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件&#xff0c;其中 equations[i] [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。 另有一些以数组 queries 表示的问题&#xff0c;其中 queries[j]…...

【10】MySQL中的加密功能:如何使用MD5加密算法进行数据加密

文章目录 1. MySQL加密功能概述2. MD5加密算法3. 在MySQL中使用MD5加密4. 使用更安全的加密方法总结 在现代的数据库应用中&#xff0c;数据的安全性和隐私性变得尤为重要。无论是存储用户的个人信息&#xff0c;还是保护敏感的业务数据&#xff0c;确保这些数据不会被未授权访…...

CSS的2D和3D动画效果

CSS的2D和3D动画效果&#xff1a;网页动态设计的魔法 在现代网页设计中&#xff0c;动画已经成为提升用户体验的重要元素。通过引入动态效果&#xff0c;我们不仅可以使交互更加流畅和直观&#xff0c;还能吸引用户的注意力&#xff0c;增强品牌认知度。CSS提供了强大的工具&a…...

30天学会Go--第9天 GO语言 Mysql 学习与实践

30天学会Go–第9天 GO语言 MySQL学习与实践 文章目录 30天学会Go--第9天 GO语言 MySQL学习与实践前言一、MySQL 基础知识1.1 MySQL 的核心特征1.2 MySQL 的常见使用情景 二、安装 MySQL2.1 Windows 安装2.2 macOS 安装2.3 Linux 安装 三、MySQL 常用命令3.1 数据库操作3.2 表操…...

跟李笑来学美式俚语(Most Common American Idioms): Part 54

Most Common American Idioms: Part 54 前言 本文是学习李笑来的Most Common American Idioms这本书的学习笔记&#xff0c;自用。 Github仓库链接&#xff1a;https://github.com/xiaolai/most-common-american-idioms 使用方法: 直接下载下来&#xff08;或者clone到本地…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...