C++ 巩固进阶之三

大纲

C++ 11 的 function 类模板

概念介绍

function 是 C++ 11 引入的一个类模板,用于存储任何可以调用的目标(如普通函数、函数指针、函数对象、Lambda 表达式等),并通过统一的接口进行调用。它能够封装和管理函数,允许将函数作为对象传递和存储,位于 <functional> 头文件中,常用于回调和高阶函数。

  • 使用说明

    • 用函数类型实例化 function 类模板
    • 通过 function 类模板调用 operator() 函数的时候,需要根据函数类型传入相应的参数
  • 使用特点

    • 通过类型擦除技术存储任意可调用对象(普通函数、Lambda 表达式、仿函数、成员函数指针等),无需关心具体类型,仅需关注调用签名‌
    • 编译时会检查参数和返回类型是否匹配,避免了传统函数指针的类型不安全问题‌
  • 使用场景

    • 事件回调
    • 作为函数参数传递可调用对象
    • 存储 Lambda 表达式

对比说明

C 语言中的函数指针与 C++ 的 function 类模板的对比如下:

特性函数指针 (C 语言)function 类模板 (C++)
灵活性只能指向具有匹配签名的函数可以封装多种类型的可调用对象 (函数、Lambda、函数对象)
状态管理不支持状态封装支持状态封装(如 Lambda 表达式和函数对象)
类型安全不提供额外的类型安全提供类型安全检查
性能开销较低可能有较高的内存和性能开销
多态性不支持多态支持多态

C 语言中的函数指针与 C++ 的 function 类模板的区别如下:

  • 基本概念

    • 函数指针(C 语言)
      • 函数指针是一个变量,用于存储函数的地址。通过该指针,可以间接调用对应的函数。
      • C 语言中的函数指针需要显式指定函数签名(返回值类型和参数类型)。
    • function 类模板(C++):
      • function 是 C++ 11 引入的类模板,提供了一种通用的、类型安全的方式来存储、传递和调用可调用对象(如普通函数、函数指针、Lambda 表达式、函数对象等)。
      • 它可以封装多种不同类型的可调用对象,并且允许它们通过统一的接口被调用。
  • 灵活性和类型支持

    • 函数指针(C 语言):
      • 函数指针只能指向具有相同函数签名(参数类型和返回类型)的函数。
      • 不支持封装函数对象、Lambda 表达式等其他类型的可调用对象。
    • function 类模板(C++):
      • function 支持多种类型的可调用对象,包括普通函数、函数指针、Lambda 表达式、函数对象等。
      • 它的模板参数可以适配任意函数签名,支持更加灵活的调用。
      • 还支持捕获外部状态的 Lambda 表达式,或者包含状态的函数对象。
  • 状态管理

    • 函数指针(C 语言):
      • 函数指针无法封装额外的状态信息,指向的仅仅是一个函数。
      • 如果需要管理状态(如在函数调用前后执行某些操作),必须依赖外部的代码来实现。
    • function 类模板(C++):
      • function 可以封装状态信息。例如,Lambda 表达式和函数对象可以拥有成员变量和成员函数,允许在调用时使用封装的状态。
      • 这种能力使得 function 在处理回调和事件处理时更具优势。
  • 类型安全

    • 函数指针(C 语言):
      • 函数指针本身类型是静态的,编译时要求函数签名必须匹配,但它不提供额外的类型安全检查,错误的使用可能导致未定义行为。
    • function 类模板(C++):
      • function 是类型安全的,编译时会检查存储的可调用对象是否与声明的函数签名一致。
      • C++ 编译器提供了类型安全的保证,避免了函数签名不匹配带来的错误。
  • 内存管理和性能

    • 函数指针(C 语言):
      • 函数指针直接存储函数的地址,不涉及额外的内存分配,因此它的性能开销较小。
      • 由于没有额外的封装,也没有多态或状态的管理,性能上较为高效。
    • function 类模板(C++):
      • function 需要进行额外的内存分配和类型擦除(Type Erasure)。对于 Lambda 表达式和函数对象,它会封装一个通用的接口,这可能带来额外的性能开销。
      • 但是,它的灵活性和类型安全是 function 的优势。
  • 多态和扩展性

    • 函数指针(C 语言):
      • 函数指针没有多态性。它们只是简单地指向某个函数,无法支持运行时多态。
    • function 类模板(C++):
      • function 支持通过函数对象、Lambda 表达式和虚拟函数等方式实现运行时多态,提供了更大的灵活性。
      • 它可以封装复杂的行为,例如结合面向对象编程中的继承和多态来实现不同的回调机制。

