第七章 仿函数(functors)另名函数对象(function objects)

1.仿函数概念

  1. 仿函数(functors)是早期的命名,C++标准规格定案后所采用的新名称是函数对象(function objects)。就实现意义而言,"函数对象”比较贴切:一种具有函数特质的对象, 就其行为而言,“仿函数”一词比较突出。因此,本书绝大部分时候采用“仿函数”一词。这种东西在调用者可以像函数一样地被调用(调用),在被调用者则以对象所定义的functioncall operator扮演函数的实质角色。
  2. 仿函数的作用主要在哪里?从第6章可以看出,STL所提供的各种算法,往往有两个版本。其中一个版本表现出最常用(或最直观)的某种运算,第二个版本 则表现出最泛化的演算流程,允许用户”以template参数来指定所要采行的策略”。
  3. 例如accumulate()其一般行为(第一版本)是将指定范围内的所有元素相加, 第二版本则允许你指定某种“操作“,取代第一版本中的“相加“行为。
  4. 再举sort() 为例,其第一版本是以operator<为排序时的元素位置调整依据,第二版本则允 许用户指定任何"操作“,务求排序后的两两相邻元素都能令该操作结果为true。但要将某种“操作“当做算法的参数,唯一办法就是先将该“操作“(可能拥有数条以上的指令)设计为一个函数,再将函数指针当做算法的一个参数;或是将该“操作“设计为一个所谓的仿函数(就语言层面而言是个class), 再以该 仿函数产生一个对象,并以此对象作为算法的一个参数。

根据以上陈述,既然函数指针可以达到“将整组操作当做算法的参数”,那又何必有所谓的仿函数呢?

  • 原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求:函数指针无法和STL其它组件(如配接器adapter, 第8章)搭配,产生更灵活的变化。

    • 就实现观点而言,仿函数其实上就是一个“行为类似函数”的对象。为了能够 “行为类似函数”,其类别定义中必须自定义(或说改写、重载)function call运 算子(operator(),语法和语意请参考1.9.6节)。
      • 拥有这样的运算子后,我们就 可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator(), 像这样:
#include<functional>
#include<iostream>
using namespace std;
int main()
{
greater<int> ig;
cout<<boolalpha<<ig(4,6);//(A)false
cout<<greater<int>()(6,4);//(B) true
}
  1. 第一种greaterig 的意思是产生一个名为ig的对象,ig(4,6)则是调用其operator(),并给予两个参数4,6。
  2. 第二种 用法中的greater()意思是产生一个临时(无名的)对象,之后的(4,6)才 是指定两个参数4,6。临时对象的产生方式与生命周期,请参见1.9.2节。
  3. 程序中的boolalpha是一种所谓的iostream manipulators (操控器),用来控制输出入设备的状态.boolalpha意思是从此以后对bool值的输出,都改为以字符串,'true" 或,•false"表现。
  4. 上述第二种语法在一般情况下不常见,但是对仿函数而言,却是主流用法。

下图所示的是STL仿函数与STL算法之间的关系。
在这里插入图片描述
STL仿函数的分类, 若以操作数(operand)的个数划分, 可分为一元和二元 仿函数, 若以功能划分, 可分为算术运算(Arithmetic)、 关系运算(Rational)、 逻辑运算(Logical)三大类。 任何应用程序欲使用STL内建的仿函数, 都必须包含头文件,SGI则将它们实际定义于 <stl_function.h> 文件中。 以下分别描述。

