回调函数

回调函数

3 分钟彻底理解回调函数 | B 站

3 分钟搞懂回调函数 每个开发者都具备的基础能力 | B 站

『教程』回调函数是个啥? | B 站

1. 认识回调函数?

1. 什么是回调函数?

回调函数(Callback Function)是 函数指针的一种应用形式,它的核心思想是:“把函数作为参数传给另一个函数,由那个函数在合适的时机再调用它。” 通常用于 “别人调用你自己的函数”,你把函数地址传给某个函数,等它内部逻辑执行到某个阶段时,再“反过来”调用你传入的函数。换句话说:

  • 你写函数 A(定义了“要做什么”)。
  • 别人写函数 B(控制“什么时候做”)。
  • 你把 A 的函数地址交给 B,B 内部“回头”去调用 A。这就是“回调”。

如果你还是不理解,那么我先在这里埋一个伏笔:对于我来说,回调函数虽然从英文翻译过来回调不太好理解,但是用过函数传参就能直接上手回调函数,它的用法就是将函数作为参数进行传递,传到对方函数中,类比变量一样被调用,这!就是回调函数,还是很好理解吧,后面来几个示例就能深入理解和上手啦。

2. 使用场景

回调函数常用于以下几种情况:

  1. 事件驱动:如网络库 muduo 等库中,收到数据时触发回调函数。
  2. 异步编程:任务完成后调用回调报告结果。
  3. 通用算法:如 qsort() 让用户自定义排序比较规则。
  4. 钩子机制:插件式扩展、信号槽系统。
场景示例回调的含义
GUI 按钮点击事件用户点击按钮后,系统调用你的“回调函数”处理事件
网络 I/O有数据到达时,框架调用注册好的“onMessage”函数
排序算法排序逻辑由库提供,比较规则由你提供的“比较回调函数”定义

当然,回调函数的使用场景远不止于此!

2. 回调函数的使用

1. 函数指针定义

1
2
3
返回类型 (*函数指针名)(参数列表);
// 注意:函数名 ≠ 函数指针,但是实际使用中,直接传递函数名会自动退化为函数指针(等价于 &函数名),所以有:
返回类型 (函数名)(参数列表);

2. 基本使用(C 风格)

1. 代码示例 1

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
// 无参无值的回调函数:
#include <iostream>
using namespace std;

// 无参无值的回调函数
void print()
{
cout << "Hello, world!" << endl;
}

void test(int x, void (*callback)())
{
for (int i = 0; i < x; i++)
{
callback();
}
}

int main()
{
test(3, print);
// 输出:
//Hello, world!
//Hello, world!
//Hello, world!
return 0;
}

在这个代码中,print 函数就是回调函数,它被当作参数在 test 函数中进行传递,注意传参的写法:返回值 (*函数指针)(参数列表),只是这里无参无返回值,然后回调函数 print 在 for 循环中的 callback(); 被调用。

2. 代码示例 2

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
// 有参有返回值的回调函数(add、sub、mul、divi 都是回调函数)
#include <iostream>
using namespace std;

int add(int a, int b)
{
return a + b;
}

int sub(int a, int b)
{
return a - b;
}

int mul(int a, int b)
{
return a * b;
}

int divi(int a, int b)
{
if (b == 0)
{
cout << "除 0 错误!" << endl;
return -1;
}

return a / b;
}

void calculate(int a, int b, int (*callback)(int x, int y))
{
cout << "开始计算" << a << "和" << b << "的运算结果:" << endl;
cout << "结果为:" << callback(a, b) << endl;
}

int main()
{
calculate(10, 5, add);
calculate(10, 5, sub);
calculate(10, 5, mul);
calculate(10, 5, divi);
calculate(10, 0, divi);
//输出:
//开始计算10和5的运算结果:
//结果为:15
// 开始计算10和5的运算结果 :
//结果为:5
// 开始计算10和5的运算结果 :
//结果为:50
// 开始计算10和5的运算结果 :
//结果为:2
// 开始计算10和0的运算结果 :
//除 0 错误!
// 结果为: - 1
return 0;
}