使用案例

案例代码一

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
#include <iostream>
#include <functional>
#include <string>

using namespace std;

void hello1() {
cout << "hello world" << endl;
}

void hello2(string str) {
cout << "hello " << str << endl;
}

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

int main() {
// function 函数对象类型
function<void()> function1 = hello1;
function1();

function<void(string)> function2 = hello2;
function2("peter");

function<int(int, int)> function3 = sum;
int total = function3(1, 2);
cout << "total = " << total << endl;

// function 函数对象类型 + Lambda 表达式
function<int(int, int)> function4 = [](int a, int b) {return a + b; };
int total2 = function4(3, 5);
cout << "total2 = " << total2 << endl;

return 0;
}

程序运行输出的结果如下:

1
2
3
4
hello world
hello peter
total = 3
total2 = 8

案例代码二

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 <functional>
#include <limits>
#include <map>

using namespace std;

class Test {

public:

void hello(string str) {
cout << "hello " << str << endl;
}

};

int main() {
// 通过 function 函数对象类型,调用类的成员函数
Test t;
function<void(Test*, string)> function1 = &Test::hello;
function1(&t, "peter");
function1(&Test(), "peter"); // 或者使用临时变量

return 0;
}

程序运行输出的结果如下:

1
2
hello peter
hello peter

案例代码三

使用 function 函数对象类型来实现图书管理系统的菜单列表选择功能。

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
#include <iostream>
#include <functional>
#include <string>
#include <map>

using namespace std;

void doShowAllBooks() {
cout << "查看所有书籍" << endl;
};

void doBorrowBook() {
cout << "借书" << endl;
};

void doBackBook() {
cout << "还书" << endl;
}

void doQueryBook() {
cout << "查询书籍" << endl;
}

void doLoginOut() {
cout << "注销" << endl;
}

int main() {
int choice = 0;

map<int, function<void()>> actionMap;
actionMap.insert({ 1, doShowAllBooks });
actionMap.insert({ 2, doBorrowBook });
actionMap.insert({ 3, doBackBook });
actionMap.insert({ 4, doQueryBook });
actionMap.insert({ 5, doLoginOut });

for (;;) {
cout << "\n-------------------" << endl;
cout << "1. 查看所有书籍" << endl;
cout << "2. 借书" << endl;
cout << "3. 还书" << endl;
cout << "4. 查询书籍" << endl;
cout << "5. 注销" << endl;
cout << "-------------------" << endl;
cout << "请选择: ";

// 检测输入是否合法
if (!(cin >> choice)) {
cout << "输入数字无效,请重新输入!" << endl;
// 清除错误状态
cin.clear();
// 丢弃错误输入
cin.ignore(numeric_limits<streamsize>::max(), '\n');
continue;
}

auto it = actionMap.find(choice);
if (it == actionMap.end()) {
cout << "输入数字无效,请重新输入!" << endl;
continue;
}

it->second();
}

return 0;
}

底层原理

案例代码一

模拟实现 function 类模板的功能,并调用拥有一个参数且不带返回值的函数。

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
#include <iostream>
#include <functional>
#include <string>

using namespace std;

// 定义类模板
// 这里的 Fty 代表任意函数签名,如 R(ARG)
template<typename Fty>
class myfunction {

};

/**
* @brief 模板类的特化,支持特定的函数签名 R(ARG)
* @tparam R 代表返回值类型
* @tparam ARG 代表参数类型
*/
template<typename R, typename ARG>
class myfunction<R(ARG)> {

public:
// 定义函数指针类型,指向 R(ARG) 类型的函数
using PFUNC = R(*)(ARG);

// 构造函数,接收一个函数指针,并存储起来
myfunction(PFUNC pfunc) : _pfunc(pfunc) {

}

/**
* @brief 重载小括号运算符,使对象可以像函数一样调用
* @param arg 传递给存储函数的参数
* @return 调用存储函数的返回值
*/
R operator()(ARG arg) {
return _pfunc(arg);
}

private:
// 存储函数指针
PFUNC _pfunc;

};

void hello(string str) {
cout << "hello " << str << endl;
}

int main() {
// 创建 myfunction 对象,并绑定 hello 函数
myfunction<void(string)> func1 = hello;

// 通过 func1 调用 hello
func1("peter");

return 0;
}

