第22章 函数对象与Lambda表达式

函数指针

函数指针的概念

函数指针是指向函数的指针,它可以存储函数的地址,并通过该地址调用函数。

函数指针的声明与使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明函数指针
int (*addPtr)(int, int);

// 定义函数
int add(int a, int b) {
return a + b;
}

// 赋值
addPtr = add;

// 调用
int result = addPtr(1, 2); // 结果为3

函数指针作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void process(int a, int b, int (*operation)(int, int)) {
int result = operation(a, b);
std::cout << "Result: " << result << std::endl;
}

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

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

// 使用
int main() {
process(10, 5, add); // 输出: Result: 15
process(10, 5, subtract); // 输出: Result: 5
return 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
int add(int a, int b) {
return a + b;
}

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

int (*getOperation(char op))(int, int) {
if (op == '+') {
return add;
} else if (op == '-') {
return subtract;
}
return nullptr;
}

// 使用
int main() {
auto operation = getOperation('+');
if (operation) {
std::cout << operation(10, 5) << std::endl; // 输出: 15
}
return 0;
}

函数指针的类型别名

使用typedefusing为函数指针类型创建别名,提高代码可读性:

1
2
3
4
5
6
7
8
// 使用typedef
typedef int (*Operation)(int, int);

// 使用using
using Operation = int (*)(int, int);

// 使用
Operation addPtr = add;

函数对象

函数对象的概念

函数对象(Function Object),也称为仿函数(Functor),是一种具有函数行为的对象。它是一个类的实例,该类重载了函数调用运算符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
class Add {
public:
int operator()(int a, int b) const {
return a + b;
}
};

class Subtract {
public:
int operator()(int a, int b) const {
return a - b;
}
};

// 使用
int main() {
Add add;
int result1 = add(10, 5); // 结果为15

Subtract subtract;
int result2 = subtract(10, 5); // 结果为5

return 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Add {
public:
int operator()(int a, int b) const {
return a + b;
}
};

class Subtract {
public:
int operator()(int a, int b) const {
return a - b;
}
};

void process(int a, int b, const Add& operation) {
int result = operation(a, b);
std::cout << "Result: " << result << std::endl;
}

void process(int a, int b, const Subtract& operation) {
int result = operation(a, b);
std::cout << "Result: " << result << std::endl;
}

// 更通用的版本,使用模板
template<typename Operation>
void process(int a, int b, Operation operation) {
int result = operation(a, b);
std::cout << "Result: " << result << std::endl;
}

// 使用
int main() {
Add add;
Subtract subtract;

process(10, 5, add); // 输出: Result: 15
process(10, 5, subtract); // 输出: Result: 5

return 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
27
class Counter {
public:
Counter() : count(0) {}

int operator()() {
return ++count;
}

int getCount() const {
return count;
}

private:
int count;
};

// 使用
int main() {
Counter counter;

std::cout << counter() << std::endl; // 输出: 1
std::cout << counter() << std::endl; // 输出: 2
std::cout << counter() << std::endl; // 输出: 3
std::cout << "Total: " << counter.getCount() << std::endl; // 输出: Total: 3

return 0;
}

标准库中的函数对象

C++标准库提供了一系列函数对象,定义在<functional>头文件中:

  1. 算术运算std::plus, std::minus, std::multiplies, std::divides, std::modulus, std::negate
  2. 比较运算std::equal_to, std::not_equal_to, std::greater, std::less, std::greater_equal, std::less_equal
  3. 逻辑运算std::logical_and, std::logical_or, std::logical_not
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <functional>

int main() {
// 算术运算
std::plus<int> add;
std::cout << add(10, 5) << std::endl; // 输出: 15

std::minus<int> subtract;
std::cout << subtract(10, 5) << std::endl; // 输出: 5

// 比较运算
std::greater<int> greater;
std::cout << greater(10, 5) << std::endl; // 输出: 1

std::less<int> less;
std::cout << less(10, 5) << std::endl; // 输出: 0

return 0;
}

Lambda表达式

Lambda表达式的概念

Lambda表达式是C++11引入的一种匿名函数的表达方式,它允许在需要函数的地方直接定义函数,而不需要单独声明函数。

Lambda表达式的语法

1
[capture](parameters) -> return_type { body }
  • capture:捕获列表,指定哪些外部变量可以在lambda体内使用
  • parameters:参数列表,与普通函数的参数列表类似
  • return_type:返回类型,可以省略,由编译器自动推导
  • body:函数体,包含要执行的代码

Lambda表达式的基本使用

1
2
3
4
5
6
7
8
9
10
11
// 基本用法
auto add = [](int a, int b) { return a + b; };
int result = add(10, 5); // 结果为15

// 带返回类型
auto subtract = [](int a, int b) -> int { return a - b; };
int result2 = subtract(10, 5); // 结果为5

// 无参数
auto hello = []() { std::cout << "Hello, Lambda!" << std::endl; };
hello(); // 输出: Hello, Lambda!

捕获列表

Lambda表达式可以通过捕获列表访问外部变量:

  1. 值捕获[var][=]
  2. 引用捕获[&var][&]
  3. 混合捕获[var, &other][=, &var][&, var]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 值捕获
int x = 10;
auto addX = [x](int y) { return x + y; };
x = 20;
int result = addX(5); // 结果为15,因为x是值捕获,捕获的是x的副本

// 引用捕获
int y = 10;
auto addY = [&y](int z) { return y + z; };
y = 20;
int result2 = addY(5); // 结果为25,因为y是引用捕获,捕获的是y的引用

// 全部值捕获
int a = 1, b = 2;
auto sum = [=]() { return a + b; };

// 全部引用捕获
auto sumRef = [&]() { return a + b; };

// 混合捕获
auto mixed = [a, &b]() { return a + b; };

Lambda表达式作为参数

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

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};

// 使用lambda表达式作为sort的比较函数
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 降序排序
});