在这个代码中,加减乘除 4 个函数都是回调函数,在 calculate 函数中当作参数进行传递,注意此时传参写法:int (*callback)(int x, int y),指明了返回值、函数指针及其参数,由 callback(a, b) 执行回调逻辑,即每个回调函数自己内部的代码逻辑。

上面的代码虽然简单,但是也差不多能看出来回调函数用处的强大,回调函数有着很多高级用法,本文暂不涉及,用的多了,和其他语法、库、容器等搭配使用才能领略回调的强大!

3. 取别名

不知道大家初看上面的代码感觉咋样,反正我觉得不那么好看/好辩认,所以通常会用到取别名,回调函数取别名的方法主要用 typedeusing(C++11 及其往后),先介绍一下 typedef 和回调函数搭配的语法:

1
2
typedef 返回值类型 (*类型名)(参数列表);
// typedef 旧名字 新名字 // 适用简单类型(也是常见用法)

1. typedef 为回调函数取别名

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
#include <iostream>
#include <string>
using namespace std;
// 定义一个函数指针类型:返回 string,参数 (int, string)
// 格式:typedef 返回值类型 (*类型名)(参数列表)
typedef string(*callback)(int, string);

// 回调函数:把字符串 s 拼接 x 次
string append(int x, string s)
{
string res;
for (int i = 0; i < x; ++i)
{
res += s; // 拼接 x 次
}
return res;
}

// 测试函数:接收字符串 + 回调函数 + 次数
void test(string s, int x, callback cb)
{
string ret = s + cb(x, "World");// 调用回调函数,得到拼接结果

cout << ret << endl;
}

int main()
{
test("Hello", 3, append);
// 输出:HelloWorldWorldWorld
return 0;
}

如果是初学者可能会对这个 typedef 的用法比较疑惑,没关系,我来解答:typedef 的语法本质是 “先写一个该类型的变量声明,再把变量名换成新类型名”。typedef 旧名字 新名字 这种用法应该比较常见的,但这种用法仅只适用于 简单类型(比如 typedef int MyInt;),但对于 复杂类型,比如指针、数组、函数指针就会存在一点区别:

  1. 简单类型: 想给 int 起个别名 MyInt 会用到 typedef int MyInt,此时 MyIntint 的别名。
  2. 指针类型: 想给 int 起个别名 IntPtr 会用到 typedef int* IntPtr,此时 IntPtrint 的别名。
  3. 函数指针类型: 想给 指向 void (int) 函数的指针 起个别名 callback: 先写 定义一个这种指针的变量 cbvoid (*cb)(int);,这里的 cb 是一个指针,指向 参数为 int、返回 void 的函数,加 typedef,把变量名 cb 换成新类型名 callback 就得到了 typedef void (* callback)(int);,此时 callbackvoid (*)(int) 这个函数指针类型的别名。
  4. ……(更多复杂类型)

void (*callback)(int) 它表示:callback 是一个 指向函数的指针,该函数 返回类型为 void,并且 参数列表为 (int)。可以用自然语言读成:callback 是一个指向接受一个 int 参数、返回 void 的函数的指针。所以,当你再次遇到它样子变种时,知道怎么看了吧。

注意括号的作用: void *callback(int) 是完全不同的意思(它表示“返回 void* 的函数”),只有加括号 void (*callback)(int) 才表示“指向函数的指针”!

2. using 为回调函数取别名

语法格式(C++11 及其以后):

1
using 别名 = 返回值类型 (*)(参数列表);
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
#include <iostream>
#include <string>
using namespace std;
// 定义一个函数指针类型:返回 string,参数 (int, string)
// 格式:using 别名 = 返回值类型 (*)(参数列表);
using callback = string (*)(int, string);

// 回调函数:把字符串 s 拼接 x 次
string append(int x, string s)
{
string res;
for (int i = 0; i < x; ++i)
{
res += s; // 拼接 x 次
}
return res;
}

// 测试函数:接收字符串 + 回调函数 + 次数
void test(string s, int x, callback cb)
{
string ret = s + cb(x, "World");// 调用回调函数,得到拼接结果
cout << ret << endl;
}

int main()
{
test("Hello", 3, append);
// 输出:HelloWorldWorldWorld

return 0;
}

我觉得这里应该什么疑问吧,按照格式套就行。

3. 模板支持(为什么推荐使用 using?)

