STL与泛型编程<十五>:预定义的仿函数和仿函数适配器

如下图,STL中预定于的这些仿函数
这里写图片描述
使用请先头文件#include
由上图可以看出,总的来说可以分成一元仿函数(只有一个参数)和二元仿函数(两个参数);又可以分为:算术类,相对关系类,逻辑运算类。

函数适配器(Function Adapters)调用原理分析

binder1st and bind1st

目的在于将无法匹配的仿函数适配成可以匹配的型别,类似于生活中的插座。先看一个例子1:给定一个vector,其元素为【0,0,0,10,0,2】,如何通过not_equal_to匹配到第一个非零元素呢?也许你会这么解

vector<int>::iterator pos = find_if(col.begin(),col.end(),not_equal_to<int>(0));

但是却行不通,原因有两个
1. not_equal_to构造函数不接受参数“0”
2. not_equal_to的operator()不接受一个参数,需要两个参数!
其C++98声明如下所示

template <class T> 
struct not_equal_to : binary_function <T,T,bool> 
{
  bool operator() (const T& x, const T& y) const
  {return x!=y;}
};

其C++11声明如下:

template <class T> 
struct not_equal_to
{
  bool operator() (const T& x, const T& y) const {return x!=y;}
  typedef T first_argument_type;
  typedef T second_argument_type;
  typedef bool result_type;
};

解决办法是通过binder1st套接not_equal_to,可以看出仿函数的作用就是:如果想要用的仿函数,由于参数方面不符合要求,那么可以通过适配器来进行适配。

先来看下binder1st型别的部分定义

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 Operation::first_argument_type& y) : op (x), value(y) {}

  typename Operation::result_type operator() (const typename Operation::second_argument_type& x) const
    { return op(value,x); }
};
/*
可以看出
1. binder1st是个template class型别,共有两个成员变量:op和value,其中根据命名含义可以得知op是操作(函数),而value是个op操作中所需的第一个参数;
2.binder1st共有两个成员函数,一个构造函数,一个重载了operator()运算符;
3.其中构造函数需要两个参数来初始化其成员,显然一个是op,一个是value;
4.其operator()操作返回op(value,x),显然value就是op操作的第一个参数,而还需要传入第二个参数x。
5.由于其重载了operator()操作,可以说binder1st也是个仿函数
*/

再看下not_equal_to的源代码(C++ 11)

template <class T> 
struct not_equal_to 
{
  bool operator() (const T& x, const T& y) const {return x!=y;}
  typedef T first_argument_type;
  typedef T second_argument_type;
  typedef bool result_type;
};

接下来看使用过程

typename not_equal_to<int>::first_argument_type nonZero(0);
not_equal_to<int> f;
vector<int> pos;
pos = find_if(col.begin(),col.end(),
            binder1st<not_equal_to<int> >(f,nonZero));
/*
1.显然是把binder1st当作仿函数使用;
2.由上面的定义可知,binder1st是个template型别,需要op(操作)来初始化;
3.调用其operator()操作,由上面其binder1st源代码看出f即是op操作,nonZero即是Operation::first_argument_type value;
4.因此就是调用op(value,x)也就是f(value,x),也就是not_equal_to<int>(value,x);
5.显然value是左值,也就是nonZero(就是0啦),那么x(右值是谁),可以猜到,正式find_if()中迭代器范围中的每个值。
6.因此find_if(...)调用就可以看成如下行为了:
for (pos=beg; pos!=end; ++pos)
    if (not_equal_to(value,*pos))
    break;
*/

然后看下find_if的源代码

template<class InputIterator, class UnaryPredicate>
  InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{
  while (first!=last) {
    if (pred(*first)) return first;
    ++first;
  }
  return last;
}
/*
可以知道其实调用pred的operator()操作
*/

实际调用的时候,其实更简单,用bind1st(template function)替代binder1st(template class)

