C++沉思录__函数对象与函数对象适配器

所谓的函数对象,就是重载了类的()作用符的类对象,其行为类似于函数,因而又叫做仿函数。函数对象提供了一种方法,把将要调用的函数与准备递给这个函数的隐式参数绑定了起来。这样,我们就可以用简单的语法建立起复杂的表达式。

与一般程序相比,函数对象允许我们把组合操作作为运行程序的一部分,之所以可以进行这种组合,是因为函数对象可以把函数当做值处理,因而带来了极大的灵活性。这段话我还没有理解。

/*
以标准函数库中的find_if()为例 讲解所谓的仿函数
*/
#include<vector>
template <class InputIterator, class Predict>
InputIterator find_if(InputIterator first,InputIterator last,Predict pred)
{
    
while(first != last && !pred(*first))
{
    ++first;
}
return  first;
}
template<class T>
class Greater
{
    public:
 bool operator()(const T & n)
 {
     if(n >50)
     return true;
     else
     return false;
 }

};

测试:

int main(int argc, char const *argv[])
{
/*
测试程序
*/
vector<int> vct_test;
for(int i =0;i<10000;i++ )
    vct_test.push_back(rand()%1000);
    vector<int>::iterator start =vct_test.begin();
     vector<int>::iterator end =vct_test.end();
      Binder1sts<less_equal<int> > binder1 = bind1sts(less_equal<int>(),60);//==>binder1st(less,60);
     size_t n1 = count_if(vct_test.begin(), vct_test.end(), binder1);
     cout << n1 <<endl;
    return 0;
}
int main(int argc, char const *argv[])
{
/*
本例测试仿函数
*/
vector<int> vct_test;
for(int i =0;i<10000;i++ )
    vct_test.push_back(rand()%1000);
    vector<int>::iterator start =vct_test.begin();
     vector<int>::iterator end =vct_test.end();
       cout<<*find_if(start,end,Greater<int>())<<endl;

    return 0;
}

通过上述例子,我们很容易发现函数对象所解决的问题之一就是把迭代器范围内数据,通过函数体的处理,传递到判断式之中,并获取我们想要的信息。这样我们便可以理解这句话 :函数对象提供了一种方法,将要调用的函数,和准备传递给这个函数的参数隐式的绑定了在一起。find_if()算法,将迭代器范围内的数据,传递给了判断式,并进行判断,最终获得我们所想要的信息。其将迭代器中要处理的数据和函数对象进行了绑定,函数对象的参数值就是迭代器所指向的值。

当然,函数指针也可以实现这样的功能,同样是上面的程序,我们也可是使用函数指针实现,也就是回调函数实现(如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。)相对于函数指针,编译器可以内联执行函数对象 函数对象内部可以保持状态。

什么是函数适配器呢,函数对象适配器,就是对

什么是函数配接器呢,所谓的函数配接器就是对原有的配接器进行改造,使其满足先前已有的算法,因而也叫做函数适配器。

函数适配器分为两类:绑定器 与 求反器;

1.   绑定器:bind1st:将给定值绑定到二元函数对象的第一个实参;  bind2nd:将给定值绑定到二元函数对象的第二个实参;他们的返回值都是一元函数对象;

下面从绑定器的实现出发,了解bind2nd的工作过程。

/*
函数配接器的实现的例子
Typename关键字 告诉编译把一个特殊的名字解释成一个类型,在下列情况下必须对一个name使用typename关键字:
    一个唯一的name(可以作为类型理解),嵌套在另一个类型中;
   依赖于一个模板参数,就是说模板参数在某种程度上包含这个name,当模板参数是编译器在指认一个类型时便会产生误解
   为了保险起见,应该在所有编译可能错把一个type当成一个变量的地方使用typename,如果你的类型在模板参数中是有限制的,那就必须使用typename
*/
template <class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation& op, const T& x) {
    typedef typename Operation::second_argument_type arg2_type;
    //也就是说在operation类中要定义这样一种类型和对象 
    // 调用binder2nd
    return binder2nd<Operation>(op, arg2_type(x));
}

// binder2nd是一个一元仿函数(本身可以搭配函数适配器一起使用)
template <class Operation> 
class binder2nd
  : public unary_function<typename Operation::first_argument_type,
                          typename Operation::result_type> 
{
protected:
    // 传进来的二元仿函数
    Operation op;
    // 传进来的第二个参数  因为仿函数内部可以typedef  而函数指针则不行
    // 因此只能适配仿函数  而不能适配函数指针
    typename Operation::second_argument_type value;
public:
    // 构造函数
    binder2nd(const Operation& x,
            const typename Operation::second_argument_type& y) 
       : op(x), value(y) {}

    // 直接调用二元仿函数 
    typename Operation::result_type
    operator()(const typename Operation::first_argument_type& x) const {
        return op(x, value); 
    }
};

绑定器的工作过程如上图所示,首先调用 bind2nd(const Operation& op, const T& x)函数, 调用binder2nd有参构造函数,

生成一个对象,该对象也就是函数对象调用重载后的operator()方法,这样实现了对原有函数对象的改造,实现了第二个参数设置成为固定值,第一个参数设定为可变值的操作,也就是绑定操作。

再如:

 

 测试:

int main(int argc, char const *argv[])
{
/*
测试程序
*/
vector<int> vct_test;
for(int i =0;i<10000;i++ )
    vct_test.push_back(rand()%1000);
    vector<int>::iterator start =vct_test.begin();
     vector<int>::iterator end =vct_test.end();
      Binder1sts<less_equal<int> > binder1 = bind1sts(less_equal<int>(),60);//==>binder1st(less,60);
     size_t n1 = count_if(vct_test.begin(), vct_test.end(), binder1);
     cout << n1 <<endl;
    return 0;
}

2. 求反器:not1:对一元函数对象的结果取反; not2:对二元函数对象的结果取反;

size_t n1 = count_if(ivec.begin(), ivec.end(), not1(bind2nd(less_equal<int>(), 5)));

求反适配器的作用也很简单,就是对原有的比较结果取反就OK了。这里不再赘述。

猜你喜欢

转载自blog.csdn.net/weixin_39804483/article/details/83036177