先来看看下面 2 个代码:

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
81
82
83
84
85
86
87
88
89
90
91
// 代码1:
#include <iostream>
#include <string>
using namespace std;
//C++11 引入using后,支持直接将using与template结合,定义 “模板类型别名”,无需嵌套类,使用时也无需额外语法。
//实现步骤:
//直接用 “template <模板参数> using 别名 = 目标类型” 的语法,一步定义模板化的类型别名。
//使用时,直接用 “别名<具体类型>” 调用,和普通类型别名的使用逻辑一致。
template <typename T>
using callback = void (*)(T); // 模板化回调类型

void printI(int x)
{
cout << "Int: " << x << endl;
}

void printS(string s)
{
cout << "String: " << s << endl;
}

void printD(double d)
{
cout << "Double: " << d << endl;
}

int main()
{
// 多种写法:
callback<int> intCb = printI;
callback<string> strCb(printS); // 用构造方式初始化(函数指针的初始化)
callback<double> doubleCb = &printD; // 与原写法等价,&可省略
// auto ……也可以

intCb(100); // 输出:Int: 100
(*strCb)("test"); // 输出:String: test
doubleCb(3.14); // 输出:Double: 3.14

return 0;
}



// 代码2:
#include <iostream>
#include <string>
using namespace std;
//typedef本身不能直接跟模板参数结合,但可以把它放在一个类模板内部,通过类模板的参数间接实现 “模板化的类型别名”。
//实现步骤:
//定义一个类模板,在类内部用typedef定义目标类型别名。
//使用时,必须通过 “类模板名<具体类型>::type” 的语法调用,不能直接用类模板名简化。
// 用类模板嵌套 typedef 实现模板化回调类型(间接支持模板)
template <typename T>
struct callback
{
typedef void (*Type)(T); // 在类内部用 typedef 定义函数指针类型
};

void printI(int x)
{
cout << "Int: " << x << endl;
}

void printS(string s)
{
cout << "String: " << s << endl;
}

void printD(double d)
{
cout << "Double: " << d << endl;
}

int main()
{
// 必须通过 "类模板名<类型>::Type" 访问 typedef 定义的类型
callback<int>::Type intCb = printI;
callback<string>::Type strCb(printS); // 构造方式初始化
callback<double>::Type doubleCb = &printD; // & 可省略

intCb(100); // 输出:Int: 100
(*strCb)("test"); // 输出:String: test
doubleCb(3.14); // 输出:Double: 3.14

return 0;
}

// 观察上面2段代码:
// typedef 对模板支持较弱,需要依赖类模板嵌套,使用繁琐。
// using是 C++11 为解决这个问题设计的方案,能直接定义模板类型别名,在模板场景下(包括模板化回调函数)完全优于typedef。
// 实际开发中,只要环境支持 C++11 及以上,涉及模板化的类型别名(如通用回调、容器别名),都应该优先用using。

相信看完上面的代码,在回调函数和模板同时存在时,你再也不想用 typedef 了,还是 using 来的香,当遇到更复杂的场景,你第一个想到的是 typedef 还是 using 呢?言归正传,即使在非回调函数的场景中,我们始终推荐使用 using

4. std::function —— 通用的函数包装器

C++11 及以后标准中,用 std::functionstd::bind、Lambda 等来封装、传递和调用回调函数。注意:它能存储:普通函数、Lambda 表达式、成员函数、仿函数对象等,它让“函数”也能像普通变量一样传来传去,用于回调最方便,这一点在后面会遇到。

1. 基本语法结构

1
2
#include <functional>
std::function<返回类型(参数类型列表)> 变量名;

示例:

1
function<void(int)> cb;   // 可以存储任何“接收int、返回void”的可调用对象

2. 代码示例 1(入门)

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
#include <iostream>
#include <string>
#include <functional>
#include <sstream>
using namespace std;

void print1()
{
cout << "Hello, World!" << endl;
}
void test1(int n, function<void()> callback)
{
for (int i = 0; i < n; i++)
{
callback();
}
}


void print2(string s)
{
cout << s << endl;
}
void test2(int n, function<void(string)> callback)
{
for (int i = 0; i < n; i++)
{
callback("hello, callback!");
}
}


