关于cpp的lambda匿名函数那些事
熟悉python
的同学都知道,python
中存在lambda匿名函数,这种函数往往只能完成简单的功能,常用于内嵌函数:如map()
、filter()
、sorted()
等高阶函数。举个例子:
# 使用 lambda 表达式
add = lambda x, y: x + y
# 调用 lambda 函数
print(add(5, 3)) # 输出 8
C++
中的匿名函数(也称为lambda表达式)是一种可以定义内联函数的方式,它不需要显式地命名函数名称。匿名函数常用于需要传递简单函数的场景,例如回调、算法或事件处理程序。C++
从 C++11
开始引入了 lambda表达式,并在后续版本中不断增强其功能。
1. 基本语法
C++
中lambda表达式的基本语法是:
[capture](parameters) -> return_type {function_body};
- capture:是捕获列表,指定lambda可以捕获的外部作用域的变量,有以下几种常见的捕获方式
- 按值捕获:通过按值拷贝的方式捕获外部的变量
[x]
:捕获外部变量x
的值[=]
:按值捕获所有在lambda中使用的外部变量
- 按引用捕获:通过引用访问外部变量,可以修改外部变量
[&x]
:按引用捕获变量 x[&]
:按引用捕获所有在 lambda 中使用的外部变量
- 混合捕获:可以按值和按引用混合捕获
[x, &y]
:按值捕获 x,按引用捕获 y
- 捕获所有变量:
[=]
:按值捕获所有外部变量。[&]
:按引用捕获所有外部变量。
- 按值捕获:通过按值拷贝的方式捕获外部的变量
- parameters:是参数列表,类似于普通函数的参数列表
- return_type:是返回值类型,可以不指定,编译器会自动进行推断
- function_body:是函数体
一个基本的使用示例:
#include <iostream>
int main() {
int a = 10;
int b = 20;
// 按值捕获
auto sum = [a, b]() {
return a + b;
};
std::cout << "Sum: " << sum() << std::endl; // 输出:Sum: 30
// 按引用捕获
auto increment = [&a]() {
a++;
};
increment();
std::cout << "Incremented a: " << a << std::endl; // 输出:Incremented a: 11
return 0;
}
2. 使用lambda表达式的常见场景
lambda表达式常用于传递给需要回调函数的库函数,如标准库中的算法函数,如std::for_each
、std::sort
等。
举个简单的例子:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9};
std::sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b; // 降序排序
});
for (int num : vec) {
std::cout << num << " ";
}
// 输出:9 5 4 3 1 1
return 0;
}
3. 为什么有了参数列表,还需要捕获列表呢?
有了参数列表,我们可以按照引用来进行参数传递,这些也能够达到捕获列表的目的,为什么还需要捕获列表呢?
捕获列表和参数列表在功能上有明显的区别:
- 捕获列表:
- 用于访问 lambda 定义时所在作用域中的变量,无需显式传递。
- 可以捕获变量的值(按值捕获)或引用(按引用捕获)。
- 允许 lambda 在不改变函数签名的情况下,访问外部变量。
- 适用于需要访问多个外部变量,或需要保持与外部变量的关系的场景。
- 参数列表:
- 用于定义 lambda 接受的输入参数,类似于普通函数的参数。
- 仅限于通过函数调用传递的参数,无法直接访问外部作用域的变量。
- 需要显式传递每个需要的变量,适用于那些只依赖于传入参数的场景。
使用lambda表达式可以简化函数签名和调用,当lambda需要访问多个外部变量的时候,使用参数列表会导致函数签名变得复杂,而且调用时需要显式传递所有相关变量,这不仅增加了代码的复杂性,还可能导致错误。
比如:使用std::for_each
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int a = 5;
int b = 10;
std::vector<int> nums = {1, 2, 3, 4, 5};
// 如果不使用捕获列表,需要显式传递 a 和 b
std::for_each(nums.begin(), nums.end(), [&](int x, int a_val, int b_val) {
std::cout << (x + a_val + b_val) << " ";
}, a, b); // 需要额外传递 a 和 b, 导致代码错误
return 0;
}
因为std::for_each
的标准签名是
template <class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f);
这里的 Function
是一个接受一个参数的函数或可调用对象(比如 lambda 表达式)。for_each
依次对范围 [first, last)
中的每个元素调用 f
,而 f
只能接受容器中元素作为唯一参数。
在你的代码中,[&](int x, int a_val, int b_val)
这个 lambda 接受三个参数,但 for_each
只能传递一个参数给它,即容器中的元素(x
)。因此,传递 a
和 b
在 for_each
调用之后是无效的,编译器会报错,告诉你 for_each
不能传递额外的参数。
如果你想在 lambda 表达式中使用外部变量 a
和 b
,你需要通过捕获列表捕获它们,而不是在 for_each
调用后传递它们。捕获列表可以让 lambda 表达式访问定义它时所在作用域中的变量,而不需要改变 lambda 的参数列表或传递额外的参数。如
std::for_each(nums.begin(), nums.end(), [&](int x) {
std::cout << (x * a + b) << " ";
});