vector<int>::iterator pos;
pos = find_if(vol.begin(),col.end(),bind1st(not_equal_to<int>(),0);//因此这个0就是not_equal_to()操作中的左值,而容器中的元素就是其右值

因此这个0就是not_equal_to()操作中的左值,而容器中的元素就是其右值

bind1st封装了binder1st的复杂性

template <class Operation, class T>
  binder1st<Operation> bind1st (const Operation& op, const T& x)
{
  return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
/*
可知需要带入两个参数:第一个参数是仿函数,第二个是个值(左值)
*/

binder2nd and bind2st

其实bind2nd和bind1st也一样,区别在于bind2nd中的参数value是其参数op的右值,而bind1st是左值。下面看代码就清楚了

template <class Operation, class T>
  binder1st<Operation> bind1st (const Operation& op, const T& x)
{
  return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
/*
可以看出x是op操作的第一个参数(左值)
*/
template <class Operation, class T>
  binder1st<Operation> bind1st (const Operation& op, const T& x)
{
  return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
/*
可以看出x是op操作的第二个参数(左值)
*/

下面看一个使用两个适配器对比的例子

vector<int>::iterator pos;
pos = find_if(col.begin(),col.end(),bind1st(less<int>(),0)); //0<容器的元素

pos = find_if(col.begin(),col.end(),bind2nd(greater<int>(),0)); //容器的元素>0 
/*
这两个表达式是等效的
*/

适配器

所谓的函数适配器就是将仿函数和另一个仿函数(或某个值,或一般函数)结合起来组成一个仿函数,其声明也在#include 中
如以下

find_if(col.begin(),col.end(),bind2nd(greater<int>(),42))
/*
解释:greater<int>()
其中greater是个template型别,因此需要指定类型,如int,则写成greater<int>,然后再产生一个临时对象则可以写成greater<int>(),且会调用其operator()成员函数(而这个是由于算法决定的)
*/

其中bind2nd(greater(),42)是一个组合仿函数,检查某个值是否大于42,实际上是bind2nd是将一个二元仿函数(greater<>)转换为一元仿函数,其中42作为二元仿函数greater<>的第二个参数。因此上诉程序以42来作为参数来调用greater<>。
如下图是一些预定义的函数适配器及其效果

函数适配器也是仿函数,因此可以结合仿函数以形成更为强大(更复杂)的表达式,如下

pos = find_if(col.begin(),col.end(),not1(bind2nd(modulus<int>(),2)));
/*
bind2nd(modulus<int>(),2)表示对2取模,其中奇数会返回1(相当于true),偶数会返回0(false),因此是一个寻找奇数的,而not1又使之反过来了
*/

针对成员函数而设计的函数适配器

为什么需要成员函数的适配器?
对于函数及对象obj,在obj上调用f的形式有以下三种
(1)f(obj);//f是全局函数(非obj的成员函数)
(2)obj.f();//f是成员函数,obj非指针
(3)obj->f();//f是成员函数,obj指针
但是看下STL中算法的实现过程就可知道了,比如for_each

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function fn)
{
  while (first!=last) {
    fn (*first);
    ++first;
  }
  return fn;      // or, since C++11: return move(fn);
}

如果这样情况下就只能使用(1)的形式了
而我们是通过 mem_fun(容器中的元素是指针就使用这个)/mem_fun_ref(否则使用这个)来实现调用对象的成员函数的
就是透过这些适配器你可以对区间的每个元素都调用其成员函数,如下
例子如下

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <functional>

using namespace std;

class Person
{
    private:
        string _name;
    public:
        Person(string name)
        : _name(name) {}
        void print() const 
        { 
            cout << _name << endl;
        }
        void print_with_prefix(const string& prefix) const
        {
            cout << prefix << _name << endl;
        }
};

void foo(const vector<Person>& col)
{
    for_each(col.begin(),col.end(),mem_fun_ref(&Person::print));  //刚开始错写为&Person::print(),不需要加() 
    //for_each(col.begin(),col.end(),bind2nd(mem_fun_ref(&Person::print_with_prefix("person: ")))); 开始的错误写法
    string str = "person : ";
    for_each(col.begin(),col.end(),bind2nd(mem_fun_ref(&Person::print_with_prefix),str));
    //但是这样就会出现乱码?为什么 
}

int main(void)
{   
    vector<Person> col;
    Person p1("david");
    Person p2("summer");
    col.push_back(p1);
    col.push_back(p2);
    foo(col);  
    return 0;
}  

若将类型换为vector

让自己定义的仿函数可以使用适配器

你可以编写自己的仿函数(如前面的例子),但如果要希望它们能够和函数适配器搭配使用的话,就必须满足以下:提供一些型别成员来反映其参数和返回值的型别。C++标准程序库提供了以下的一些结构

template<typename Arg, typename Result>
struct unary_function
{
    typedef Arg argument_type;
    typedef Result result_type;
}

template<typename Arg1, typename Arg2, typename Result>
struct binary_function
{
    typedef struct first_argument_type;
    typedef struct second_argument_type;
    typedef struct result_type;
}

见下例子

#include <iostream>
#include <functional>
#include <cmath>
#include <algorithm> 
#include <iterator>
#include <vector>
using namespace std;

template<typename T1, typename T2>
class fopow : public binary_function<T1, T2, T1>
{
    public:
    T1 operator() (T1 base, T2 exp) const
    {
        return pow(base,exp);   
    }   
};

int main(void)
{
    vector<int> col;
    for (int i=1; i<=9; ++i)
        col.push_back(i);

    transform(col.begin(),col.end(),ostream_iterator<int>(cout," "),bind1st(fopow<float,int>(),3));
    cout << endl;

    transform(col.begin(),col.end(),ostream_iterator<int>(cout," "),bind2nd(fopow<float,int>(),3));
    return 0;
 } 
 /*
 输出:3 9 27 81 243 729 2187 6561 19683
       1 8 27 64 125 216 343 512 729 
 */

针对一般函数(非成员函数)使用的函数适配器

可以使用函数适配器ptr_fun,规则如下

ptr_fun(op);
/*
可翻译如下:
(1)op(param);
(2)op(param1,param2);
*/

看一个例子,一个参数的全局函数

bool check(int elem);
pos = find_if(col.begin(),col.end(),not1(ptr_fun(check)));

两个参数的全局函数

pos = find_if(col.begin(),col.end(),bind2nd(ptr_fun(strcmp)," "));
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值