详解STL之仿函数

仿函数(函数对象)概观

仿函数的通俗定义:仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。仿函数的语法几乎和普通函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。

仿函数的作用主要在哪里?
STL 所提供的各种算法,往往有两个版本,其中一个版本表现出最常用(或最直观)的某种运算,第二个版本则表现出最泛化的演算流程,允许用户“以 template 参数来指定所要采取的策略”。以 sort() 为例,其第一版本是以 operator< 为排序时的元素位置调整依据,第二个版本则允许用户指定任何操作,务求排序后的两两相邻元素都能令该操作结果为 true。

要将某种 “操作” 当作算法的参数,唯一办法就是先将该 “操作”(可能有数条以上的指令) 设计为一个函数,再将函数指针当作算法的一个参数;或是将该 “操作” 设计成一个所谓的仿函数(就语言层面上来说是个类),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。

根据以上陈述,既然函数指针可以达到 “将数组操作当作算法的参数”,那又何须仿函数呢?原因在于函数指针毕竟不能满足 STL 对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL 其它组件(如配接器 adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以。

就实现观点而言,仿函数其实就是一个 “行为类似函数” 的对象。为了能够 “行为类似函数”,其类别定义中必须自定义(重载)“ () ” 运算符。拥有这样的运算符后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的 operator()。如下:

#include <functional>
#include <iostream>
using namespace std;
int main()
{
	greater<int> ig;
    cout << boolalpha << ig(4, 6); // false 4 > 6
    cout << greater<int>()(6, 4);   // true 6 > 4
}

其中第一种用法比较熟悉,greater<int> ig 产生一个名为 ig 的对象,ig(4, 6) 则是调用其 operator(),并传入两个参数4,6;第二种用法中的 greater<int>() 时产生一个临时(无名)对象,之后的(6, 4) 才是指定两个参数6,4。

上述的第二种语法在一般情况下不常见,但是对仿函数而言,却是主流用法。(STL中的仿函数绝大部分采用这种语法)

STL 仿函数的分类,若以操作数的个数划分,可分为一元和二元仿函数;若以功能划分,可分为算术运算、关系运算、逻辑运算三大类。任何应用程序想使用 STL 内建的仿函数,都必须包含 #include <functional> 头文件,SGI 则将它们实际定义于 <stl_funcation.h> 中。


可配接(adaptable)的关键

STL 仿函数应该有能力被函数配接器配接,彼此像积木一样串接。为了拥有配接能力,每一个仿函数必须定义自己的相应型别,就像迭代器如果要融入整个 STL 大家庭,也必须依照规定定义自己的5个型别一样,这些相应型别是为了让配接器能够取出,获得仿函数的某些信息(仿函数能够保存信息,函数指针则不能)。相应型别都只是一些typedef,所以必要操作都在编译期就全部完成了,对程序的执行效率没有任何影响,不带来热河额外负担。

仿函数的相应型别主要用来表现函数参数型别和传回值型别。为了方便起见,<stl_function.h> 定义了两个 classes,分别代表一元仿函数和二元仿函数(STL不支持三元仿函数),其中没有任何data members 或 member functions,唯有一些型别定义。任何仿函数,只要依个人需求选择继承其中一个 class,便自动拥有了那些相应型别,也就自动拥有了配接能力。

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

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

// 以下配接器用来表示某个仿函数的逻辑负值
template<class Predicate>
class unary_negate
{
...
public:
	// 模板中,需要 typename 来指明后面的定义是一个类型
	bool operator() (const typename Predicate::argument_type&x) const
	{
		...
	}
};

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,告诉编译器其为一个类型值
    typename Operation::result_type operator() (const typename Operation::second_argument_type& x) const { ... }
};
  1. 算术类仿函数 参见相关源码;

  2. 关系运算类仿函数 参见相关源码;

  3. 逻辑运算类仿函数 参见相关源码;

  4. 证同、选择、投射 参见相关源码;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值