程序运行输出的结果如下:

1
hello peter

案例代码二

模拟实现 function 类模板的功能,并调用拥有两个参数且带返回值的函数。

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
#include <iostream>
#include <functional>
#include <string>

using namespace std;

// 定义类模板
// 这里的 Fty 代表任意函数签名,如 R(ARG1, ARG2)
template<typename Fty>
class myfunction {

};

/**
* @brief 模板类的特化,支持特定的函数签名 R(ARG1, ARG2)
* @tparam R 代表返回值类型
* @tparam ARG1 代表第一个参数类型
* @tparam ARG2 代表第二个参数类型
*/
template<typename R, typename ARG1, typename ARG2>
class myfunction<R(ARG1, ARG2)> {

public:
// 定义函数指针类型,指向 R(ARG1, ARG2) 类型的函数
using PFUNC = R(*)(ARG1, ARG2);

/**
* @brief 构造函数,接收一个函数指针,并存储起来
* @param pfunc 指向函数的指针
*/
myfunction(PFUNC pfunc) : _pfunc(pfunc) {

}

/**
* @brief 重载小括号运算符,使对象可以像函数一样调用
* @param arg1 传递给存储函数的第一个参数
* @param arg2 传递给存储函数的第二个参数
* @return 调用存储函数的返回值
*/
R operator()(ARG1 arg1, ARG2 arg2) {
return _pfunc(arg1, arg2);
}

private:
// 存储函数指针
PFUNC _pfunc;

};

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

int main() {
// 创建 myfunction 对象,并绑定 sum 函数
myfunction<int(int, int)> func1 = sum;

// 通过 func1 调用 sum
int result = func1(3, 5);
cout << "result: " << result << endl;

return 0;
}

程序运行输出的结果如下:

1
result: 8

案例代码三

模拟实现 function 类模板的功能,并调用拥有不同数量参数(可变参数列表)的函数,避免编写多个模板类的特例化。

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 <functional>
#include <string>

using namespace std;

// 定义类模板
// 这里的 Fty 代表任意函数签名,如 R(ARG...)
template<typename Fty>
class myfunction {

};

/**
* @brief myfunction 模板类的特化,支持可变长参数列表
* @tparam R 代表返回值类型
* @tparam ARG 代表参数列表(可以是任意数量的参数类型)
*/
template<typename R, typename ... ARG>
class myfunction<R(ARG...)> {

public:
// 定义函数指针类型,指向 R(ARG...) 类型的函数
using PFUNC = R(*)(ARG...);

/**
* @brief 构造函数,接收一个函数指针,并存储起来
* @param pfunc 指向函数的指针
*/
myfunction(PFUNC pfunc) : _pfunc(pfunc) {

}

/**
* @brief 重载小括号运算符,使对象可以像函数一样调用
* @param arg 传递给存储函数的参数列表
* @return 调用存储函数的返回值
*/
R operator()(ARG... arg) {
return _pfunc(arg...);
}

private:
// 存储函数指针
PFUNC _pfunc;

};

void hello(string str) {
cout << "hello " << str << endl;
}

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

int main() {
// 创建 myfunction 对象,并绑定 hello 函数
myfunction<void(string)> func1 = hello;

// 通过 func1 调用 hello
func1("peter");

// 创建 myfunction 对象,并绑定 sum函数
myfunction<int(int, int)> func2 = sum;

// 通过 func2 调用 sum
int result = func2(3, 4);
cout << "result: " << result << endl;

return 0;
}

程序运行输出的结果如下:

1
2
hello peter
result: 7

C++ 11 的 bind 绑定器

概念介绍

bind 的概念

bind 是 C++ 标准库 <functional> 头文件中的一个工具,用于创建可调用对象(Callable Object),它可以将函数与部分参数进行绑定,并返回一个新的可调用对象,方便后续调用。

  • 主要作用

    • 固定部分参数:可以预先绑定部分参数,简化后续调用的接口。
    • 调整参数顺序:可以自定义参数的传递顺序,使函数调用更加灵活。
    • 与标准库配合:可以与 functionthread、STL 算法等一起使用,提高程序的灵活性。
  • 关键点

    • bind 返回一个可调用对象(函数对象),类似于 Lambda 表达式。
    • placeholders::_1, placeholders::_2, ... 代表占位符(最多可以有 20 个),表示绑定器调用时需要提供的参数。
    • 适用于普通函数、类成员函数、仿函数(函数对象)等。
  • 使用场景

    • 配合 function 类模板使用,用于回调管理。
    • thread 配合,用于传递类成员函数。
    • 在 STL 算法中自定义比较或筛选规则。