2.可配接器(adaptable)的关键

  1. 在STL六大组件中,仿函数可说是体积最小、 观念最简单、 实现最容易的一个。但是也能立大功:它扮演一种"策略"角色(所谓策略, 是指算法可因为不同的仿函式的介入而有不同的变异行为一虽然算法本质是不变的。 ),可以让STL算法有更灵活的演出而更加灵活的关键, 在于STL仿函数的可配接性(adaptability)。
  2. STL仿函数应该有能力被函数配接器(function adapter, 第8章)修饰,彼此像积木一样地串接。 为了拥有配接能力, 每一个仿函数必须定义自己的相应型别(associative types) , 就像迭代器如果要融入整个STL大家庭,也必须依照规定定义自己的5个相应型别一样。 这些相应型别是为了让配接器能够取出,获得仿函数的某些信息。相应型别都只是—些typedef,所有必要操作在编译期就全部完成了,对程序的执行效率没有任何影响,不带来任何额外负担。
  3. 仿函数的相应型别主要用来表现函数参数型别和传回值型别,为了方便起见,<stl_function.h>定义了两个classes,分别代表一元仿函数和二元仿函数(STL不支持三元仿函数),其中没有任何data members或member functions , 唯有些型别定义,任何仿函数,只要依个人需求选择继承其中一个class,便自动拥有了那些相应型别,也就自动拥有了配接能力。

2.1 unary _function
unary_function用来呈现一元函数的参数型别和回返值型别。其定义非常简单:

// STL规定,每一个AdaptableUnary Function都应该继承此类别 
template <class Arg, class Result> 
struct unary_function { 
typedef Arg argument_type;
typedef Result result_type;
};

一旦某个仿函数继承了unary_function,其用户便可以这样取得该仿函数的参数型别,并以相同手法取得其回返值型别(下例未显示):

//以下仿函数继承了unary_function. 
template <class T> 
struct negate : public unary_function<T, T> {
T operator()(const T& x)const{return -x;}
};
//以下配接器(adapter)用来表示某个仿函数的逻辑负值(logical negation) 
template<class Predicate> 
class unary_negate
...
public:
bool operator()(const typename Predicate::argument_type& x)const{
...
}
};

这一类例子在第8章的仿函数配接器(functor adapter)中时时可见。

2.2 binary_function

binary_function用来呈现二元函数的第一参数型别、第二参数型别,以及回返值型别。其定义非常简单:

//STL规定,每一个Adaptable 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;
};

一旦某个仿函数继承了binary_function,其用户便可以这样取得该仿函数 的各种相应型别:

//以下仿函数继承了binary_function.
template <class T> 
struct plus : public binary_function<T, T, T> {
T operator()(const T& x,const T& y)const {return x+y;}
};

//以下配接器(adapter)用来将某个二元仿函数转化为一元仿函数

template<class Operation>
class binder1st
...
protected:
Operation op;
typename Operation::first_argument_type value;
public:
typename Operator::result_type
operator()(const typename Operation::second_argument_type& x)const{
...
}
};

这一类例子在第8章的仿函数配接器(functoradapter)中时时可见。

3.算术类(Arithmetic)仿函数

STL内建的"算术类仿函数",支持加法、减法、乘法、除法、模数(余数,modulus)和否定(negation)运算。除了“否定”运算为一元运算,其他都是二元运算:

  1. 加法:plus<T>
  2. 减法:minus<T>
  3. 乘法:multiplies<t>
  4. 除法:divides<T>
  5. 模取(modulus):modulus<T>
  6. 否定(negation):negate<T>

以下6个位算术类(Arithmetic)仿函数

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

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 bianary_function<T,T,T>{
T operator()(const T& x,const T& y)const{return x/y;}
};

template<class T>
struct modulus : public bianary_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;}

这些仿函数所产生的对象,用法和一般函数完全相同。当然,我们也可以产生一个无名的临时对象来履行函数功能。下面是个实例,显示两种用法:

#incldue<iostream>
#include<functional>
using namespace std;

