仿函数 ( 函数对象 ) 详解

谁说胖子不能爱 提交于 2021-02-16 23:46:57

在C语言中我们是如何实现函数的传递呢?

      没错就是函数指针!

 

 
  1.  
    template<class T>
  2.  
    void Say(T a)
  3.  
    {
  4.  
    cout << a << endl;
  5.  
    }
  6.  
     
  7.  
    void All_Fuc(int arr[], int len,void(*say)(int))
  8.  
    {
  9.  
    for (int i = 0; i < len; i++)
  10.  
    say(arr[i]);
  11.  
    }
  12.  
    int main()
  13.  
    {
  14.  
    int arr[5] = { 1, 2, 3, 4, 5 };
  15.  
    All_Fuc(arr,5,Say);
  16.  
    return 0;
  17.  
    }

 

 

在C++中我们又可以如何去实现仿函数呢?

       那就是仿函数(函数符、函数对象).

如何实现呢?

       如果我们在一个类中重载了()运算符。那么这个类的对象就可以像调用函数一样去调用 。

 

 
  1.  
    template<class T>
  2.  
    class Say
  3.  
    {
  4.  
    public:
  5.  
    void operator() (T a)
  6.  
    {
  7.  
    cout << a << endl;
  8.  
    }
  9.  
    };
  10.  
    template<class T>
  11.  
    void All_Fuc(int arr[], int len,T func)
  12.  
    {
  13.  
    for (int i = 0; i < len; i++)
  14.  
    func(arr[i]);
  15.  
    }
  16.  
    int main()
  17.  
    {
  18.  
    int arr[5] = { 1, 2, 3, 4, 5 };
  19.  
    All_Fuc(arr, 5, Say<int>());
  20.  
    return 0;
  21.  
    }

 

 

在以上代码中我们可以看出当在 Say<int>类中重载()运算符后。那么对于Say的对象 s .我们就可以这样s(int a);去调用这个函数。


当然大家可以看到以上两段代码中除了函数的定义方式不一样(一个是用的函数,一个是用的类重载() ), 在主函数中调用的时候也有一点点小区别,就是在传递的时候。如果是函数指针我们写的是函数名Say,但是如果是仿函数我们写的是Say<int>().其实相当于传递的是一个临时对象进去。

 

好,在上面两个例子中大家已经能够知道什么是仿函数,以及仿函数定义出来如何使用。那么现在问题来了,为什么要使用仿函数呢?它和普通函数区别在哪儿呢?

 

一.仿函数和普通函数的区别

 

仿函数是“smart functions”(智能型函数),一般来说我们让一个函数保存状态。使得每次调用函数会因状态不同而执行效果不同。我们一般的做法都是在函数内部定义static变量。但是这是全局变量!所以说,如果使用仿函数,其状态完全可以由自身的一个成员变量来记录。事实上,你还可以在程序中同时拥有两种状态的实体,而这个是用普通函数无法实现的。

 

 
  1.  
    template<class T>
  2.  
    class Functor
  3.  
    {
  4.  
    public:
  5.  
    enum Type{Plus,Sub};
  6.  
    Functor(Type t = Plus) :type(t){}
  7.  
    T operator()(T a, T b)
  8.  
    {
  9.  
    if (type == Plus) return a + b;
  10.  
    return a - b;
  11.  
    }
  12.  
    private:
  13.  
    Type type;
  14.  
    };
  15.  
    int main()
  16.  
    {
  17.  
    //同时定义了一个加法器和一个减法器。
  18.  
    Functor<int> plus(Functor<int>::Plus);
  19.  
    Functor<int> sub(Functor<int>::Sub);
  20.  
    int a = 5, b = 3;
  21.  
    cout << plus(a, b) << endl;
  22.  
    cout << sub(a, b) << endl;
  23.  
    return 0;
  24.  
    }

 

