仿函数functors
仿函数(函数对象)
1、背景
有些功能实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。
- 写一个公共的函数,可以,这是一个解决方法,再将函数指针当做算法的一个参数。
- 函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。
- 当函数参数有所变化,则无法兼容旧的代码
- 函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以。
- 可以用仿函数了,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的非参数的元素传入类中:
- 免去了对一些公共变量的全局化的维护了;
- 使那些代码独立出来,以便下次复用;
- 而且这些仿函数,还可以用关联,聚合,依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理
举例
假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够统计出这个数组中大于 10 的数字的数量:
//https://blog.csdn.net/K346K346/article/details/82818801
#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)作为全局变量,后续的新增参数都只能作为全局变量,维护起来不方便。比如全局变量容易同名,造成命名空间污染。
- 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)
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)
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、功能划分
3.2.1、算术运算
//算术类仿函数 + - * / %
//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;
}
3.2.2、关系运算类
//关系运算符仿函数
// 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;
}
3.2.3、逻辑运算类
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、仿函数适配器
适配器单独一篇博客再讲,这里简单概述。
- 1、适配器也是一种常用的设计模式:将一个class的接口转换为另一个class的接口,使得原本因接口不兼容而不能合作的classes可以一起运作。
- 例如我们笔记本的电源,一般都会有一个适配器把220v的电压降到适合笔记本工作的电压范围,这样笔记本就可以工作在我们常用的电压环境了,这就扩大了笔记本的使用场景,在软件开发过程中也是一样的道理。
- 2、适配器内部有一个原来要适配的成员变量,通过改变接口来实现,那么仿函数的适配器也不例外。常用的是
bind1st, bind2nd, not1,compose1,compose2
等等,这些适配器都是仿函数,同时以要适配的仿函数作为member object
。 - 3、STL仿函数都需要继承
binary_function<T, T, bool>()
或者unary_function<T, bool>()
,只有继承自这两种父类,你声明的仿函数才可以融入STL- 他们的内部都有typedef,这几个typedef是为了提取算法传进变量,以供仿函数适配器使用,如果你没有继承这两种类,那么在算法调用仿函数时,仿函数适配器提取不出这几个变量,那么就会报错.
4.1 应用举例:将仿函数某个参数绑定为固定值的适配器
bind2nd()
.它的作用是绑定仿函数的第二参数
// binder2nd example
#include <iostream>
#include <functional>
#include <algorithm>
using namespace std;
int main () {
int numbers[] = {10,-20,-30,40,-50};
int cx;
int cx1;
binder2nd< less<int> > IsNegative (less<int>(),0);//将less<int>重新包装产生新的对象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<T, T, bool>,因为他自己声明了那三种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 <iostream>
#include <algorithm>
#include <vector>
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、<<STL源码剖析>>
6、https://blog.csdn.net/qq_46239972/article/details/106526106