提示

尽管 bind 功能强大,但在 C++ 11 之后,Lambda 表达式在大多数情况下更加直观,建议优先使用 Lambda 表达式。

bind1st 与 bind2nd 的概念

bind1stbind2nd 是 C++ 标准库 <functional> 头文件中的函数适配器,用于对二元(两个参数)函数对象进行绑定,使其成为一元(单参数)函数对象。这两个函数适配器用于旧版 C++(C++ 98 / C++ 03),在 C++ 11 及更新版本中已被 bind 取代。

  • bind1st:绑定二元(两个参数)函数对象的第一个参数,生成一个新的一元函数对象,该函数对象接收原函数的第二个参数作为输入。
  • bind2nd:绑定二元(两个参数)函数对象的第二个参数,生成一个新的一元函数对象,该函数对象接收原函数的第一个参数作为输入。

使用案例

bindfunction 基础使用

这里主要演示如何使用 C++ 11 中的 bindfunction

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
#include <iostream>
#include <functional>

using namespace std;

void hello(string str) {
cout << str << endl;
}

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

class Test {

public:
int sum(int a, int b) {
return a + b;
}

};

int main() {
// 例子一,绑定拥有一个参数的普通函数
bind(hello, "Hello Bind!")();

// 例子二,绑定拥有两个参数的普通函数
int result1 = bind(sum, 3, 5)();
cout << result1 << endl;

// 例子三,绑定类成员函数
int result2 = bind(&Test::sum, Test(), 5, 9)();
cout << result2 << endl;

// 例子四,使用参数占位符绑定(最多可以有20个参数占位符)
bind(hello, placeholders::_1)("Hello Rust!");

// 例子五,使用 function 类模板实现 bind 绑定器的复用
function<void(string)> func1 = bind(hello, placeholders::_1);
func1("Hello Python");
func1("Hello Golang");

// 例子六,使用参数占位符 + function 类模板进行绑定
function<int(int)> func2 = bind(sum, 6, placeholders::_1); // 绑定 6 作为第一个参数,第二个参数则手动传入
int result3 = func2(5); // 输出 11,相当于 sum(6, 5)
cout << result3 << endl;

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
Hello Bind!
8
14
Hello Rust!
Hello Python
Hello Golang
11

bindfunction 实现线程池

这里简单使用 C++ 11 的 bindfunction 来模拟实现线程池。特别注意,下述代码中的 ThreadPool::startPool() 会调用 join() 来阻塞等待所有子线程执行完成,即线程池运行一次后就会结束,不能重复使用,而且也没有使用到任务队列来优化线程池。

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
#include <iostream>
#include <functional>
#include <vector>
#include <thread>

using namespace std;

// 自定义线程
class Thread {

public:

Thread(function<void(int)> func, int id) : _func(func), _id(id) {

}

// 创建子线程
thread start() {
thread t(_func, _id);
return t;
}

private:
function<void(int)> _func;
int _id;
};

// 自定义线程池
class ThreadPool {

public:

ThreadPool() {

}

~ThreadPool() {
// 释放堆上的资源
for (int i = 0; i < _pool.size(); i++) {
delete _pool[i];
}
}

void startPool(int size) {
// 创建自定义的线程对象,并放入容器
for (int i = 0; i < size; i++) {
// 使用 bind 绑定器
_pool.push_back(new Thread(bind(&ThreadPool::runInThread, this, placeholders::_1), i));
}

// 创建子线程
for (int i = 0; i < size; i++) {
_handler.push_back(_pool[i]->start());
}

// 等待子线程执行完成
for (thread &t : _handler) {
t.join();
}
}

private:
vector<Thread *> _pool;
vector<thread> _handler;

// runInThread() 成员函数充当线程函数
void runInThread(int id) {
cout << "call runInThread! id: " << id << endl;
}
};

int main() {
ThreadPool pool;
pool.startPool(10);
return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
call runInThread! id: 0
call runInThread! id: 2
call runInThread! id: 5
call runInThread! id: 9
call runInThread! id: 6
call runInThread! id: 8
call runInThread! id: 1
call runInThread! id: 7
call runInThread! id: 4
call runInThread! id: 3

bind1stbind2nd 基础使用

这里主要演示如何使用旧版 C++ 提供的 bind1stbind2nd

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 <functional>
#include <algorithm>
#include <vector>
#include <ctime>

using namespace std;

// 函数模板
template<typename Container>
void showContainer(Container &con) {
// 遍历容器并打印元素
typename Container::iterator it = con.begin();
for (; it != con.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

void test01() {
vector<int> vec;

for (int i = 0; i < 10; i++) {
vec.push_back(rand() % 100 + 1);
}

showContainer(vec);

// 从小到大排序
sort(vec.begin(), vec.end());

showContainer(vec);

// 从大到小排序,greater 是二元函数对象
sort(vec.begin(), vec.end(), greater<int>());

showContainer(vec);
}

void test02() {
vector<int> vec;

for (int i = 0; i < 10; i++) {
vec.push_back(rand() % 100 + 1);
}

showContainer(vec);

// 从小到大排序
sort(vec.begin(), vec.end(), greater<int>());

showContainer(vec);

// 查找第一个小于 70 的元素

// 使用 bind1st 绑定 greater 的第一个参数,使其始终为 70,让 greater(70, x) 变成一元函数对象,等价于 f(x) = (70 > x)
vector<int>::iterator it = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70));

// 使用 bind2nd 绑定 less 的第二个参数,使其始终为 70,让 less(x, 70) 变成一元函数对象,等价于 f(x) = (x < 70)
// vector<int>::iterator it = find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70));

if (it != vec.end()) {
cout << "找到小于 70 的第一个元素:" << *it << endl;
} else {
cout << "未找到符合条件的元素" << endl;
}
}

int main() {
// 设置随机种子
srand(time(nullptr));

test01();

cout << "================================" << endl;

test02();

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
20 67 35 49 100 43 28 63 71 46 
20 28 35 43 46 49 63 67 71 100
100 71 67 63 49 46 43 35 28 20
================================
76 88 70 53 82 84 42 38 91 64
91 88 84 82 76 70 64 53 42 38
找到小于 70 的第一个元素:64

在 C++ 11 及更新版本中,bind1stbind2nd 已被 bind 取代,示例代码如下:

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
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>

using namespace std;

int main() {
vector<int> vec = {5, 2, 4, 1, 6};

// 查找第一个小于 3 的元素

// 使用 bind 绑定 greater 的第一个参数,等价于 f(x) = (3 > x)
function<bool(int)> predicate = bind(greater<int>(), 3, placeholders::_1);

// 使用 bind 绑定 less 的第二个参数,等价于 f(x) = (x < 3)
// function<bool(int)> predicate = bind(less<int>(), placeholders::_1, 3);

vector<int>::iterator it = find_if(vec.begin(), vec.end(), predicate);

if (it != vec.end()) {
cout << "找到小于 3 的第一个元素:" << *it << endl;
} else {
cout << "未找到符合条件的元素" << endl;
}

return 0;
}

程序运行输出的结果如下:

1
找到小于 3 的第一个元素:2

在 C++ 11 及更新版本中,可以使用 Lambda 表达式(更现代的 C++ 方式)来实现,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
vector<int> vec = {5, 2, 4, 1, 6};

// 查找第一个小于 3 的元素
auto it = find_if(vec.begin(), vec.end(), [](int x) { return x < 3; });

if (it != vec.end()) {
cout << "找到小于 3 的第一个元素:" << *it << endl;
} else {
cout << "未找到符合条件的元素" << endl;
}

return 0;
}

程序运行输出的结果如下:

1
找到小于 3 的第一个元素:2

bind1stbind2nd 底层原理

模拟实现 bind1st 的功能
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
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 函数对象(一元函数对象)
template<typename Compare, typename T>
class _my_bind1st {

public:
// 构造函数,比如:my_bind1st(greater<int>(), 5)
_my_bind1st(Compare comp, const T &first) : _comp(comp), _first(first) {

}

bool operator()(const T &second) {
// 底层调用的仍然是二元(两个参数)函数对象
return _comp(_first, second);
}

private:
Compare _comp;
T _first;

};

// 绑定器(模拟 bind1st 的实现)
// 绑定二元(两个参数)函数对象的第一个参数,生成一个新的一元函数对象,该函数对象接收原函数的第二个参数作为输入
template<typename Compare, typename T>
_my_bind1st<Compare, T> my_bind1st(Compare comp, const T &first) {
return _my_bind1st<Compare, T>(comp, first);
}

// 函数模板
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp) {
for (; first != last; ++first) {
if (comp(*first)) {
return first;
}
}
return last;
}

int main() {
vector<int> vec = {9, 6, 2, 4, 1};

// 查找第一个小于 5 的元素

// 使用 my_bind1st 绑定 greater 的第一个参数,使其始终为 5,让 greater(5, x) 变成一元函数对象,等价于 f(x) = (5 > x)
vector<int>::iterator it = my_find_if(vec.begin(), vec.end(), my_bind1st(greater<int>(), 5));

if (it != vec.end()) {
cout << "找到小于 5 的第一个元素:" << *it << endl;
} else {
cout << "未找到符合条件的元素" << endl;
}

return 0;
}

程序运行输出的结果如下:

1
找到小于 5 的第一个元素:2
模拟实现 bind2nd 的功能
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
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 函数对象(一元函数对象)
template<typename Compare, typename T>
class _my_bind2nd {

public:
// 构造函数,比如:my_bind2nd(less<int>(), 5)
_my_bind2nd(Compare comp, const T &second) : _comp(comp), _second(second) {

}

bool operator()(const T &first) {
// 底层调用的仍然是二元(两个参数)函数对象
return _comp(first, _second);
}

private:
Compare _comp;
T _second;

};

// 绑定器(模拟 bind2nd 的实现)
// 绑定二元(两个参数)函数对象的第二个参数,生成一个新的一元函数对象,该函数对象接收原函数的第一个参数作为输入
template<typename Compare, typename T>
_my_bind2nd<Compare, T> my_bind2nd(Compare comp, const T &second) {
return _my_bind2nd<Compare, T>(comp, second);
}

// 函数模板
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp) {
for (; first != last; ++first) {
if (comp(*first)) {
return first;
}
}
return last;
}