当然不是任何时候使用这种特效都是很好的。换句话就是说尽量不要在重载()函数中去改变对象的状态。我们通过举例来看:

 

 
  1.  
    class Average
  2.  
    {
  3.  
    public:
  4.  
    Average():count(0), sum(0){ }
  5.  
    void operator()(double num) //每次调用这个仿函数就相当于往里面添加了一个数
  6.  
    {
  7.  
    count++;
  8.  
    sum += num;
  9.  
    }
  10.  
    double GetAverage(){ return sum / count; } //最后由这个方法得到当前的平均值
  11.  
    private:
  12.  
    int count;
  13.  
    double sum;
  14.  
    };
  15.  
    template < class T ,class F>
  16.  
    void For_each(T begin, T end, F functor) //这是一个手动实现的一个简单的for_each.
  17.  
    {
  18.  
    for (T it = begin; it != end; it++)
  19.  
    functor(*it);
  20.  
    }
  21.  
    int main()
  22.  
    {
  23.  
    vector<int> arr = { 1, 2, 3, 4, 5 };
  24.  
    Average result;
  25.  
    For_each(arr.begin(), arr.end(), result);
  26.  
    cout << result.GetAverage() << endl;
  27.  
    return 0;
  28.  
    }

很显然,代码企图用Average和For_each达到对Vector中的元素求平均值的功能,但是事实上我们运行出来的结果是-1.#IND(不同的编译器可能不同,有的可能会直接运行错误)其实意思就是发生了0除。为什么为这样呢?其实原因很简单,就是我们的 For_each是使用的值传递而非引用。因为我们的仿函数调用的时候会改变对象内部的count和sum两个值的变化。那么在For_each改变的functor并没有带到main的result中。所以解决方法有两种:一.是使用引用,二.将functor返回出来。

 

这里提供第二个方法的实现(库函数的for_each也正是这样实现的):

 
  1.  
    class Average
  2.  
    {
  3.  
    //同上
  4.  
    };
  5.  
    template < class T ,class F>
  6.  
    F For_each(T begin, T end, F functor)
  7.  
    {
  8.  
    for (T it = begin; it != end; it++)
  9.  
    functor(*it);
  10.  
    return functor;
  11.  
    }
  12.  
    int main()
  13.  
    {
  14.  
    vector<int> arr = { 1, 2, 3, 4, 5 };
  15.  
    Average result = For_each(arr.begin(), arr.end(), Average()); //这里的Average()相当于一个临时对象
  16.  
    cout << result.GetAverage() << endl;
  17.  
    return 0;
  18.  
    }

我们可以看到其实有的时候我们会在不经意间使用的值传递或者是本身使用库函数的时候库函数参数使用的是值传递,这样就会导致对象信息丢失的问题。所以说我们尽可能的不要在重载()函数中去改变对象成员属性的值。

 


二.仿函数的一些基本概念

 

生成器(generator)是不用参数就可以调用的函数符(仿函数)。

一元函数(unary function) 是用一个参数就可以调用的函数符。

二元函数(binary function)是用两个参数就可以调用的函数符。

例如:供给给for_each()的函数符应该是一个一元函数,因为它每次用于一个容器元素。

当然,这些概念都有相应的改进版:

返回bool值的一元函数是谓词(predicate);

返回bool值的二元函数是二元谓词(binary predicate)。

例如:供给给sort函数的第三个参数就应该是一个二元谓词,表示一个优先级关系。

 

三.仿函数在STL中的定义

在STL中也为我们准备好了一些常用的仿函数。

 

这里是头文件 =>    #include<functional>

仿函数

效果

negate<type>()

-param

plus<type>()

param1+param2

minus<type>()

param1-param2

multiplies<type>()

param1*param2

divides<type>()

param1/param2

modulus<type>()

param1%param2

equal_to<type>()

param1==param2

not_equal_to<type>()

param!=param2

less<type>()

param1<param2

greater<type>()

param1>param2

less_equal<type>()

param1<=param2

greater_equal<type>()

param1>=param2

logical_not<type>()

!param

logical_and<type>()

param1&&param2

logical_or<type>()

param1||param2

 

四.自适应函数符

什么叫自适应呢?其实很简单,我举个例子来说,假如我想实现一个对Vector所有元素求积的功能.(假定元素类型为整数或者实数)

 

 
  1.  
    template<class T>
  2.  
    class Mul
  3.  
    {
  4.  
    public:
  5.  
    T operator()(T a,T b)
  6.  
    {
  7.  
    return a*b;
  8.  
    }
  9.  
    };
  10.  
    template < class T ,class F>
  11.  
    ____ For_each(T begin, T end, F functor)
  12.  
    {
  13.  
    ____ ans = 1;
  14.  
    for (T it = begin; it != end; it++)
  15.  
    ans = functor(*it,ans);
  16.  
    return ans;
  17.  
    }
  18.  
    int main()
  19.  
    {
  20.  
    vector<int> arr = { 1, 2, 3, 4, 5 };
  21.  
    int ans = For_each(arr.begin(), arr.end(), Mul<int>());
  22.  
    cout << ans << endl;
  23.  
    return 0;
  24.  
    }

