c++之仿函数[1]

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)的类型不相符。如果一定要完成这个任务,按照以往的经验,我们可以考虑如下可能途径:

  • 阈值作为函数的局部变量。局部变量不能在函数调用中传递,故不可行;
  • 函数传参。这种方法我们已经讨论过了,多个参数不适用于已定义好的 RecallFunc() 函数。
  • 全局变量。我们可以将阈值设置成一个全局变量。这种方法虽然可行,但不优雅,且容易引入 Bug,比如全局变量容易同名,造成命名空间污染。

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

2.定义【函数对象,函数功能的类

仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类

仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

仿函数是一种重载了函数调用运算符()的类或结构体,它可以像函数一样被调用。它可以接受参数,也可以返回值。常用于STL算法中的函数对象参数,例如sort()、find_if()等。仿函数可以是普通函数指针、函数对象、Lambda表达式等形式。

如果编程者要将某种“操作”当做算法的参数,一般有两种方法:

  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");
}

这个例子应该可以让您体会到仿函数的一些作用:它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。于是仿函数提供了第四种解决方案:成员变量。成员函数可以很自然地访问成员变量,从而可以解决第一节“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;
}

4.STL中基础仿函数【算数+关系+逻辑+其他

仿函数的主要功能是为了搭配STL算法使用,单独使用仿函数的情况比较少
仿函数(functors)在C++标准中采用的名称是函数对象(function objects)。仿函数主要用于STL中的算法中,虽然函数指针虽然也可以作为算法的参数,但是函数指针不能满足STL对抽象性的要求,也不能满足软件积木的要求–函数指针无法和STL其他组件搭配,产生更灵活变化。仿函数本质就是类重载了一个operator(),创建一个行为类似函数的对象。

对于重载了()操作符的类,可以实现类似函数调用的过程,所以叫做仿函数,实际上仿函数对象仅仅占用1字节,因为内部没有数据成员,仅仅是一个重载的方法而已。实际上可以通过传递函数指针实现类似的功能,但是为了和STL内部配合使用,他提供了仿函数的特性。常用的STL中算数运算符,关系运算符,逻辑运算符都是定义好的仿函数来实现的。

4.1 仿函数定义自己型别

算法内部可能需要使用仿函数返回值或者输出值的类型参数,因此定义两个类。

//一元函数的参数和返回值类型,通常被继承
template <class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;
    typedef Result result_type;
};

//二元函数的参数和返回值类型,通常被继承
template <class Arg1, class Arg2, class Result>
struct binary_function {
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};  

上述两个类分别定义了一元和二元函数参数和返回值型别,对应的仿函数类仅仅需要继承此类即可。

4.2 算术类仿函数[加减乘除]

标准库给我们定义了一些通用的仿函数,可以直接调用,生成对象,面向用户。

//////算术类仿函数 + - * / %////////////////////////////////
//C++11以后,自定义functors,已经不需要继承自像binary_function 这样的基类了。
//plus仿函数,生成一个对象,里面仅仅有一个函数重载的方法。 template <class T> struct plus : public binary_function<T, T, T> { T operator()(const T& x, const T& y) const { return x + y; } }; //minus仿函数 template <class T> struct minus : public binary_function<T, T, T> { T operator()(const T& x, const T& y) const { return x - y; } }; template <class T> struct multiplies : public binary_function<T, T, T> { T operator()(const T& x, const T& y) const { return x * y; } }; template <class T> struct divides : public binary_function<T, T, T> { T operator()(const T& x, const T& y) const { return x / y; } }; template <class T> struct modulus : public binary_function<T, T, T> { T operator()(const T& x, const T& y) const { return x % y; } }; //取负值 template <class T> struct negate : public unary_function<T, T> { T operator()(const T& x) const { return -x; } };
#include <iostream>     // std::cout
#include <functional>   // std::plus
#include <algorithm>    // std::transform
using namespace std;
int main(void)
{
    cout << minus<int>()(10,5) << endl;//5
    cout << multiplies<int>()(10,5) << endl;//50
    cout << divides<int>()(10,5) << endl;//2
    cout << modulus<int>()(10,5) << endl;//0
    cout << negate<int>()(10) << endl;//-10
    return 0;
}