void test3(int n, function<void(string)> callback)
{
for (int i = 0; i < n; i++)
{
stringstream ss;
ss << "这是第 " << i << " 次调用";
callback(ss.str());
}
}


int main()
{
test1(3, print1);
test2(3, print2);
test3(3, print2);
return 0;
}

如果上面传统 C 风格的回调函数会用了,那么这里了解完 std::function 的语法格式,代码其实很浅显易懂了,锻炼一下不要注释能不能理解,真不是我懒得不想写注释 🤪。

3. 代码示例 2(进阶)

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// 示例1:
#include <iostream>
#include <functional>
using namespace std;

void print(int x)
{
cout << "普通函数: " << x << endl;
}

void test(int n, function<void(int)> cb)
{
for (int i = 0; i < n; ++i)
{
cb(i);
}
}

int main()
{
test(3, print); // 直接传函数名
}




// 示例2:
#include <iostream>
#include <functional>
using namespace std;
// 语法格式:using 别名 = std::function<返回值类型(参数类型...)>;
using func_t = function<void(int)>;

void test(int n, func_t cb)
{
for (int i = 0; i < n; ++i)
{
cb(i); // 调用回调函数,这个 i 会传递给 lambda 表达式,即下面的 int x 部分
}
}

int main()
{
// 理解:将 lambda 表达式作为参数传递给 test 函数,test 函数将 lambda 表达式作为回调函数调用。
// 可以理解为:这里的 lambda 表达式就是回调函数。
// lambda 表达式拿到上面传过来的 i 值,然后输出。
test(3, [](int x) {
cout << "Lambda 回调: " << x << endl;
});
}




// 示例3:
// std::function 的本质是一个 “函数包装器”,可以存储任何可调用对象(函数、lambda、函数对象等)。
// 它内部会管理这些可调用对象的拷贝或引用,就像一个 “函数指针的升级版”,但更灵活。
#include <functional>
#include <iostream>
using namespace std;
using Callback = function<void(int)>;

class Server
{
// 注意:_onMessage 就像一个 “函数容器”(本身是 std::function 类型,专门用来存放函数),初始时这个容器是空的。
// 可以理解成 _onMessage 是一个回调函数类型,初始是空的,不包含任何逻辑
Callback _onMessage;
public:
void set(Callback cb)
{
_onMessage = cb; // 这里就会将 lambda 表达式(的逻辑)拷贝给 _onMessage,从而拥有和下面lambda一样的效果
}

void simulateMessage(int data)
{
if (_onMessage) // 初始_onMessage是空的,所以这里不会执行,后续拥有lambda一样的逻辑效果才会进入
{
_onMessage(data); // 走到这里才会将传进来的42导向回调逻辑,即_onMessage(lambda的逻辑)
}
}
};

int main()
{
Server s;

// 注册回调
// lambda[](int x) { ... } 本质上是一个 “匿名函数”,它有明确的参数(int x)和返回值(void),
// 正好匹配 Callback 类型(function<void(int)>)。
// set 方法调用时,这个 lambda 会被拷贝到 _onMessage 中(std::function 会负责存储这个拷贝)。
s.set([](int x) {
cout << "收到消息:" << x << endl;
});

// 模拟事件触发
s.simulateMessage(42); // 当把42传给 _onMessage 时,_onMessage拥有和lambda一样的效果
}

这里就推荐以后直接用 using 了,std::function 搭配的语法格式:using 别名 = std::function<返回值类型(参数类型...)>; 需要注意一下。 示例 3 的代码可能需要细细品味一下……

4. std::bind 和占位符的使用

std::bind 的本质是:把一个函数和它的部分参数“提前绑定”起来,生成一个新的可调用对象,其语法结构是:

1
2
3
auto 新函数 = bind(函数地址, 参数1, 参数2, ...);
// placeholders::_1, placeholders::_2, placeholders::_3, …… 表示占位符,对应该函数未来的参数。
// 返回一个新的可调用对象,可以存到 std::function 里。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders; // 可打开命名空间简写,但通常不推荐,可读性差!

void show(int a, int b)
{
cout << a << " " << b << endl;
}

int main()
{
auto f = bind(show, _1, 100); // _1 表示占位,将来会传给第1个参数
f(10); // 等价于 show(10, 100),输出:10 100
}