写出来大概为这样,但是很显然在For_each中有两个地方没法写下去。就是如何知道求出来的积是什么类型呢?换句话意思说我如何在For_each中知道我的第三个参数F functor 这个仿函数的执行的返回值呢(也就是class Mul 的模板类型参数T)? 所以我们在这里我们需要对Mul这个类中引入三个类型。分别是first_argument_type,second_argument_type,result_type:

 

 

 
  1.  
    template<class T>
  2.  
    class Mul
  3.  
    {
  4.  
    public:
  5.  
    T operator()(T a, T b)
  6.  
    {
  7.  
    return a*b;
  8.  
    }
  9.  
    typedef T first_argument_type, second_argument_type, result_type;
  10.  
    };

这样就相当于对这个二元函数符记录了它的两个参数和返回类型。所以我们的For_each就可以这样相应的写:

 

 
  1.  
    template < class T, class F>
  2.  
    typename F::result_type For_each(T begin, T end, F functor) //因为在这里编译器不知道result_type是F的成员变量还是类型,所以要用关键字typename限定。
  3.  
    {
  4.  
    typename F::result_type ans = 1;
  5.  
    for (T it = begin; it != end; it++)
  6.  
    ans = functor(*it, ans);
  7.  
    return ans;
  8.  
    }


 

 

 

所以我们把有这样的记录了参数、返回值类型的仿函数称之为自适应的仿函数。在库函数中,为我们准备好了两个类型:std::unary_function 和 std::binary_function

我们先看看源码吧:

 

 
  1.  
    template<class _Arg,class _Result>
  2.  
    struct unary_function
  3.  
    { // base class for unary functions
  4.  
    typedef _Arg argument_type;
  5.  
    typedef _Result result_type;
  6.  
    };
  7.  
     
  8.  
    // TEMPLATE STRUCT binary_function
  9.  
    template<class _Arg1,class _Arg2,class _Result>
  10.  
    struct binary_function
  11.  
    { // base class for binary functions
  12.  
    typedef _Arg1 first_argument_type;
  13.  
    typedef _Arg2 second_argument_type;
  14.  
    typedef _Result result_type;
  15.  
    };

 

 

我们可以看出来其实这两个类型(分别对应一元函数和二元函数)只是分别定义了自己模板参数类型和返回值类型。这样的话假如我要写一个一元函数,我只需要去继承这个unary_function 即可。那么我们上面的Mul类还可以这么写:

 

 
  1.  
    template<class T>
  2.  
    class Mul : public unary_function<T,T>
  3.  
    {
  4.  
    public:
  5.  
    T operator()(T a, T b)
  6.  
    {
  7.  
    return a*b;
  8.  
    }
  9.  
    };

 

 

五.函数适配器(函数配接器)

对于函数适配器,书上比较正式的解释是:标准库提供了一组函数适配器(function adapter),用于特化和扩展一元和二元函数对象。

而我的理解:函数配适器就是改变函数符的输入或者输出使得原本的函数符功能特化。所以我觉得叫做函数修饰器更比较贴切吧(个人感觉)。

在库中提供的函数适配器分为两类:

(1)绑定器(binder),是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。

(2)求反器(negator),是一种函数适配器,它将谓词函数对象的真值求反。

先举例绑定器:例如上文中我写的Mul仿函数,这个仿函数是一个二元函数,他需要给出两个参数。但是如果其中一个参数已经确定了。那么其实这个二元函数就可以退化为一个一元函数。所以这时候就需要绑定器来实现"类型转换"。假如:Mul中第二个参数已经固定为3,那么退化后的一元函数就为:bind2nd(Mul<int>(),3); 其实返回的就是一个一元函数operator()(T a){ return Functor(a,3); } 可以看得到其实实质还是调用的Mul的重载()函数。所以绑定器只是在外面包了一层壳。

给出一个例子吧,假如我要实现这样一个代码:Vector存放了若干个整数,现在输入一个整数x,问Vector的所有整数在数轴上与x的距离和为多少?