4.3 关系运算符仿函数[等于|不等于,大于|小于,大于等于|小于等于]

//关系运算符仿函数
// x==y 仿函数
template <class T>
struct equal_to : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x == y; }
};

// x!=y 仿函数
template <class T>
struct not_equal_to : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x != y; }
};
// x>y 仿函数
template <class T>
struct greater : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x > y; }
};
// x<y 仿函数
template <class T>
struct less : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x < y; }
};

// x>=y 仿函数
template <class T>
struct greater_equal : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x >= y; }
};
// x<=y 仿函数
template <class T>
struct less_equal : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x <= y; }
};
// sort algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector
#include <functional>   // std::

bool myfunction (int i,int j) { return (i < j); }

int main () {
  int myints[] = {32,71,12,45,26,80,53,33};
  std::vector<int> myvector (myints, myints+8);               // 32 71 12 45 26 80 53 33

  // using default comparison (operator <):
  std::sort (myvector.begin(), myvector.begin()+4);           //(12 32 45 71)26 80 53 33

  // using function as comp
  std::sort (myvector.begin()+4, myvector.end(), myfunction); // 12 32 45 71(26 33 53 80)

  // using object as comp
  std::sort (myvector.begin(), myvector.end(), std::less<int>());     //(12 26 32 33 45 53 71 80)

  // print out content:
  std::cout << "myvector contains:";
  for (std::vector<int>::iterator it=myvector.begin(); it!=myvector.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

4.4 逻辑运算符仿函数【||,&】

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

template <class T>
struct logical_or : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x || y; }
};

template <class T>
struct logical_not : public unary_function<T, bool> {
    bool operator()(const T& x) const { return !x; }
};

4.5 证同、选择、投射仿函数

///////////////////////////////////////////////////////////////////////////////////////////
//证同仿函数,主要用于RB或者hashmap里面 key = value情况
template <class T>
struct identity : public unary_function<T, T> {
  const T& operator()(const T& x) const { return x; }
};

//选择仿函数,主要用与RB和hashmap里面key 不为value情况,从pair种取出key
template <class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
  const typename Pair::first_type& operator()(const Pair& x) const
  {
    return x.first;
  }
};

//选择仿函数,主要用与RB和hashmap里面key 不为value情况,从pair种取出value
template <class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type> {
  const typename Pair::second_type& operator()(const Pair& x) const
  {
    return x.second;
  }
};

//投射函数,输入x和y返回x
template <class Arg1, class Arg2>
struct project1st : public binary_function<Arg1, Arg2, Arg1> {
  Arg1 operator()(const Arg1& x, const Arg2&) const { return x; }
};
//投射函数,输入x和y返回y
template <class Arg1, class Arg2>
struct project2nd : public binary_function<Arg1, Arg2, Arg2> {
  Arg2 operator()(const Arg1&, const Arg2& y) const { return y; }
};
/////////////////////////////////////////////////////////////////////

5.STL中仿函数适配器【】

仿函数适配器是通过将上述仿函数重新配置成含有新功能的模板函数。

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

 

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

 

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

 

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

 

  • 对仿函数返回值进行否定适配器:传入仿函数对象即可,和以前一样使用,仅仅包装了一下子而已。
  • 将仿函数某个参数绑定为固定值的适配器
  • 将两个仿函数合并成一个仿函数的适配器
  • 将函数指针合并成仿函数的适配器
  • 将成员函数指针提取出来包装成仿函数适配器

 

参考文章:
原文链接:https://blog.csdn.net/u010710458/article/details/79734558

原文链接:https://blog.csdn.net/YGG12022/article/details/124043116

 

posted on 2023-03-15 11:22  斗战胜佛美猴王  阅读(274)  评论(0编辑  收藏  举报