占位符 _1_2 的意义:std::bind 可以“延后传入”某些参数。

1
2
auto f = bind(show, _2, _1);
f(10, 20); // 会调用 show(20, 10)

换句话说,_1, _2 只是告诉编译器:以后当我调用这个函数时,把第 1 个、2 个实参放到对应位置。这似乎很简单,没什么难的吧,下面要介绍 2 个注意点:bind 的本质是 “提前绑定部分参数,生成一个新的函数对象”,这个新函数对象的参数数量是固定的 —— 等于你在 bind 时使用的占位符个数 ,所以:bind 生成的函数对象必须在一次调用中补全所有未绑定的占位符,无法分多条语句逐个补充,只要有占位符未被赋值,就无法触发函数执行。

  • 只能用一条语句全部补充: 意思是 bind 中存在占位符导致参数“不完整”,后续 只能用一条语句全部补充完整
  • 全部补充完毕才会执行回调/只要有缺失就永远不会回调: 意思是只有当 bind 的参数真正完整的那一条语句才会执行回调函数,如果不完整,参数缺失(占位符没被填补),那么回调函数就永远不会生效、永远不会被调用!

再来看一小段代码巩固一下吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <functional>
using namespace std;
//using namespace std::placeholders;

void show(int a, int b, int c)
{
cout << a << " " << b << " " << c << endl;
}

int main()
{
auto f1 = bind(show, std::placeholders::_1, 20, 30); // 绑定两个参数
f1(10); // show(10,20,30)

auto f2 = bind(show, placeholders::_2, placeholders::_1, 30); // 绑定三个参数(注意这里的顺序!)
// f2(10); // 报错!
f2(10, 20); // show(20,10,30)
// 补充的顺序默认就是第一个,第二个,第三个...第一个参数会补充给placeholders::_1,第二个参数会补充给placeholders::_2,以此类推

auto f3 = bind(show, 10, 20, 30); // 没有占位符
f3(); // show(10,20,30)
}

5. bind 的返回值是什么?

std::bind 会返回一个 可调用对象,这个对象的类型是编译器自动生成的匿名类型(类似 lambda 表达式的类型),因此无法直接用具体的类型名声明(比如不能写成 std::bind_type f = ...)。

1. 万能 auto

auto 是个好东西,我不关心,直接就无脑 auto 就行了:

1
2
3
4
void func(int a, int b) {}

auto f = std::bind(func, 1, std::placeholders::_1);
// f的类型是匿名的,只能用auto接收
2. 显式指定类型的方法:用 std::function 包装

虽然 bind 的返回值类型匿名,但可以根据 原函数的签名和绑定后的参数要求,用 std::function 显式指定类型。具体规则是:std::function 的模板参数 = 绑定后需要传入的参数类型 + 返回值类型

示例 1:绑定后需要传入 1 个参数,返回值为 void。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <functional>
using namespace std;

void show(int a, int b)
{
cout << a << " " << b << endl;
}

int main()
{
// 绑定规则:show的第1个参数固定为100,第2个参数由调用时传入(_1)
// 绑定后,调用f时需要传入1个int,返回值为void → 对应function<void(int)>
function<void(int)> f = bind(show, 100, placeholders::_1);
f(200); // 等价于show(100, 200)
return 0;
}

示例 2:绑定后不需要传入参数,返回值为 int。

1
2
3
4
5
6
7
8
9
10
11
12
int add(int a, int b)
{
return a + b;
}

int main()
{
// 绑定规则:a=10,b=20,调用时不需要传参,返回int → 对应function<int()>
function<int()> f = bind(add, 10, 20);
cout << f() << endl; // 等价于add(10, 20) → 输出30
return 0;
}

示例 3:绑定后需要传入 2 个参数,返回值为 bool

1
2
3
4
5
6
7
8
9
10
11
12
13
bool compare(int a, int b, int c)
{
return (a + b) > c;
}

int main()
{
// 绑定规则:c固定为100,a和b由调用时传入(_1和_2)
// 调用时需要传入2个int,返回bool → 对应function<bool(int, int)>
function<bool(int, int)> f = bind(compare, placeholders::_1, placeholders::_2, 100);
cout << f(60, 50) << endl; // 等价于compare(60,50,100) → 110>100 → 输出1
return 0;
}
3. 如何确定 std::function 的类型?

