• 什么是仿函数?


    转自:https://blog.csdn.net/K346K346/article/details/82818801

    1.为什么要有仿函数

    我们先从一个非常简单的问题入手,来了解为什么要有仿函数。假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够统计出这个数组中大于 10 的数字的数量,你的代码很可能是这样的:

    #include <iostream>
    using namespace std;
    
    int RecallFunc(int *start, int *end, bool (*pf)(int))
    {
        int count=0;
        for(int *i=start;i!=end+1;i++)
        {
            count = pf(*i) ? count+1 : count;
        }
        return count;
    }
    
    bool IsGreaterThanTen(int num)
    {
        return num>10 ? true : false;
    }
    
    int main()
    {
        int a[5] = {10,100,11,5,19};
        int result = RecallFunc(a,a+4,IsGreaterThanTen);
        cout<<result<<endl;
        return 0;
    }

    RecallFunc() 函数的第三个参数是一个函数指针,用于外部调用,而 IsGreaterThanTen() 函数通常也是外部已经定义好的,它只接受一个参数的函数。如果此时希望将判定的阈值也作为一个变量传入,变为如下函数就不可行了:

    bool IsGreaterThanThreshold(int num, int threshold) 
    {
        return num>threshold ? true : false;
    }

    虽然这个函数看起来比前面一个版本更具有一般性,但是它不能满足已经定义好的函数指针参数的要求,因为函数指针参数的类型是bool (*)(int),与函数bool IsGreaterThanThreshold(int num, int threshold)的类型不相符。如果一定要完成这个任务,按照以往的经验,我们可以考虑如下可能途径:
    (1)阈值作为函数的局部变量。局部变量不能在函数调用中传递,故不可行;
    (2)函数传参。这种方法我们已经讨论过了,多个参数不适用于已定义好的 RecallFunc() 函数。
    (3)全局变量。我们可以将阈值设置成一个全局变量。这种方法虽然可行,但是不优雅,且非常容易引入 Bug,比如全局变量容易同名,造成命名空间污染。

    那么有什么好的处理方法呢?仿函数应运而生。

    2.仿函数的定义
    仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

    如果编程者要将某种“操作”当做算法的参数,一般有两种方法:
    (1)一个办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数。上面的实例就是该做法;
    (2)将该“操作”设计为一个仿函数(就语言层面而言是个 class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。

    很明显第二种方法会更优秀,因为第一种方法扩展性较差,当函数参数有所变化,则无法兼容旧的代码,具体在第一小节已经阐述。正如上面的例子,在我们写代码时有时会发现有些功能代码,会不断地被使用。为了复用这些代码,实现为一个公共的函数是一个解决方法。不过函数用到的一些变量,可能是公共的全局变量。引入全局变量,容易出现同名冲突,不方便维护。

    这时就可以使用仿函数了,写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。而且相对于函数更优秀的性质,仿函数还可以进行依赖、组合与继承等,这样有利于资源的管理。如果再配合模板技术和 Policy 编程思想,则更加威力无穷,大家可以慢慢体会。Policy 表述了泛型函数和泛型类的一些可配置行为(通常都具有被经常使用的缺省值)。

    STL 中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如 STL 中的容器 set 就使用了仿函数 less ,而 less 继承的 binary_function,就可以看作是对于一类函数的总体声明,这是函数做不到的。

    //less的定义
    template<typename _Tp> struct less : public binary_function<_Tp, _Tp, bool>
    {
          bool operator()(const _Tp& __x, const _Tp& __y) const
          { return __x < __y; }
    };
     
    //set的申明
    template<typename _Key, typename _Compare = std::less<_Key>,typename _Alloc = std::allocator<_Key>> class set;

    仿函数中的变量可以是 static 的,同时仿函数还给出了 static 的替代方案,仿函数内的静态变量可以改成类的私有成员,这样可以明确地在析构函数中清除所用的内容,如果用到了指针,那么这个是不错的选择。有人说这样的类已经不是仿函数了,但其实,封装后从外界观察,可以明显地发现,它依然有函数的性质。

    3.仿函数实例
    我们先来看一个仿函数的例子。

    class StringAppend
    {
    public:
        explicit StringAppend(const string& str) : ss(str){}
        void operator() (const string& str) const
        {
             cout<<str<<' '<<ss<<endl;
        }
    private:
        const string ss;
    };
    
    int main()
    {
        StringAppend myFunctor2("and world!");
        myFunctor2("Hello");
    }

    编译运行输出:

    Hello and world!

    这个例子应该可以让您体会到仿函数的一些作用:它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。于是仿函数提供了第四种解决方案:成员变量。成员函数可以很自然地访问成员变量,从而可以解决第一节“1.为什么要有仿函数”中提到的问题:计数出数组中大于指定阈值的数字数量。

    #include <iostream>
    using namespace std;
    
    class IsGreaterThanThresholdFunctor
    {
    public:
        explicit IsGreaterThanThresholdFunctor(int t):threshold(t){}
        bool operator() (int num) const
        {
            return num > threshold ? true : false;
        }
    private:
        const int threshold;
    };
    
    int RecallFunc(int *start, int *end, IsGreaterThanThresholdFunctor myFunctor)
    {
        int count = 0;
        for (int *i = start; i != end + 1; i++)
        {
            count = myFunctor(*i) ? count + 1 : count;
        }
        return count;
    }
    
    int main()
    {
        int a[5] = {10,100,11,5,19};
        int result = RecallFunc(a, a + 4, IsGreaterThanThresholdFunctor(10));
        cout << result << endl;
    }

    编译运行输出:

     3
  • 相关阅读:
    Mongodb---记一次事故故障
    扎克伯格的中文夜:想要成功就不能放弃
    TTS-零基础入门之停止列表中单条语音播报
    卡尔曼滤波(Kalman Filter) 的进一步讨论
    2014.8.12-AKKA和Actor model 分布式开发环境学习小结
    c# 删除程序占用的文件,强力删除文件,彻底删除文件,解除文件占用
    Spring源代码解析和配置文件载入
    java调用c++ dll出现中文乱码
    Android任务栈TaskStack
    应用中清理缓存应用实现
  • 原文地址:https://www.cnblogs.com/WPF-342201/p/12976624.html
Copyright © 2020-2023  润新知