C++ 标准模板库(STL)——仿函数(functors)

仿函数functors

  • 仿函数(函数对象)
    • 1、背景
    • 2、定义
    • 3、类型
      • 3.1、操作数个数划分
        • 3.1.1、一元仿函数基类(unary_function)
        • 3.1.2、二元仿函数基类(binary_function)
      • 3.2、功能划分
        • 3.2.1、算术运算
        • 3.2.2、关系运算类
        • 3.2.3、逻辑运算类
    • 4、仿函数适配器
      • 4.1 应用举例:将仿函数某个参数绑定为固定值的适配器
    • 5、函数指针、仿函数、Lambda表达式在同一场景下的使用示例
  • 参考

仿函数(函数对象)

1、背景

C++ 标准模板库(STL)——仿函数(functors)_第1张图片

有些功能实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。

  • 写一个公共的函数,可以,这是一个解决方法,再将函数指针当做算法的一个参数。
    • 函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。
    • 当函数参数有所变化,则无法兼容旧的代码
    • 函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以。
  • 可以用仿函数了,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的非参数的元素传入类中:
    • 免去了对一些公共变量的全局化的维护了
    • 使那些代码独立出来,以便下次复用;
    • 而且这些仿函数,还可以用关联,聚合,依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理

举例
假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够统计出这个数组中大于 10 的数字的数量:

//https://blog.csdn.net/K346K346/article/details/82818801
#include 
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)作为全局变量,后续的新增参数都只能作为全局变量,维护起来不方便。比如全局变量容易同名,造成命名空间污染。
  • 3)函数传参。这种方法我们已经讨论过了,多个参数不适用于已定义好的 RecallFunc() 函数。
  • 4)成员变量,将需要比较的值作为成员变量保存起来,可以作为类构造函数的参数进行传参,可拓展性强,且易维护。这就出现了仿函数。

2、定义

“函数对象”:一种具有函数性质的对象(是类 而不是普通的函数)。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写,重载)function call运算子(operator())。拥有这样的运算子后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator()。
特点:

  • 仿函数的应用场景主要在:作为算法组件中的相关函数接口的参数。
  • 仿函数对象仅仅占用1字节,因为内部没有数据成员

3、类型

STL仿函数的分类:

  • 1、以操作数的个数划分(通常被继承))
    • 一元函数对象基类(unary_function)
    • 二元函数对象基类(binary_function)
  • 2、以功能划分,可分为
    • 算术运算(Arithmetic)
    • 关系运算(Rational)
    • 逻辑运算(Logical)

3.1、操作数个数划分

3.1.1、一元仿函数基类(unary_function)

C++ 标准模板库(STL)——仿函数(functors)_第2张图片

template <class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;	// 参数类型别名
    typedef Result result_type;	// 返回值类型别名
};

举例:

//2.使用1元仿函数
class CopyClass1Param :public unary_function<int, bool>
{
public:
    bool operator()(const int value) const
    {
        return value>2;
    }
};

3.1.2、二元仿函数基类(binary_function)

C++ 标准模板库(STL)——仿函数(functors)_第3张图片

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元仿函数
class CopyClassUpNum : public binary_function<int, int, bool>
{
public:
    bool operator()(const int srcValue, const int base) const
    {
        return srcValue > base;
    }
};


3.2、功能划分

C++ 标准模板库(STL)——仿函数(functors)_第4张图片

3.2.1、算术运算

C++ 标准模板库(STL)——仿函数(functors)_第5张图片

//////算术类仿函数 + - * / %////////////////////////////////
//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      // std::cout
#include    // std::plus
#include     // 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;
}

3.2.2、关系运算类

C++ 标准模板库(STL)——仿函数(functors)_第6张图片

//关系运算符仿函数////////////////////////////////////////////
// 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
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      // std::cout
#include     // std::sort
#include        // std::vector
#include    // 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;
}

3.2.3、逻辑运算类

C++ 标准模板库(STL)——仿函数(functors)_第7张图片

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、仿函数适配器

适配器单独一篇博客再讲,这里简单概述。
C++ 标准模板库(STL)——仿函数(functors)_第8张图片

  • 1、适配器也是一种常用的设计模式:将一个class的接口转换为另一个class的接口,使得原本因接口不兼容而不能合作的classes可以一起运作。
    • 例如我们笔记本的电源,一般都会有一个适配器把220v的电压降到适合笔记本工作的电压范围,这样笔记本就可以工作在我们常用的电压环境了,这就扩大了笔记本的使用场景,在软件开发过程中也是一样的道理。
  • 2、适配器内部有一个原来要适配的成员变量,通过改变接口来实现,那么仿函数的适配器也不例外。常用的是bind1st, bind2nd, not1,compose1,compose2等等,这些适配器都是仿函数,同时以要适配的仿函数作为member object
  • 3、STL仿函数都需要继承binary_function()或者unary_function(),只有继承自这两种父类,你声明的仿函数才可以融入STL
    • 他们的内部都有typedef,这几个typedef是为了提取算法传进变量,以供仿函数适配器使用,如果你没有继承这两种类,那么在算法调用仿函数时,仿函数适配器提取不出这几个变量,那么就会报错.