// 使用lambda表达式作为for_each的操作函数
std::for_each(nums.begin(), nums.end(), [](int num) {
std::cout << num << " "; // 输出: 5 4 3 2 1
});
std::cout << std::endl;

return 0;
}

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

std::function<int(int, int)> getOperation(char op) {
if (op == '+') {
return [](int a, int b) { return a + b; };
} else if (op == '-') {
return [](int a, int b) { return a - b; };
} else if (op == '*') {
return [](int a, int b) { return a * b; };
} else if (op == '/') {
return [](int a, int b) { return a / b; };
}
return nullptr;
}

int main() {
auto add = getOperation('+');
std::cout << add(10, 5) << std::endl; // 输出: 15

auto multiply = getOperation('*');
std::cout << multiply(10, 5) << std::endl; // 输出: 50

return 0;
}

泛型Lambda表达式

C++14引入了泛型Lambda表达式,允许使用自动类型推导:

1
2
3
4
5
6
// 泛型lambda
auto add = [](auto a, auto b) { return a + b; };

int result1 = add(10, 5); // 结果为15
std::string result2 = add("Hello, ", "Lambda!"); // 结果为"Hello, Lambda!"
double result3 = add(3.14, 2.71); // 结果为5.85

可变Lambda表达式

默认情况下,值捕获的变量在Lambda体内是不可修改的。使用mutable关键字可以修改值捕获的变量:

1
2
3
4
5
6
7
8
9
int x = 10;
auto increment = [x]() mutable {
x++; // 修改捕获的x的副本
return x;
};

std::cout << increment() << std::endl; // 输出: 11
std::cout << increment() << std::endl; // 输出: 12
std::cout << x << std::endl; // 输出: 10,因为修改的是副本

Lambda表达式的捕获初始化

C++14引入了Lambda表达式的捕获初始化,允许在捕获列表中初始化捕获的变量:

1
2
3
4
5
6
7
8
int x = 10;

// 捕获初始化
auto add = [y = x + 5](int z) {
return y + z;
};

std::cout << add(5) << std::endl; // 输出: 20

函数对象、函数指针与Lambda表达式的比较

性能比较

  1. 函数指针:调用开销较小,但不支持状态
  2. 函数对象:调用开销与函数指针相当,支持状态
  3. Lambda表达式:编译时会被转换为函数对象,性能与函数对象相当

使用场景

  1. 函数指针:当需要一个简单的函数回调,且不需要状态时
  2. 函数对象:当需要一个可重用的、带状态的函数对象时
  3. Lambda表达式:当需要一个临时的、一次性的函数对象时

函数包装器

std::function

std::function是C++11引入的一种通用函数包装器,定义在<functional>头文件中,它可以包装:

  1. 普通函数
  2. 函数指针
  3. 成员函数
  4. 函数对象
  5. 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
#include <functional>

// 普通函数
int add(int a, int b) {
return a + b;
}

// 函数对象
class Multiply {
public:
int operator()(int a, int b) const {
return a * b;
}
};

int main() {
// 包装普通函数
std::function<int(int, int)> f1 = add;
std::cout << f1(10, 5) << std::endl; // 输出: 15

// 包装函数对象
Multiply multiply;
std::function<int(int, int)> f2 = multiply;
std::cout << f2(10, 5) << std::endl; // 输出: 50

// 包装lambda表达式
std::function<int(int, int)> f3 = [](int a, int b) { return a - b; };
std::cout << f3(10, 5) << std::endl; // 输出: 5

return 0;
}