int main()
{
//以下产生一些仿函数实体(对象)
plus<int> plusobj; 
minus<int> minusobj; 
multiplies<int> multipliesobj; 
divides<int> dividesobj; 
modulus<int> modulusobj; 
negate<int> negateobj; 

//以下运用上述对象,履行函数功能
cout << plusobj(3,5) << endl; //8
cout << minusobj (3,5) << endl; //-2
cout<<multipliesobj(3, 5) << endl;//15
cout<< dividesobj(3,5) << endl; //0
cout << modulusobj(3,5) << endl; //3
cout<<negateobj(3) << endl; //-3

//以下直接以仿函数的临时对象履行函数功能
//语法分析:functor<T>是一个临时对象,后面再接一对小括号
//意指调用function call operator
cout << plus<int>()(3,5) << endl; //8
cout<<minus<int>()(3,5) << endl; //-2
cout << multiplies<int>()(3,5)<< endl;//15
cout<<divides<int>(3,5)<<endl;//0
cout<<modulus<int>(3,5)<<endl;//3
cout<<negate<int>()(3)<<endl;//-3
}

前面提过,不会有人在这么单纯的情况下运用这些功能极其简单的仿函数。仿函数的主要用途是为了搭配STL算法。例如,以下式子表示要以1为基本元素,对vectoriv中的每一个元素进行乘法(multiplies)运算:

accumulate(iv. begin(),iv.end(),1, multiplies<int>()) ;

证同元素(identity element)

所谓 “运算op的证同元素(identity element) " , 意思是数值A若与该元素 做op运算,会得到A自己。 加法的证同元素为0, 因为任何元素加上0仍为自己。 乘法的证同元素为1, 因为任何元素乘以1仍为自己。

请注意,这些函数并非STL标准规格中的一员,但许多STL实现都有它们:

template <class T> 
inline T identity_element(plus<T>) 
{ return T(O); } 
//SGI STL并未实际运用这个函数
template <class T> 
inline T identity_element(multiplies<T>)
{ return T (1); }
//乘法的证同元素应用于<stl_numerics.h>的power(). 见6.3.6节

4.关系类运算类(Relational)仿函数

STL内建的 “关系运算类仿函数” 支持了等于、 不等千、 大于、 大于等于、小于、 小于等于六种运算。 每一个都是二元运算。

  1. 等于(equality) : equal_to<T>
  2. 不等于(inequality):not_equal_to<T>
  3. 大于(greater than):greater<T>
  4. 大于或等于(greater than or equal) : greater_equal<T>
  5. 小于Oess than) : less<T>
  6. 小于或等于(less than or equal) less_equal<T>
//以下6个为关系运算类(Relational)仿函数
template <class T> 
struct equal_to:public binary_function<T,T,bool> {
bool operator() (const T& x, const T& y) const { return 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; }
};

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

template <class T> 
struct greater:public binary_function<T,T,bool> {
bool operator() (const T& x, const T& y) const { return 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; }
};

template <class T> 
struct greater_equal:public binary_function<T,T,bool> {
bool operator() (const T& x, const T& y) const { return 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; }
};

这些仿函数所产生的对象,用法和一般函数完全相同。当然,也可以产生一个无名的临时对象来履行函数功能。下面是个实例,显示两种用法:

#include <iostream> 
#include <functional>
using namespace std; 
int main() 
{
//以下产生一些仿函数实体(对象)
equal_to<int> equal_to_obj; 
not_equal_to<int> not_equal_to_obj; 
greater<int> greater_obj; 
greater_equal<int> greater_equal_obj; less<int> less_obj; 
less_equal<int>less_equal_obj;


//以下运用上述对象, 履行函数功能
cout<<equal_to_obj(3,5)<< endl; //0
cout<<not_equal_to_obj(3,5)<< endl;//1 cout<<greater_obj(3,5)<< endl; //0
cout<<greater_equal_obj(3,5)<< endl;//0 cout<<less_obj(3,5)<< endl; //1
cout<<less_equal_obj(3,5)<< endl; //1

//以下直接以仿函数的临时对象履行函数功能
//语法分析:functor<T>()是一个临时对象,后面再接一对小括号
// 意指调用function call operator 
cout<<equal_to<int>()(3,5)<< endl; //0
cout<< not_equal_to<int>()(3,5)<< endl;//1
cout<<greater<int>()(3,5)<<endl; //0
cout<<greater_equal<int>()(3,5)<< endl; //0
cout<<less<int>()(3,5)<<endl;// 1 
cout<<less_equal<int>()(3,5)<<endl;//1
}