只需明确一个问题:调用 bind 生成的函数对象时,需要传入几个参数?每个参数的类型是什么?返回值类型是什么?

步骤拆解:

  1. 确定原函数的返回值类型(比如 voidintbool)。
  2. 统计 bind 中使用的占位符数量(_1_2…),这就是调用时需要传入的参数个数。
  3. 占位符的类型对应原函数中未被固定的参数类型(比如 _1 对应原函数中第一个未绑定的参数类型)。
  4. 组合上述信息,得到 std::function<返回值类型(参数类型1, 参数类型2...)>

似乎来的不如 auto 香,所以实际怎么写,不用我多说了吧 🤪。

6. 什么时候必须显式指定类型?

  1. 作为类的成员变量:类成员变量不能用 auto 声明,必须显式指定类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MyClass
    {
    private:
    // 显式指定bind返回值的类型为function<void(int)>
    function<void(int)> callback;
    public:
    void setCallback()
    {
    callback = bind(show, 100, placeholders::_1);
    }
    };
  2. 作为函数的返回值:函数返回值不能用 auto(C++14 起允许,但显式类型更清晰)。

    1
    2
    3
    4
    5
    // 函数返回一个"需要传入int、返回void"的可调用对象
    function<void(int)> getCallback()
    {
    return bind(show, 100, placeholders::_1);
    }

5. 扩展

1. std::ref / std::cref —— 引用包装器

bindfunction 默认会“拷贝”参数。若想 传引用(例如防止对象被拷贝),要用 ref()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <functional>
using namespace std;
void print(int &x)
{
cout << "x = " << x << endl;
++x;
}

int main()
{
int a = 5;
auto f = bind(print, ref(a)); // 传引用,不是拷贝
f(); // x=5
f(); // x=6
}

若不用 ref(),bind 会拷贝一份 a,打印永远是 5。此外,ref() 比较简单就不多说了,直接当 & 这个引用用就行,其他章节也会有涉及。

2. std::mem_fn —— 将“成员函数指针”转成可调用对象

有时我们希望把 成员函数当普通函数一样用mem_fn 就是干这个的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>
using namespace std;

struct Foo
{
void show(int x) { cout << "x = " << x << endl; }
};

int main()
{
Foo f;
auto fn = mem_fn(&Foo::show); // 转成可调用对象
fn(f, 42); // 等价于 f.show(42)
}

适用于回调场景中成员函数的统一封装。

3. std::not_fn(C++17)—— 对逻辑函数取反

如果你有个函数/谓词返回 bool,not_fn 可以生成一个“逻辑相反”的版本。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <functional>
using namespace std;

bool isEven(int x) { return x % 2 == 0; }
int main()
{
auto isOdd = not_fn(isEven);
cout << isOdd(3) << endl; // 1
cout << isOdd(4) << endl; // 0
}

常用于 std::find_if_notstd::remove_if 等 STL 算法的逻辑反转。

4. 仿函数(函数对象)+ std::function 一起用

C++ 把“重载 operator() 的类”也当作函数来使用。

1
2
3
4
5
6
7
8
9
10
struct Add
{
int operator()(int a, int b) const { return a + b; }
};

int main()
{
function<int(int,int)> f = Add();
cout << f(1,2) << endl; // 3
}

bindlambda、仿函数等其实都能混合搭配出更高级的用法。

3. 小结

看到这里,你或多或少也该理解回调函数了吧,所以,还记得文章开头的伏笔吗?回调函数就是把函数当作参数传递使用,和传普通参数大同小异!简单回顾一下吧:

  • 函数指针的定义:返回类型 (*函数指针名)(参数列表);/返回类型 (函数名)(参数列表);
  • 取别名推荐使用:using 别名 = 返回值类型 (*)(参数列表);,既因为 typedef 模板支持较弱,又因为语法糖 🍭。
  • C++11 库支持后推荐使用:std::function<返回类型(参数类型列表)> 变量名;
  • bind 和占位符:auto 新函数 = bind(函数地址, 参数1, 参数2, ...);placeholders::_1, placeholders::_2, placeholders::_3, ……