std::bind

std::bind是C++11引入的一个函数适配器,定义在<functional>头文件中,它可以将函数与参数绑定,生成一个新的可调用对象:

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

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

int main() {
// 绑定前两个参数
auto add5 = std::bind(add, 5, std::placeholders::_1, std::placeholders::_2);
std::cout << add5(10, 15) << std::endl; // 输出: 30 (5 + 10 + 15)

// 绑定第一个和第三个参数
auto add10 = std::bind(add, std::placeholders::_1, 10, std::placeholders::_2);
std::cout << add10(5, 15) << std::endl; // 输出: 30 (5 + 10 + 15)

return 0;
}

应用示例

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

struct Person {
std::string name;
int age;
};

int main() {
std::vector<Person> people = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20}
};

// 按年龄排序
std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.age < b.age;
});

// 输出结果
for (const auto& person : people) {
std::cout << person.name << " (" << person.age << ")" << std::endl;
}
// 输出:
// Charlie (20)
// Alice (25)
// Bob (30)

return 0;
}

2. 查找

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

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 查找第一个大于5的元素
auto it = std::find_if(nums.begin(), nums.end(), [](int num) {
return num > 5;
});

if (it != nums.end()) {
std::cout << "First number greater than 5: " << *it << std::endl; // 输出: 6
}

return 0;
}

3. 转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <algorithm>
#include <vector>
#include <string>
#include <sstream>

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<std::string> strs(nums.size());

// 将整数转换为字符串
std::transform(nums.begin(), nums.end(), strs.begin(), [](int num) {
std::stringstream ss;
ss << num;
return ss.str();
});

// 输出结果
for (const auto& str : strs) {
std::cout << str << " "; // 输出: 1 2 3 4 5
}
std::cout << std::endl;

return 0;
}

4. 过滤

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

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> evenNums;

// 过滤出偶数
std::copy_if(nums.begin(), nums.end(), std::back_inserter(evenNums), [](int num) {
return num % 2 == 0;
});

// 输出结果
for (const auto& num : evenNums) {
std::cout << num << " "; // 输出: 2 4 6 8 10
}
std::cout << std::endl;

return 0;
}

5. 函数组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <functional>

// 函数组合
template<typename F, typename G>
auto compose(F f, G g) {
return [f, g](auto x) {
return f(g(x));
};
}

int main() {
auto add5 = [](int x) { return x + 5; };
auto multiply2 = [](int x) { return x * 2; };

// 组合函数: 先加5,再乘2
auto add5ThenMultiply2 = compose(multiply2, add5);
std::cout << add5ThenMultiply2(10) << std::endl; // 输出: 30

// 组合函数: 先乘2,再加5
auto multiply2ThenAdd5 = compose(add5, multiply2);
std::cout << multiply2ThenAdd5(10) << std::endl; // 输出: 25

return 0;
}

最佳实践

1. 优先使用Lambda表达式

对于一次性使用的小函数,优先使用Lambda表达式,它更加简洁、直观。

2. 合理使用捕获列表

  • 值捕获:当需要使用变量的当前值,且不希望变量的变化影响Lambda表达式时
  • 引用捕获:当需要使用变量的最新值,或变量较大时
  • 混合捕获:当需要值捕获某些变量,引用捕获其他变量时

3. 避免过度使用Lambda表达式

对于复杂的、需要重复使用的逻辑,应该定义为普通函数或函数对象,而不是Lambda表达式。

4. 注意Lambda表达式的生命周期

  • 引用捕获:确保被引用的变量在Lambda表达式的生命周期内有效
  • 值捕获:注意值捕获的变量的副本大小,避免捕获过大的对象

5. 合理使用std::function

当需要存储或传递不同类型的可调用对象时,使用std::function

总结

函数对象、函数指针和Lambda表达式是C++中实现回调和函数式编程的重要工具。它们各有优缺点,适用于不同的场景:

  • 函数指针:简单、直接,但不支持状态
  • 函数对象:支持状态,可重用性好,但需要显式定义类
  • Lambda表达式:简洁、直观,支持捕获外部变量,适用于一次性使用的场景

在现代C++中,Lambda表达式已经成为实现回调和函数式编程的首选工具,它结合了函数指针的简洁性和函数对象的灵活性。

通过本章的学习,读者应该掌握函数指针、函数对象和Lambda表达式的基本用法,能够在实际编程中合理选择和使用这些工具,提高代码的可读性和可维护性。