一般而言不会有人在这么单纯的情况下运用这些功能极其简单的仿函数。仿函 数的主要用途是为了搭配STL算法。例如以下式子表示要以递增次序对vector iv进行排序:

sort(iv.begin(),iv.end(),greater<int>());
5.逻辑运算类(Logical)仿函数

STL内建的“逻辑运算类仿函数”支持了逻辑运算中的And、Or、Not三种运算,其中And和Or为二元运算,Not为一元运算。

  1. 逻辑运算And:logical_and<T>

  2. 逻辑运算Or:logical_or<T>

  3. 逻辑运算Not:logical_not<T>

//以下3个为逻辑运算类(Logical)仿函数
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; }
};

这些仿函数所产生的对象,用法和一般函数完全相同。当然,我们也可以产生一个无名的临时对象来履行函数功能。下面是一个实例,显示两种用法:

#include<iostream>
#include<functional>
using namespace std;

int main()
{
//以下产生一些仿函数实体(对象)
logical_and<int>and_obj; 
logical_or<int>or_obj; 
logical_not<int>not_obj; 

//以下运用上述对象,履行函数功能
cout<<and_obj(true,true) << endl; //1
cout<<or_obj(true,false) << endl; //1
cout<<not_obj(true)<< endl; //0
//以下直接以仿函数的临时对象履行函数功能
//语法分析:functor<T> ()是一个临时对象,后面再接一对小括号
//意指调用functioncall operator 
cout<<logical_and<int>()(true,true)<< endl;//1
cout<<logical_or<int>()(true.false)<< endl; //1
cout<<logical_not<int>()(true)<< endl; //0
}

一般而言,不会有人在这么单纯的情况下运用这些功能极其简单的仿函数。仿 函数的主要用途是为了搭配STL算法。

6.证同(identity)、选择(select)、投射(proiect)

  1. 这一节介绍的仿函数,都只是将其参数原封不动地传回。
  2. 其中某些仿函数对传回的参数有刻意的选择,或是刻意的忽略。
  3. 之所以不在STL或其它泛型程序设计过程中直接使用原本极其简单的identity,project, select等操作,而要再划分一层出来,全是为了间接性:间接性是抽象化的重要工具。

C++标准规格并未涵盖本节所列的任何一个仿函数,不过它们常常存在于各个实现品中作为内部运用。以下列出SGI STL的版本。

//证同函数(identity function)。任何数值通过此函数后,不会有任何改变
//此式运用于<stl_set.h>,用来指定RB-tree所需的KeyOfValueop 
//那是因为set元素的键值即实值,所以采用过ientity
template <class T> 
struct identity:public unary_function(T,T>{
const T& operator()(const T& x)const {return x;}
};
//选择函数(selection function): 接受一个pair,传回其第一元素
//此式运用于<stl_rnap.h>,用来指定RB-tree所需的KeyOfValueop 
//由于map系以pair元素的第一元素为其键值,所以采用select1st 
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;
}
};

//选择函数:接受一个pair,传回其第二元素
//SGI STL并未运用此式
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;
}
};

//投射函数:传回第一参数,忽略第二参数
// SGI STL并未运用此式
template<class Arg1, class Arg2> 
struct project1st:public binary_function<Arg1, Arg2, Arg1> {
Argl operator() (const Argl& x, const Arg2&) const { return x; });


//投射函数:传回第二参数,忽略第一参数
// SGI STL并未运用此式
template <class Arg1, class Arg2> 
struct project2nd:public binary_function<Arg1, Arg2, Arg2> { 
Arg2 operator() (const Arg1&, const Arg2& y) const{ return y;} 
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值