STL学习_仿函数篇
简介
仿函数,实质是函数对象,是一种具有函数特质的对象。对调用者,它可以像函数一样地被调用;对被调用者,它可以以对象所定义的function call operator扮演函数的实质决策。
STL算法中,很多算法的实现都是基于两种版本:
- 版本一表现出最常用(最直观)的某种运算;
- 版本二表现出最泛化的演算流程,允许用户“以template参数来指定所要采取的策略”。
版本二是可以允许用户指定任何“操作”,然后将操作当做算法的参数,实现的方法可以将该“操作”(可能拥有数条以上的指令)设计为一个函数,再将函数指针当做算法的一个参数;或是将该“操作”设计为一个所谓的仿函数,再以该仿函数产生一个对象,并以此对象作为算法的一个参数。
函数指针就能够实现版本二的需求,但为什么还需要仿函数?原因:函数指针不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件搭配,产生更灵活的变化。
仿函数实质是一个“行为类似函数”的对象。为使其“行为类似函数”,其类别定义中必须自定义(改写、重载)function call运算算子。拥有这样的运算子后,就可以在仿函数的对象后面加上一对小括号,依此来调用仿函数所定义的operator()。
STL仿函数与STL算法之间的关系如下:
algorithm(first, last, ..., functorObj) { functorObj(...) } // 其中functorObj是STL内建的仿函数,或用户自定义的仿函数,对应定义格式如下: template<typename T> class functor { void operator()(...); }
分类
STL仿函数中,按照操作数的个数划分,主要分为一元和二元仿函数;而按其功能划分,主要分为算法运算、关系运算,逻辑运算三大类。
STL组件中,仿函数是体积最小,观念最简单,实现最容易的一个。但它却扮演一种“策略”角色,可以让STL算法有更灵活的演出。而更灵活的关键在于STL仿函数的可配接性。仿函数为了拥有可配接性,必须定义自己的相应型别(STL的5个型别之一),此型别主要是为了让配接器能够取出,从而获得仿函数的某些信息。相应的型别都只是一些typedef,所有必要操作在编译期就全部完成,对程序的执行效率没有任何影响,不会带来任何额外负担。仿函数的型别主要用来表现函数参数型别和传回值型别。任何仿函数只要拥有了型别,就拥有了配接能力。
源码分析
下面主要针对STL源码中的仿函数,按仿函数的类别对其进行简单介绍。
1)一元仿函数
// unary_function:用来呈现一元函数的参数型别和返回值型别。 template <class _Arg, class _Result> struct unary_function { typedef _Arg argument_type; // 参数型别 typedef _Result result_type; // 返回值型别 };
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; // 返回值型别 };
3)算法类仿函数
// plus:加法运算 template <class _Tp> struct plus : public binary_function<_Tp,_Tp,_Tp> { _Tp operator()(const _Tp& __x, const _Tp& __y) const { return __x + __y; } }; // minus:减法运算 template <class _Tp> struct minus : public binary_function<_Tp,_Tp,_Tp> { _Tp operator()(const _Tp& __x, const _Tp& __y) const { return __x - __y; } }; // multiplies:乘法运算 template <class _Tp> struct multiplies : public binary_function<_Tp,_Tp,_Tp> { _Tp operator()(const _Tp& __x, const _Tp& __y) const { return __x * __y; } }; // divides:除法运算 template <class _Tp> struct divides : public binary_function<_Tp,_Tp,_Tp> { _Tp operator()(const _Tp& __x, const _Tp& __y) const { return __x / __y; } }; // modulus:取余运算 template <class _Tp> struct modulus : public binary_function<_Tp,_Tp,_Tp> { _Tp operator()(const _Tp& __x, const _Tp& __y) const { return __x % __y; } }; // negate:取反运算 template <class _Tp> struct negate : public unary_function<_Tp,_Tp> { _Tp operator()(const _Tp& __x) const { return -__x; } };
4)证同元素仿函数
所谓“运算符op的证同元素(identity element),指数值A若与该元素做op运算,会得到A自己。加法的证同元素为0,因为任何元素加上0仍为自己;乘法的证同元素为1,因为任何元素乘以1仍为自己。
template <class _Tp> inline _Tp identity_element(plus<_Tp>) { return _Tp(0); } // 乘法证同元素。即数值A与1相乘,值为A。 template <class _Tp> inline _Tp identity_element(multiplies<_Tp>) { return _Tp(1); }
5)关系运算类仿函数
// equal_to:相等运算 template <class _Tp> struct equal_to : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; } }; // not_equal_to:不相等运算 template <class _Tp> struct not_equal_to : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x != __y; } }; // greater:大于运算 template <class _Tp> struct greater : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x > __y; } }; // less:小于运算 template <class _Tp> struct less : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x < __y; } }; // greater_equal:大于等于运算 template <class _Tp> struct greater_equal : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x >= __y; } }; // less_equal:小于等于运算 template <class _Tp> struct less_equal : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x <= __y; } };
6)逻辑运算类仿函数
// logical_and:逻辑And运算 template <class _Tp> struct logical_and : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x && __y; } }; // logical_or:逻辑Or运算 template <class _Tp> struct logical_or : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x || __y; } }; // logical_not:逻辑Not运算 template <class _Tp> struct logical_not : public unary_function<_Tp,bool> { bool operator()(const _Tp& __x) const { return !__x; } };
7)证同(identity)、选择(select)、投射(project)类仿函数
// 证同函数:任何值通过此函数后,不会有任何改变 template <class _Tp> struct _Identity : public unary_function<_Tp,_Tp> { const _Tp& operator()(const _Tp& __x) const { return __x; } }; template <class _Tp> struct identity : public _Identity<_Tp> {}; // 选择函数:接受一个pair,传回其第一元素 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,传回其第二元素 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 _Pair> struct select1st : public _Select1st<_Pair> {}; template <class _Pair> struct select2nd : public _Select2nd<_Pair> {}; // 投射函数:传回第一参数,忽略第二参数 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; } }; template <class _Arg1, class _Arg2> struct project1st : public _Project1st<_Arg1, _Arg2> {}; template <class _Arg1, class _Arg2> struct project2nd : public _Project2nd<_Arg1, _Arg2> {};
仿函数的主要用途是搭配STL算法使用。仿函数的使用方式有两种:
- 1)定义仿函数实体(对象),然后运行实体(对象),履行函数功能;
- 2)直接模仿仿函数的临时对象履行函数功能。
具体使用举例如下(以加法仿函数为例):
// 方法1 // 定义仿函数实体(对象) plus<int> plusobj; // 运用上述实体(对象),履行函数功能 cout << plustobj(1, 2) << endl; // 方法2 cout << plus<int>() (3, 5) << endl;
参考文献
STL源码剖析——侯捷
STL源码