有些功能实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。
写一个公共的函数,可以,这是一个解决方法,再将函数指针当做算法的一个参数。 函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。当函数参数有所变化,则无法兼容旧的代码函数指针毕竟不能满足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)成员变量,将需要比较的值作为成员变量保存起来,可以作为类构造函数的参数进行传参,可拓展性强,且易维护。这就出现了仿函数。“函数对象”:一种具有函数性质的对象(是类 而不是普通的函数)。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写,重载)function call运算子(operator())。拥有这样的运算子后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator()。 特点:
仿函数的应用场景主要在:作为算法组件中的相关函数接口的参数。仿函数对象仅仅占用1字节,因为内部没有数据成员STL仿函数的分类:
1、以操作数的个数划分(通常被继承)) 一元函数对象基类(unary_function)二元函数对象基类(binary_function) 2、以功能划分,可分为 算术运算(Arithmetic)关系运算(Rational)逻辑运算(Logical)举例:
//2.使用1元仿函数 class CopyClass1Param :public unary_function<int, bool> { public: bool operator()(const int value) const { return value>2; } };举例:
//4.使用2元仿函数 class CopyClassUpNum : public binary_function<int, int, bool> { public: bool operator()(const int srcValue, const int base) const { return srcValue > base; } };举例
// 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; }适配器单独一篇博客再讲,这里简单概述。
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是为了提取算法传进变量,以供仿函数适配器使用,如果你没有继承这两种类,那么在算法调用仿函数时,仿函数适配器提取不出这几个变量,那么就会报错.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; }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
