大纲 函数对象 函数对象的概念 尽管函数指针被广泛用于实现函数回调,但 C++ 还提供了一个重要的实现回调函数的方法,那就是函数对象。重载函数调用操作符 ()
的类,其对象常称为函数对象(Function Object),即它们是行为类似函数的对象。 一个类对象,表现出一个函数的特征,就是通过 对象名 + (参数列表)
的方式使用一个类对象,如果没有上下文,完全可以把它看作一个函数对待,这是通过重载类的 ()
操作符来实现的。在 C++ 标准库中,函数对象被广泛地使用以获得弹性,并且标准库中的很多算法都可以使用函数对象或者函数来作为自定义的回调行为。值得一提的是,函数对象的别名是 仿函数
,或者是 伪函数
。
提示
functor
被翻译成函数对象,或者叫 仿函数
,或者叫 伪函数
,是重载了 ()
操作符的普通类对象。从语法上讲,它与普通函数的行为类似。函数对象是一个类,并不是一个函数。它重载了 ()
操作符,因此可以像函数一样调用。 greater<>
与 less<>
就是 STL 提供的函数对象。函数对象的谓词 谓词的概念 谓词是指返回值是 bool
类型的普通函数或者是重载了 operator()
的函数对象(仿函数)。如果 opeartor ()
接受一个参数,那么叫做 一元谓词
;如果 opeartor ()
接受两个参数,那么就叫做 二元谓词
,谓词可以作为一个判断式。
提示
一元函数对象
:函数参数有 1 个二元函数对象
:函数参数有 2 个谓词就是返回值是 bool
类型的普通函数或者函数对象(仿函数)。 谓词的使用案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <iostream> #include <vector> #include <algorithm> using namespace std;class GreaterThen20 {public : bool operator () (const int &val) { return val > 20 ; } }; class MyCompare {public : bool operator () (const int &v1, const int &v2) { return v1 > v2; } }; void test01 () { cout << "------ 一元函数对象的使用 ------" << endl; vector<int > v; v.push_back (10 ); v.push_back (20 ); v.push_back (30 ); v.push_back (40 ); v.push_back (50 ); GreaterThen20 greaterThen; vector<int >::iterator pos = find_if (v.begin (), v.end (), greaterThen); if (pos != v.end ()) { cout << "first number greater then 20 is " << *pos << endl; } else { cout << "not found first number greater then 20" << endl; } } void test02 () { cout << "------ 二元函数对象的使用 ------" << endl; vector<int > v; v.push_back (10 ); v.push_back (30 ); v.push_back (20 ); v.push_back (50 ); v.push_back (40 ); MyCompare myCompare; sort (v.begin (), v.end (), myCompare); for_each(v.begin (), v.end (), [](int value) { cout << value << " " ; }); } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 ------ 一元函数对象的使用 ------ first number greater then 20 is 30 ------ 二元函数对象的使用 ------ 50 40 30 20 10
内建的函数对象 STL 内建了一些函数对象(即函数对象类模板),分为:算数类函数对象、关系运算类函数对象、逻辑运算类函数对象。这些函数对象所产生的对象,用法和普通函数完全相同,当然还可以产生匿名的临时对象来履行函数的功能。使用 STL 内建的函数对象,需要引入头文件 #include <functional>
。
内建函数对象介绍 算数类函数对象(除了 negate
是一元运算,其他都是二元运算) 1 2 3 4 5 6 template <class T > T plus<T> template <class T > T minus<T> template <class T > T multiplies<T> template <class T > T divides<T> template <class T > T modulus<T> template <class T > T negate<T>
1 2 3 4 5 6 template <class T >bool equal_to<T> template <class T >bool not_equal_to<T> template <class T >bool greater<T> template <class T >bool greater_equal<T> template <class T >bool less<T> template <class T >bool less_equal<T>
逻辑运算类仿函数:(not
为一元运算,其他为二元运算) 1 2 3 template <class T >bool logical_and<T> template <class T >bool logical_or<T> template <class T >bool logical_not<T>
内建函数对象的使用案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <functional> #include <algorithm> #include <vector> using namespace std;void test01 () { cout << "------ 算数类函数对象的使用 ------" << endl; negate<int > neg; cout << neg (10 ) << endl; plus<int > add; cout << add (1 , 3 ) << endl; } void test02 () { cout << "------ 关系运算类函数对象的使用 ------" << endl; vector<int > v; v.push_back (10 ); v.push_back (30 ); v.push_back (20 ); v.push_back (50 ); v.push_back (40 ); greater<int > greate; sort (v.begin (), v.end (), greate); for_each(v.begin (), v.end (), [](int value) { cout << value << " " ; }); } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 ------ 算数类函数对象的使用 ------ -10 4 ------ 关系运算类函数对象的使用 ------ 50 40 30 20 10
函数对象简单使用的案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> using namespace std;class MyPrint {public : void operator () (const int &num) { cout << "num = " << num << endl; } }; void MyPrint2 (int num) { cout << "num2 = " << num << endl; } void doPrint (MyPrint print, int num) { print (num); } int main () { MyPrint myPrint; myPrint (2 ); MyPrint ()(3 ); MyPrint2 (4 ); doPrint (myPrint, 5 ); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 num = 2 num = 3 num2 = 4 num = 5
函数对象结合容器使用的案例 在下述案例中,往 set 容器插入自定义数据类型,并通过函数对象(仿函数)让 set 容器使用自定义的排序规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <set> using namespace std;class CStudent {public : CStudent (int id, string name) { this ->id = id; this ->name = name; } int getId () const { return this ->id; } string getName () const { return this ->name; } private : int id; string name; }; class StuFunctor {public : bool operator () (const CStudent &stu1, const CStudent &stu2) { return stu1.getId () > stu2.getId (); } }; void printSet (set<CStudent, StuFunctor> &s) { for (set<CStudent, StuFunctor>::iterator it = s.begin (); it != s.end (); it++) { cout << "id: " << it->getId () << ", name: " << it->getName () << endl; } } int main () { set<CStudent, StuFunctor> setStu; setStu.insert (CStudent (3 , "小张" )); setStu.insert (CStudent (1 , "小李" )); setStu.insert (CStudent (5 , "小王" )); setStu.insert (CStudent (2 , "小刘" )); printSet (setStu); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 id: 5, name: 小王 id: 3, name: 小张 id: 2, name: 小刘 id: 1, name: 小李
函数对象的总结 函数对象通常不定义构造函数和析构函数,所以在执行构造和析构时,不会发生任何问题,这避免了函数调用的运行时问题。 函数对象超出了普通函数的概念,函数对象可以有自己的状态(即可以使用成员变量记录状态)。 函数对象可以实现内联编译,性能好,用函数指针几乎不可能实现。 模版函数对象使函数对象具有通用性,这也是它的优势之一。 适配器 函数适配器 函数适配器的概念 STL 中已经定义了大量的函数对象,但是有时候需要对函数返回值进行进一步的简单计算,或者填上更多的参数,不能直接代入算法。函数适配器实现了这一功能,可以将一种函数对象转化为另一种符合要求的函数对象。 函数适配器可以分为 4 大类:绑定适配器(bind adaptor)、组合适配器(composite adaptor)、指针函数适配器(pointer function adaptor)和成员函数适配器(member function adaptor)。STL 中所有的函数适配器如下表所示:
直接构造 STL 中的函数适配器通常会导致冗长的类型声明。为简化函数适配器的构造,STL 还提供了函数适配器辅助函数(如下表所示),借助于泛型自动推断技术,无需显式的类型声明便可实现函数适配器的构造。
常用的函数适配器 STL 提供了一组函数适配器,用于特殊化或者扩展一元和二元函数对象。常用适配器是:
绑定器(binder)
: binder 通过把二元函数对象的一个实参绑定到一个特殊的值上,将其转换成一元函数对象。C++ 标准库提供两种预定义的 binder 适配器:bind1st
和 bind2nd
,前者把值绑定到二元函数对象的第一个实参上,后者绑定在第二个实参上。取反器(negator)
: negator 是一个将函数对象的值翻转的函数适配器。C++ 标准库提供两个预定义的 ngeator 适配器,其中 not1
可以翻转一元函数对象的真值,而 not2 可以翻转二元函数对象的真值。常用函数适配器列表如下:
函数适配器的使用 绑定适配器 使用步骤第一步:绑定数据,如 bind2nd
或者 bind1st
,目的是让二元函数对象的一个实参绑定到一个特殊的值上,将其转换成一元函数对象 第二步:函数对象继承自 binary_function<参数类型1, 参数类型2, 返回值类型>
第三步:operator()
加 const
修饰符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <vector> #include <algorithm> #include <functional> using namespace std;class MyPrint : public binary_function<int , int , void > {public : void operator () (int v, int start) const { cout << v + start << " " ; } }; int main () { vector<int > v; for (int i = 0 ; i < 10 ; i++) { v.push_back (i); } int num = 100 ; for_each(v.begin (), v.end (), bind2nd (MyPrint (), num)); return 0 ; }
程序运行输出的结果如下:
1 100 101 102 103 104 105 106 107 108 109
取反适配器 使用步骤第一步:取反,如 not1
或者 not2
,目的是将一个函数对象的值翻转 第二步:函数对象继承自 unary_function<参数类型, 返回值类型>
第三步:operator()
加 const
修饰符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <vector> #include <algorithm> #include <functional> using namespace std;class GreaterThenFive : public unary_function<int , bool > {public : bool operator () (int v) const { return v > 5 ; } }; int main () { vector<int > v; for (int i = 0 ; i < 10 ; i++) { v.push_back (i); } vector<int >::iterator pos = find_if (v.begin (), v.end (), not1 (GreaterThenFive ())); if (pos != v.end ()) { cout << "found first number greate then five is " << *pos << endl; } else { cout << "not found first number greate then five" << endl; } vector<int >::iterator pos2 = find_if (v.begin (), v.end (), not1 (bind2nd (greater<int >(), 5 ))); if (pos2 != v.end ()) { cout << "found first number greate then five is " << *pos2 << endl; } else { cout << "not found first number greate then five" << endl; } return 0 ; }
程序运行输出的结果如下:
1 2 found first number greate then five is 0 found first number greate then five is 0
函数指针适配器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <vector> #include <algorithm> #include <functional> using namespace std;void MyPrint (int v, int start) { cout << v + start << " " ; } int main () { vector<int > v; for (int i = 0 ; i < 10 ; i++) { v.push_back (i); } int num = 100 ; for_each(v.begin (), v.end (), bind2nd (ptr_fun (MyPrint), 100 )); return 0 ; }
程序运行输出的结果如下:
1 100 101 102 103 104 105 106 107 108 109
成员函数适配器 如果容器存放的是对象指针,则使用 mem_fun
如果容器存放的是对象实例,则使用 mem_fun_ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iostream> #include <vector> #include <algorithm> #include <functional> using namespace std;class Person {public : Person (string name, int age) { this ->name = name; this ->age = age; } void showPerson () { cout << "name: " << this ->name << ", age: " << this ->age << endl; } string name; int age; }; void test01 () { cout << "----------- 成员函数适配器 mem_fun_ref 的使用 -------------" << endl; vector<Person> v; Person p1 ("Jim" , 15 ) ; Person p2 ("Peter" , 18 ) ; Person p3 ("David" , 16 ) ; Person p4 ("Tom" , 20 ) ; v.push_back (p1); v.push_back (p2); v.push_back (p3); v.push_back (p4); for_each(v.begin (), v.end (), mem_fun_ref (&Person::showPerson)); } void test02 () { cout << "----------- 成员函数适配器 mem_fun 的使用 -------------" << endl; vector<Person *> v; Person p1 ("Jim" , 15 ) ; Person p2 ("Peter" , 18 ) ; Person p3 ("David" , 16 ) ; Person p4 ("Tom" , 20 ) ; v.push_back (&p1); v.push_back (&p2); v.push_back (&p3); v.push_back (&p4); for_each(v.begin (), v.end (), mem_fun (&Person::showPerson)); } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 ----------- 成员函数适配器 mem_fun_ref 的使用 ------------- name: Jim, age: 15 name: Peter, age: 18 name: David, age: 16 name: Tom, age: 20 ----------- 成员函数适配器 mem_fun 的使用 ------------- name: Jim, age: 15 name: Peter, age: 18 name: David, age: 16 name: Tom, age: 20