C++ 标准模板库(STL)——仿函数(functors)

    技术2026-01-26  6

    仿函数functors

    仿函数(函数对象)1、背景2、定义3、类型3.1、操作数个数划分3.1.1、一元仿函数基类(unary_function)3.1.2、二元仿函数基类(binary_function) 3.2、功能划分3.2.1、算术运算3.2.2、关系运算类3.2.3、逻辑运算类 4、仿函数适配器4.1 应用举例:将仿函数某个参数绑定为固定值的适配器 5、函数指针、仿函数、Lambda表达式在同一场景下的使用示例 参考

    仿函数(函数对象)

    1、背景

    有些功能实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。

    写一个公共的函数,可以,这是一个解决方法,再将函数指针当做算法的一个参数。 函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。当函数参数有所变化,则无法兼容旧的代码函数指针毕竟不能满足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)成员变量,将需要比较的值作为成员变量保存起来,可以作为类构造函数的参数进行传参,可拓展性强,且易维护。这就出现了仿函数。

    2、定义

    “函数对象”:一种具有函数性质的对象(是类 而不是普通的函数)。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写,重载)function call运算子(operator())。拥有这样的运算子后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator()。 特点:

    仿函数的应用场景主要在:作为算法组件中的相关函数接口的参数。仿函数对象仅仅占用1字节,因为内部没有数据成员

    3、类型

    STL仿函数的分类:

    1、以操作数的个数划分(通常被继承)) 一元函数对象基类(unary_function)二元函数对象基类(binary_function) 2、以功能划分,可分为 算术运算(Arithmetic)关系运算(Rational)逻辑运算(Logical)

    3.1、操作数个数划分

    3.1.1、一元仿函数基类(unary_function)

    template <class Arg, class Result> struct unary_function { typedef Arg argument_type; // 参数类型别名 typedef Result result_type; // 返回值类型别名 };

    举例:

    //2.使用1元仿函数 class CopyClass1Param :public unary_function<int, bool> { public: bool operator()(const int value) const { return value>2; } };

    3.1.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; // 返回值类型别名 };

    举例:

    //4.使用2元仿函数 class CopyClassUpNum : public binary_function<int, int, bool> { public: bool operator()(const int srcValue, const int base) const { return srcValue > base; } };

    3.2、功能划分

    3.2.1、算术运算

    //算术类仿函数 + - * / % //plus仿函数,生成一个对象,里面仅仅有一个函数重载的方法。 template <class T> struct plus : public binary_function<T, T, T> { T operator()(const T& x, const T& y) const { return x + y; } }; //minus仿函数 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 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; } }; #include <iostream> // std::cout #include <functional> // std::plus #include <algorithm> // std::transform using namespace std; int main(void) { cout << minus<int>()(10,5) << endl;//5 cout << multiplies<int>()(10,5) << endl;//50 cout << divides<int>()(10,5) << endl;//2 cout << modulus<int>()(10,5) << endl;//0 cout << negate<int>()(10) << endl;//-10 return 0; }

    3.2.2、关系运算类

    //关系运算符仿函数 // x==y 仿函数 template <class T> struct equal_to : public binary_function<T, T, bool> { bool operator()(const T& x, const T& y) const { return x == y; } }; // 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; } }; // 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; } }; // 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; } }; // 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; } }; // 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; } };

    举例

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

    3.2.3、逻辑运算类

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

    4、仿函数适配器

    适配器单独一篇博客再讲,这里简单概述。

    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是为了提取算法传进变量,以供仿函数适配器使用,如果你没有继承这两种类,那么在算法调用仿函数时,仿函数适配器提取不出这几个变量,那么就会报错.

    4.1 应用举例:将仿函数某个参数绑定为固定值的适配器

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

    5、函数指针、仿函数、Lambda表达式在同一场景下的使用示例

    #includ <iostream> #include <algorithm> #include <vector> using namespace std; bool Cmp(int a,int b) { return a>b; } class Cmp2 { public: bool operator()(int a,int b) { return a>b; } }; class printElem { public: void operator () (int elem) { cout<<elem<<" "; } }; int main(void) { vector<int> ver{0,9,4,3,8}; //使用函数指针 sort(ver.begin(),ver.end(),*Cmp); for_each(ver.begin(),ver.end(),printElem ()); cout<<endl;//输出9 8 4 3 0 vector<int> ver2{1,3,8,9,4}; //使用仿函数 sort(ver2.begin(),ver2.end(),Cmp2()); for_each(ver2.begin(),ver2.end(),printElem ()); cout<<endl;//输出9 8 4 3 1 vector<int> ver3{5,8,3,7,9}; //使用Lambda表达式 sort(ver3.begin(),ver3.end(),[](int a,int b){return a>b;}); for_each(ver3.begin(),ver3.end(),printElem ()); cout<<endl;//输出9 8 7 5 3 return 0; }

    参考

    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

    Processed: 0.018, SQL: 9