前言
本小节将介绍STL六大组件之一的仿函数(又称函数对象),在前面分析算法部分时,我们其实已经见识到了仿函数的大量使用(各算法的另一个版本,传入comp仿函数)。关于仿函数的实现,其实就是重载了()
运算符,然后调用,所以又称函数对象。接下来我们就来分析该部分。
仿函数
虽然函数指针也可以完成将各种操作当作参数传入,但是之所以出现了仿函数,是因为函数指针并不能满足STL对抽象的要求,也不能和其他组件很好的搭配。
SGISTL
自带了一部分仿函数,都在stl_function.h
中,如果要使用仿函数,则需要引入头文件functional
。这里给出一个简单的例子:
#include <iostream>
#include <functional>
using namespace std;
int main()
{
int i = 2, j = 8;
multiplies<int> mul;
cout << mul(i, j) << endl; //调用()操作符
cout << minus<int>()(i, j) << endl; //产生一个临时无名对象调用()操作符
return 0;
}
上面使用的两个仿函数的源码实现如下:
//可见就只是重载了()操作符
//不过它们都继承了binary_function
//这个基类的作用我们等下分析
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; }
};
STL
的仿函数按照操作数的个数分类分为一元和二元仿函数;按照功能来分类,可以分为算术运算、关系运算、逻辑运算三类。上面的这个例子中,继承的binary_function
其实是代表该运算是二元运算,并且接着的三个模板参数分别代表两个参数的类型以及返回值的类型,这样做的目的都是为了可被函数配接器配接。
一元仿函数和二元仿函数的基类
//一元仿函数的基类
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;
};
与配接器的联系
而配接器就可以利用定义的这些相应型别完成和其仿函数不同的功能。
为了帮助理解,接下来举个例子
//仿函数配接器,利用仿函数改变一下设计变成求逻辑相反数(为true则返回false,为false返回true)
template <class Predicate>
class unary_negate
: public unary_function<typename Predicate::argument_type, bool> {
protected:
//仿函数
Predicate pred;
public:
explicit unary_negate(const Predicate& x) : pred(x) {}
//利用继承的unary_negate里面typedef的参数类型获取参数类型
bool operator()(const typename Predicate::argument_type& x) const {
return !pred(x);
}
};
就这样通过原来的仿函数为基础改变了下设计形成另一个仿函数(此时应该称为仿函数配接器了),这就是继承那两个基类的作用,获取到参数及返回值的类型,这个做法在仿函数配接器里面经常可见,而且可以看到unary_negate
还继承了unary_function
,证明其他的仿函数配接器还可以通过改变它来完成另外的功能,就像搭积木一样。这是函数指针无法完成的。
stl中各仿函数的实现
stl
中实现了一部分常用的仿函数,以下按照仿函数的功能进行分类分析。
算术类:
加法:
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 minus : 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; } };
以上的仿函数都很简单,就是根据参数个数来继承
unary_function
或者binary_function
。而仿函数的用途是与算法相搭配。这里举一个例子,
比如我们之前分析power
算法时,它的源码如下:template <class T, class Integer> inline T power(T x, Integer n) { //最后一个参数为乘法仿函数,参数类型及返回值类型都为T return power(x, n, multiplies<T>()); } template <class T, class Integer, class MonoidOperation> T power(T x, Integer n, MonoidOperation op) { //任何数的0次方都为1,这是代表运算的证同元素,我们等下进行详细的分析 if (n == 0) return identity_element(op); else { while ((n & 1) == 0) { n >>= 1; //调用传入的仿函数进行求解 x = op(x, x); } T result = x; n >>= 1; while (n != 0) { //调用传入的仿函数进行求解 x = op(x, x); if ((n & 1) != 0) //调用传入的仿函数进行求解 result = op(result, x); n >>= 1; } return result; } }
从中可以看到算法和仿函数之间的紧密搭配。
证同元素:
上面的
power
算法中当次方数n等于0时使用了identity_element(op)
作为返回值。这点我们在当时分析power
算法的时候并没有细讲,在这里补上这个坑。证同元素是指当数值A与该元素进行某种op运算得出的结果还是A自己时,此时该元素就是该op运算的证同元素。比如乘法的证同元素为1(任何数乘1都为本身),加法的证同元素为0(任何数加0都为本身)等。
关系运算类:
等于
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 greater : 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 : 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; } };
逻辑运算类:
逻辑与
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_or : public binary_function<T, T, bool> { bool operator()(const T& x, const T& y) const { return x || y; } };
证同、选择、投射
以上三种仿函数,刚才了解了一下证同元素,而关于选择仿函数,它的作用是接受一个pair作为传入参数,并返回pair的第一个/第二个元素。关于投射仿函数,它的作用是返回第一个/第二个参数,忽略第二个/第一个参数。
它们的实现如下:
证同:
template <class T> struct identity : public unary_function<T, T> { const T& operator()(const T& x) const { return x; } };
选择:
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; } }; 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; } };
投射:
template <class Arg1, class Arg2> struct project1st : public binary_function<Arg1, Arg2, Arg1> { Arg1 operator()(const Arg1& x, const Arg2&) const { return x; } }; template <class Arg1, class Arg2> struct project2nd : public binary_function<Arg1, Arg2, Arg2> { Arg2 operator()(const Arg1&, const Arg2& y) const { return y; } };
小结
关于仿函数的分析就到此为止,其实实现的很简单也很基础,主要是为了配合STL其他组件进行使用。下一小节中我们将分析STL中最后一个组件配接器部分。