int main() {
vector<int> vec = {9, 6, 2, 4, 1};

// 查找第一个小于 5 的元素

// 使用 my_bind2nd 绑定 less 的第二个参数,使其始终为 5,让 less(x, 5) 变成一元函数对象,等价于 f(x) = (x < 5)
vector<int>::iterator it = my_find_if(vec.begin(), vec.end(), my_bind2nd(less<int>(), 5));

if (it != vec.end()) {
cout << "找到小于 5 的第一个元素:" << *it << endl;
} else {
cout << "未找到符合条件的元素" << endl;
}

return 0;
}

程序运行输出的结果如下:

1
找到小于 5 的第一个元素:2

C++ 11 的 Lambda 表达式

概念介绍

C++ 11 的 Lambda 表达式(Lambda Expressions)是一种用于定义匿名函数对象的简洁语法,使得代码更加简洁、灵活。其强大的变量捕获、类型推导和灵活性,使其在现代 C++ 开发中被广泛使用。

  • Lambda 表达式的语法

    • [capture](parameter_list) -> return_type {function_body},即 [捕获外部变量](形参列表) -> 返回类型 {操作代码}
    • capture(捕获列表):定义 Lambda 访问外部变量的方式(比如:按值或按引用)。
      • []:表示不捕获任何外部变量
      • [this]:表示捕获外部的 this 指针。
      • [=]:表示以传值的方式捕获外部的所有变量。
      • [&]:表示以传引用的方式捕获外部的所有变量。
      • [=, &a]:表示以传值的方式捕获外部的所有变量,但是 a 变量以传引用的方式捕获。
      • [a, b]:表示以传值的方式捕获外部的 ab 变量。
      • [a, &b]:表示以传值的方式捕获外部的 a 变量,以传引用的方式捕获外部的 b 变量。
    • parameter_list(形参列表):类似普通函数的参数。
    • return_type(返回类型):可省略,通常编译器可自动推导。
    • function_body(函数体):Lambda 实现的具体逻辑。
  • Lambda 表达式的适用场景

    • STL 算法的回调(如 std::for_each
    • 多线程编程(如 std::thread
    • 回调函数(如事件处理)
    • 临时函数对象(避免定义冗长的 struct

Lambda 表达式的本质

C++ Lambda 表达式的本质是匿名的函数对象(Functor),即 没有名字的类的实例。C++ 编译器在编译 Lambda 时,会自动生成一个匿名类,并在其中重载 operator () 函数,从而使其行为类似于函数。值得一提的是,C++ Lambda 不是单纯的 “匿名函数”,而是一个匿名类的实例,即匿名的函数对象。

使用案例

Lambda 的核心特性

  • 变量捕获(按值或按引用)
1
2
3
4
int x = 10, y = 20;
auto lambda1 = [x, &y]() -> void { y = x + y; }; // x 按值捕获,y 按引用捕获
lambda1();
cout << y; // 输出 30
  • 省略返回类型
1
2
auto add = [](int a, int b) -> int { return a + b; };
cout << add(3, 5); // 输出 8
  • 使用 mutable 关键字修改按值捕获的变量
1
2
3
4
int a = 5;
auto modify = [a]() mutable -> int { a *= 2; return a; };
cout << modify() << endl; // 输出 10
cout << a << endl; // 原变量 a 仍然是 5
  • 使用 function 进行存储
1
2
3
4
#include <functional>

function<int(int, int)> func = [](int a, int b) -> int { return a + b; };
cout << func(2, 3); // 输出 5
  • 作为 STL 算法的回调
1
2
3
4
5
#include <vector>
#include <algorithm>

vector<int> v = {1, 2, 3, 4, 5};
for_each(v.begin(), v.end(), [](int item) -> void { cout << item * 2 << " "; }); // 输出 2 4 6 8 10

提示

若希望在 Lambda 表达式中不指定返回类型,那么可以省略 -> return_type,比如:auto add = [](int a, int b) { return a + b; };

Lambda 的基础使用

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
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>

using namespace std;

int main() {
// 设置随机种子
srand(time(nullptr));

vector<int> vec;

for (int i = 0; i < 10; ++i) {
vec.push_back(rand() % 100 + 1);
}

// 遍历打印容器
for_each(vec.begin(), vec.end(), [](int item) -> void { cout << item << " "; });
cout << endl;

// 从大到小排序
sort(vec.begin(), vec.end(), [](int a, int b) -> bool { return a > b; });

// 遍历打印容器
for_each(vec.begin(), vec.end(), [](int item) -> void { cout << item << " "; });
cout << endl;

// 查找第一个小于 5 的元素
auto it = find_if(vec.begin(), vec.end(), [](int item) -> bool { return item < 50; });

// 或者简写
// auto it = find_if(vec.begin(), vec.end(), [](int item) { return item < 50; });

if (it != vec.end()) {
cout << "找到小于 50 的第一个元素:" << *it << endl;
} else {
cout << "未找到符合条件的元素" << endl;
}

return 0;
}

程序运行输出的结果如下:

1
2
3
91 18 37 28 55 86 50 47 66 52 
91 86 66 55 52 50 47 37 28 18
找到小于 50 的第一个元素:47

Lambda 的应用实践

这里主要介绍 Lambda 表达式的各种应用实战场景。

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
#include <iostream>
#include <memory>
#include <map>
#include <queue>
#include <functional>

using namespace std;

class Data {

public:
Data(int a, int b) : _a(a), _b(b) {

}

int getA() {
return _a;
}

int getB() {
return _b;
}

private:
int _a;
int _b;

};

void test01() {
// 使用 function 存储 Lambda 表达式
map<int, function<int(int, int)>> map;

map[0] = [](int a, int b) -> int { return a + b; };
map[1] = [](int a, int b) -> int { return a - b; };
map[2] = [](int a, int b) -> int { return a * b; };
map[3] = [](int a, int b) -> int { return a / b; };

cout << "30 + 15 = " << map[0](30, 15) << endl;
cout << "30 - 15 = " << map[1](30, 15) << endl;
cout << "30 * 15 = " << map[2](30, 15) << endl;
cout << "30 / 15 = " << map[3](30, 15) << endl;
}

void test02() {
// 智能指针自定义删除器
unique_ptr<FILE, function<void(FILE *)>> ptr1(fopen("data.txt", "w"), [](FILE *p) -> void {
fclose(p);
cout << "Closed File" << endl;
});
}

void test03() {
// 优先级队列自定义元素排序规则
using FUNC = function<bool(Data &, Data &)>;
priority_queue<Data, vector<Data>, FUNC> queue([](Data &data1, Data &data2) {
return data1.getA() < data2.getA();
});
queue.push(Data(10, 20));
queue.push(Data(18, 25));
queue.push(Data(15, 23));
queue.push(Data(19, 28));

while (!queue.empty()) {
Data data = queue.top();
queue.pop();
cout << "a = " << data.getA() << ", b = " << data.getB() << endl;
}
}

int main() {
test01();
cout << "----------------" << endl;
test02();
cout << "----------------" << endl;
test03();
cout << "----------------" << endl;
return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
30 + 15 = 45
30 - 15 = 15
30 * 15 = 450
30 / 15 = 2
----------------
Closed File
----------------
a = 19, b = 28
a = 18, b = 25
a = 15, b = 23
a = 10, b = 20
----------------

Lambda 的底层原理

C++ Lambda 表达式的本质是匿名的函数对象(Functor),即 没有名字的类的实例。C++ 编译器在编译 Lambda 时,会自动生成一个匿名类,并在其中重载 operator() 函数,从而使其行为类似于函数。值得一提的是,C++ Lambda 不是单纯的 “匿名函数”,而是一个匿名类的实例,即匿名的函数对象。

  • Lambda 是一个匿名类的实例

    • 编译器会为 Lambda 生成一个 匿名类,该类重载了 operator() 以实现调用操作。
    • 这个匿名类的实例(匿名函数对象)即 Lambda 本身。
  • Lambda 具有函数对象的性质

    • Lambda 可以像普通函数一样被调用。
    • 如果 Lambda 捕获了外部变量(如 auto func = [x](int y) -> int { return x + y; };),那么编译器会为该匿名类生成成员变量来存储 x,并在 operator() 函数中使用这些成员变量。
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <iostream>

using namespace std;

// 函数对象
template<typename T=void>
class MyLambda1 {

public:
MyLambda1() {

}

T operator()() {
cout << "Hello World" << endl;
}

};

void test01() {
auto func1 = []() -> void { cout << "Hello World" << endl; };
func1();

// 上面的 Lambda 表达式等价于以下代码
MyLambda1<> t1;
t1();
}

// 函数对象
template<typename T>
class MyLambda2 {

public:
MyLambda2() {

}

T operator()(int a, int b) const {
return a + b;
}

};

void test02() {
auto fun2 = [](int a, int b) -> int { return a + b; };
cout << fun2(3, 5) << endl;

// 上面的 Lambda 表达式等价于以下代码
MyLambda2<int> t2;
cout << t2(3, 5) << endl;
}

// 函数对象
template<typename T=void>
class MyLambda3 {

public:
MyLambda3(int a, int b) : _a(a), _b(b) {

}

void operator()() const {
// 交换两个变量的值
int temp = _a;
_a = _b;
_b = temp;
}

private:
mutable int _a;
mutable int _b;

};

void test03() {
int a = 10;
int b = 30;

// 按值传递,并没有真正交换两个变量的值
auto func3 = [a, b]() mutable -> void {
int temp = a;
a = b;
b = temp;
};
func3();
cout << "a = " << a << ", b = " << b << endl;

// 上面的 Lambda 表达式等价于以下代码
MyLambda3<int> t3(a, b); // 按值传递,并没有真正交换两个变量的值
t3();
cout << "a = " << a << ", b = " << b << endl;
}

// 函数对象
template<typename T=void>
class MyLambda4 {

public:
MyLambda4(int &a, int &b) : _a(a), _b(b) {

}

void operator()() const {
// 交换两个变量的值
int temp = _a;
_a = _b;
_b = temp;
}

private:
int &_a;
int &_b;

};

void test04() {
int a = 10;
int b = 30;

// 按引用传递,可以真正交换两个变量的值
auto func4 = [&a, &b]() mutable -> void {
int temp = a;
a = b;
b = temp;
};
func4();
cout << "a = " << a << ", b = " << b << endl;

// 上面的 Lambda 表达式等价于以下代码
MyLambda4<int> t4(a, b); // 按引用传递,可以真正交换两个变量的值
t4();
cout << "a = " << a << ", b = " << b << endl;
}

int main() {
test01();
cout << "----------------" << endl;
test02();
cout << "----------------" << endl;
test03();
cout << "----------------" << endl;
test04();
cout << "----------------" << endl;
return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
Hello World
Hello World
----------------
8
8
----------------
a = 10, b = 30
a = 10, b = 30
----------------
a = 30, b = 10
a = 10, b = 30
----------------

验证 Lambda 表达式是函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

using namespace std;

int main() {
auto lambda = [](int x) { return x * 2; };

// Lambda 是一个匿名类的实例
cout << std::boolalpha;
cout << "Lambda 是对象吗? " << boolalpha << is_object<decltype(lambda)>::value << '\n';

// Lambda 具有 operator() 函数
cout << "Lambda(5) = " << lambda(5) << '\n';

return 0;
}

程序运行输出的结果如下:

1
2
Lambda 是对象吗? true
Lambda(5) = 10