C++ 进阶基础之十

大纲

函数对象

函数对象的概念

尽管函数指针被广泛用于实现函数回调,但 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;

// 查找第一个大于 20 的元素
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);

// 遍历打印,使用匿名函数与 Lambda 表达式
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);

// 遍历打印,使用匿名函数与 Lambda 表达式
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 适配器:bind1stbind2nd,前者把值绑定到二元函数对象的第一个实参上,后者绑定在第二个实参上。
  • 取反器(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;

// 函数对象(仿函数),继承自 binary_function
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);
}

// 0 ~ 9 加起始值,然后输出计算结果
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;

// 函数对象(仿函数),继承自 unary_function
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);
}

// 查找第一个大于5的数字(第一种写法)
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;
}

// 查找第一个大于5的数字(第二种写法)
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
函数指针适配器
  • ptr_fun 可以将函数指针适配为函数对象
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);
}

// 0 ~ 9 加起始值,然后输出计算结果
// ptr_fun 可以将函数指针适配为函数对象
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);

// 遍历打印数据
// mem_fun_ref 可以将成员函数适配为函数对象
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);


// 遍历打印数据
// mem_fun 可以将成员函数适配为函数对象
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