( 假如Vector存放的是1,5,8,x = 3, 那么和为 abs(1-3)+abs(5-3)+abs(8-3) ) 我们可以看到求距离的这个仿函数应该是一个二元函数。需要提供两个整数。但是我们知道其中一个整数是固定为x的所以我们的代码可以这么写:

 

 
  1.  
    class Dis :public binary_function<int,int,int>
  2.  
    {
  3.  
    public:
  4.  
    int operator()(int a, int b) const //要用绑定器的函数符必须为const重载()函数.
  5.  
    {
  6.  
    if (a > b) return a - b;
  7.  
    return b - a;
  8.  
    }
  9.  
    };
  10.  
    template < class T, class F>
  11.  
    typename F::result_type For_each(T begin, T end, F functor)
  12.  
    {
  13.  
    typename F::result_type ans = 0;
  14.  
    for (T it = begin; it != end; it++)
  15.  
    ans += functor(*it);
  16.  
    return ans;
  17.  
    }
  18.  
    int main()
  19.  
    {
  20.  
    vector<int> arr = { 1, 3, 4, 5, 6, 9, 10 };
  21.  
    int x = 3;
  22.  
    //cin >> x;
  23.  
    int ans = For_each(arr.begin(), arr.end(), bind2nd(Dis(), x));
  24.  
    cout << ans << endl;
  25.  
    return 0;
  26.  
    }

常用绑定器有两个:bind1st, bind2nd,分别是把值绑定到二元函数的第一个参数或者是第二个参数。至于求反器我就不过多说明了。其实适配器并不只限定于这两种。完全可以根据我们自己的要求来写各种适配器。例如我这里写了一个适配器。Unity<Func1 H, Func2 F, Func3 G> 中 Func1是一个二元函数,Func2, Func3都是一元函数。而这个适配器返回的是一个一元函数 Unity(x).<p>其作用就是 这个复合函数(这里的函数是数学的中的复合函数) : H(F(x), G(x));

 
  1.  
    class Unity : unary_function < typename F::argument_type, typename H::result_type >
  2.  
    {
  3.  
    public:
  4.  
    Unity(const H &h, const F &f, const G &g) :m_h(h), m_f(f), m_g(g) { }
  5.  
    typename H::result_type operator() (typename F::argument_type x) const
  6.  
    {
  7.  
    return m_h(m_f(x), m_g(x));
  8.  
    }
  9.  
    private:
  10.  
    H m_h;
  11.  
    F m_f;
  12.  
    G m_g;
  13.  
    };
  14.  
    template<typename T>
  15.  
    class One : public unary_function < T, T >
  16.  
    {
  17.  
    public:
  18.  
    T operator()(T a)const
  19.  
    {
  20.  
    return a;
  21.  
    }
  22.  
    };
  23.  
    template<typename T>
  24.  
    class Two : public unary_function < T, T >
  25.  
    {
  26.  
    public:
  27.  
    T operator()(T a, T b)const
  28.  
    {
  29.  
    return a + b;
  30.  
    }
  31.  
    };
  32.  
    int main()
  33.  
    {
  34.  
    Two<int> b;
  35.  
    One<int>a;
  36.  
    Unity<Two<int>, One<int>, One<int>> m(b, a, a);
  37.  
    int num = 3;
  38.  
    cout << m(num);
  39.  
    return 0;
  40.  
    }


 

 
 

其实除了上面两种适配器还有一种可应当可以称之为适配器就是成员函数适配器。

有时候,你可能想让算法调用容器元素的成员函数,而不是外部函数。因为外部无法改变对象内的状态,且内部函数通常具有更高的效率。例如swap(string, string)总是没有string.swap(string)快速。又比如sort无法对list进行排序,这时候只能使用list.sort()来给链表排序。这时候就需要使用一定的方法将对象内部的函数“变成”函数对象,这样的函数对象叫做成员函数适配器。

其实成员函数适配器是一个一元函数,参数为调用这个成员函数的对象(或者对象指针)。当然如果是对象的话适配器就是mem_fun_ref如果是指针的话就是mem_fun。

我们来举例说明一下,例如:定义一个类Student,有个成员变量为学生名字,有一个成员函数为Say即是输出这个学生的名字。现在我们有一个vector<Student>.我们想对于For_each第三个参数传入Student这个类的成员函数。这样调用For_each就输出了每个学生的名字。代码如下:

 

 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!