4.1 应用举例:将仿函数某个参数绑定为固定值的适配器

bind2nd().它的作用是绑定仿函数的第二参数

// binder2nd example
#include 
#include 
#include 
using namespace std;

int main () {
  int numbers[] = {10,-20,-30,40,-50};
  int cx;
  int cx1;
  binder2nd< less<int> > IsNegative (less<int>(),0);//将less重新包装产生新的对象binder2nd
  cx = count_if (numbers,numbers+5 , IsNegative);//二者用法一样
  cx1 = count_if (numbers,numbers+5,bind2nd(less<int>() , 0));
  cout << "There are " << cx <<"  "<< cx1 << " negative elements.\n";//输出3,小于0的value有3个
  return 0;
}

bind2nd()函数的作用: 它将0 这个变量绑定在less()函数的第二参数上,less函数返回第一参数是否小于第二参数,那么绑定后的less()函数就应该返回 传入的参数是否小于0.

具体源码分析过程如下:

1、less()的源码:

template <class T> struct less {
  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;
};

他也是一个仿函数,但是却发现它没有继承自binary_function,因为他自己声明了那三种typedef,他需要两个两个参数,x和y,返回 x是否小于y.

2、bind2nd()的源代码:

template<typename _Operation, typename _Tp>
    inline binder2nd<_Operation>//注意这里的返回类型是binder2nd
    bind2nd(const _Operation& __fn, const _Tp& __x)
    {
      typedef typename _Operation::second_argument_type _Arg2_type;//这有一个typedef 它提取出operation的第二参数,同时可以检测第二参数类型
      return binder2nd<_Operation>(__fn, _Arg2_type(__x));
    }

3、binder2nd()的源码:

template<typename _Operation>
    class binder2nd
    : public unary_function<typename _Operation::first_argument_type,
                typename _Operation::result_type>
    {
    protected:
      _Operation op;//取出传进的仿函数
      typename _Operation::second_argument_type value;//取出第二参数,同时可检测第二参数类型

    public:
      binder2nd(const _Operation& __x,
        const typename _Operation::second_argument_type& __y)
      : op(__x), value(__y) { }//构造函数(在bind2nd()函数中调用),初始化自身成员变量 op,value

      typename _Operation::result_type
      operator()(const typename _Operation::first_argument_type& __x) const
      { return op(__x, value); }//重载operator() 在函数count_if()中被调用

      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 109.  Missing binders for non-const sequence elements
      typename _Operation::result_type
      operator()(typename _Operation::first_argument_type& __x) const
      { return op(__x, value); }//非const
    } _GLIBCXX_DEPRECATED;

4、count_if()的源码:

template <class Inputerator, class Outputerator, class Predicate>
typename iterator_traits<Inputerator>::difference_type;
count_if(Inputerator first, Inputerator last, Predicate pred)
{
    typename iterator_traits<Inputerator>::difference_type;
    for( ; first != last; ++first)
        if(pred(*first)) //这个地方会调用函数pred(*first), 重上面我们可以看到 pred绑定的函数是binder2nd()中的 operator()函数,那么此时的pred就应该是less函数
            ++n;
    return n;
}

5、函数指针、仿函数、Lambda表达式在同一场景下的使用示例

#includ 
#include 
#include 
using namespace std;
bool Cmp(int a,int b)
{
	return a>b;
}
class Cmp2
{
public:
	bool operator()(int a,int b)
	{
		return a>b;
	}
};
class printElem
{
public:
	void operator () (int elem)
	{
		cout<<elem<<" ";
	}
};
int main(void)
{
	vector<int> ver{0,9,4,3,8};
	//使用函数指针
	sort(ver.begin(),ver.end(),*Cmp);
	for_each(ver.begin(),ver.end(),printElem ());
	cout<<endl;//输出9 8 4 3 0
 
	vector<int> ver2{1,3,8,9,4};
	//使用仿函数
	sort(ver2.begin(),ver2.end(),Cmp2());
	for_each(ver2.begin(),ver2.end(),printElem ());
	cout<<endl;//输出9 8 4 3 1
	
	vector<int> ver3{5,8,3,7,9};
	//使用Lambda表达式
	sort(ver3.begin(),ver3.end(),[](int a,int b){return a>b;});
	for_each(ver3.begin(),ver3.end(),printElem ());
	cout<<endl;//输出9 8 7 5 3
 	return 0;
}

参考

1、https://blog.csdn.net/u010710458/article/details/79734558
2、https://www.cnblogs.com/jiu0821/p/6554169.html
3、https://blog.csdn.net/u013427969/article/details/78587276
4、https://www.cnblogs.com/LearningTheLoad/p/7594646.html
5、<>
6、https://blog.csdn.net/qq_46239972/article/details/106526106

你可能感兴趣的:(STL,c++,stl)