C++ Lambda 表达式允许我们定义匿名函数对象(functors),这些对象可以内联使用或作为参数传递。
Lambda 表达式是在 C++11 中引入的,用于以更方便、更简洁的方式创建匿名 functors。
它们之所以更方便,是因为我们不需要在单独的类或结构中重载 ()
运算符。
创建 C++ Lambda 表达式
一个基本的 lambda 表达式可以看起来像这样
auto greet = []() {
// lambda function body
};
这里,
[]
被称为 **lambda introducer(lambda 引入器)**,它表示 lambda 表达式的开始()
被称为 **parameter list(参数列表)**,它类似于普通函数的()
运算符
上面的代码等同于
void greet() {
// function body
}
现在,就像普通函数一样,我们可以简单地使用以下方式调用 lambda 表达式
greet();
注意:我们使用了 auto
关键字来自动推导 lambda 表达式的返回类型。
示例:C++ Lambda 函数
#include <iostream>
using namespace std;
int main() {
// create a lambda function that prints "Hello World!"
auto greet = []() {
cout << "Hello World!";
};
// call lambda function
greet();
return 0;
}
输出
Hello World!
在上面的示例中,我们创建了一个简单的程序,该程序使用 C++ lambda 表达式打印 Hello World!
。
首先,我们创建了 lambda 函数并将其赋给一个名为 greet 的变量。
auto greet = []() {
cout << "Hello World!";
};
然后,我们使用 greet 变量和 ()
运算符调用了 lambda 函数
// displays "Hello World!"
greet();
带参数的 C++ Lambda 函数
就像普通函数一样,lambda 表达式也可以接受参数。例如,
#include <iostream>
using namespace std;
int main() {
// lambda function that takes two integer
// parameters and displays their sum
auto add = [] (int a, int b) {
cout << "Sum = " << a + b;
};
// call the lambda function
add(100, 78);
return 0;
}
输出
Sum = 178
在上面的示例中,我们创建了一个 lambda 函数,该函数接受两个整数参数并显示它们的和。
auto add = [] (int a, int b) {
cout << "Sum = " << a + b;
};
这相当于
void add(int a, int b) {
cout << "Sum = " << a + b;
}
然后,我们通过传递两个整数参数来调用 lambda 函数
// returns 178
add(100, 78);
带返回类型的 C++ Lambda 函数
与普通函数一样,C++ lambda 表达式也可以具有返回类型。
编译器可以根据 return
语句隐式推导出 lambda 表达式的返回类型。
auto add = [] (int a, int b) {
// always returns an 'int'
return a + b;
};
在上述情况下,我们没有显式定义 lambda 函数的返回类型。这是因为只有一个 return
语句,它总是返回一个整数值。
但是,对于返回不同类型的多个 return
语句,我们必须显式定义类型。例如,
auto operation = [] (int a, int b, string op) -> double {
if (op == "sum") {
// returns integer value
return a + b;
}
else {
// returns double value
return (a + b) / 2.0;
}
};
请注意上面代码中的 -> double
。这显式地将返回类型定义为 double
,因为根据 op 的值,有多个语句返回不同的类型。
因此,无论各种 return
语句返回哪种类型的值,它们都被显式转换为 double
类型。
示例 2:C++ Lambda - 显式返回类型
#include<iostream>
using namespace std;
int main() {
// lambda function with explicit return type 'double'
// returns the sum or the average depending on operation
auto operation = [] (int a, int b, string op) -> double {
if (op == "sum") {
return a + b;
}
else {
return (a + b) / 2.0;
}
};
int num1 = 1;
int num2 = 2;
// find the sum of num1 and num2
auto sum = operation(num1, num2, "sum");
cout << "Sum = " << sum << endl;
// find the average of num1 and num2
auto avg = operation(num1, num2, "avg");
cout << "Average = " << avg;
return 0;
}
输出
Sum = 3 Average = 1.5
在上面的示例中,我们创建了一个 lambda 函数来查找
- 两个整数的和,或者
- 两个整数的平均值
auto operation = [] (int a, int b, string op) -> double {
if (op == "sum") {
// returns an 'int'
return a + b;
}
else {
// returns a 'double'
return (a + b) / 2.0;
}
};
在 main()
中,我们首先通过将 "sum"
作为第三个参数传递来查找 num1 和 num2 的和
auto sum = operation(num1, num2, "sum");
在这里,即使 lambda 返回整数值,它也会被显式转换为 double
类型。
然后,我们通过传递另一个字符串作为参数来查找平均值
auto avg = operation(num1, num2, "avg");
C++ Lambda 函数捕获子句
默认情况下,lambda 函数无法访问封闭函数的变量。为了访问这些变量,我们使用捕获子句。
我们可以通过两种方式捕获变量
按值捕获
这类似于 按值调用函数。在这里,当 lambda 被创建时,实际值被复制。
注意:在这里,我们只能在 lambda 体内读取变量,但不能修改它。
具有按值捕获的基本 lambda 表达式如下所示
int num_main = 100;
// get access to num_main from the enclosing function
auto my_lambda = [num_main] () {
cout << num_main;
};
在这里,[num_main]
允许 lambda 访问 num_main 变量。
如果我们从捕获子句中删除 num_main,我们将收到一个错误,因为 lambda 体无法访问 num_main。
按引用捕获
这类似于 按引用调用函数,即 lambda 具有对变量地址的访问权限。
注意:在这里,我们可以在 lambda 体内读取变量并修改它。
具有按引用捕获的基本 lambda 表达式如下所示
int num_main = 100;
// access the address of num_main variable
auto my_lambda = [&num_main] () {
num_main = 900;
};
请注意 [&num_main]
中 &
运算符的使用。这表示我们正在捕获 num_main 变量的地址。
示例 3:C++ Lambda 按值捕获
#include<iostream>
using namespace std;
int main() {
int initial_sum = 100;
// capture initial_sum by value
auto add_to_sum = [initial_sum] (int num) {
// here inital_sum = 100 from local scope
return initial_sum + num;
};
int final_sum = add_to_sum(78);
cout << "100 + 78 = " << final_sum;
return 0;
}
输出
100 + 78 = 178
在上面的示例中,我们创建了一个 lambda 表达式,该表达式返回局部变量 initial_sum 和整数参数 num 的和。
auto add_to_sum = [initial_sum] (int num) {
return initial_sum + num;
};
这里,[initial_sum]
按值捕获了封闭函数中的 initial_sum。
然后,我们调用函数并将返回存储在 final_sum 变量中。
int final_sum = add_to_sum(78);
在 lambda 函数内部
- num 是 78
- initial_sum 是 100
所以结果变成 100 + 78,即 178。
注意:假设我们要按值捕获多个变量。例如,
auto my_lambda = [a, b, c, d, e] (){
// lambda body
}
正如你所见,这可能是一项非常繁琐的任务。为了简化工作,我们可以使用**隐式按值捕获**。例如,
auto my_lambda = [=] (){
// lambda body
}
在这里,[=]
表示封闭函数的所有变量都按值捕获。
示例 4:C++ Lambda 按引用捕获
#include <iostream>
using namespace std;
int main() {
int num = 0;
cout << "Initially, num = " << num << endl;
// [&num] captures num by reference
auto increment_by_one = [&num] () {
cout << "Incrementing num by 1.\n";
num++;
};
// invoke lambda function
increment_by_one();
cout << "Now, num = " << num << endl;
return 0;
}
输出
Initially, num = 0 Incrementing num by 1. Now, num = 1
在上面的示例中,我们创建了一个 lambda 函数,该函数将局部变量 num 的值增加 1。
auto increment_by_one = [&num] () {
cout << "Incrementing num by 1.\n";
num++;
};
这里使用 [&num]
按引用捕获 num。
最初,num 的值为 0。
然后,我们调用 lambda 表达式 increment_by_one()
。这将 num 的值增加到 1。
注意:要捕获封闭函数的所有变量,我们可以使用**隐式按引用捕获**。例如,
auto my_lambda = [&] (){
// lambda body
}
这里,[&]
表示所有变量都按引用捕获。
示例:将 C++ Lambda 函数作为 STL 算法的参数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// initialize vector of integers
vector<int> nums = {1, 2, 3, 4, 5, 8, 10, 12};
int even_count = count_if(nums.begin(), nums.end(), [](int num) {
return num % 2 == 0;
});
cout << "There are " << even_count << " even numbers.";
return 0;
}
输出
There are 5 even numbers.
在上面的示例中,我们在 count_if
算法中使用了 lambda 函数来计算 nums 向量中偶数的总数。
int even_count = count_if(nums.begin(), nums.end(), [](int num) {
return num % 2 == 0;
});
请注意,我们将 lambda 表达式作为第三个参数传递给了 count_if
。lambda 表达式接受整数 num,如果 num 是偶数则返回 true
。
此外,我们是内联传递的 lambda 表达式。
[](int num) {
return num % 2 == 0;
}
常见问题
是的,我们可以在单个 lambda 表达式中使用按变量捕获和按引用捕获。
例如,
[&num1, num2]
- 按引用捕获 num1,按值捕获 num2[&, num1, num2]
- 按值捕获 num1 和 num2,其余按引用捕获[=, &num1, &num2]
- 按引用捕获 num1 和 num2,其余按值捕获
auto
关键字的 lambda 表达式?我们可以使用 function<>
模板来实现这一点。
为此,我们首先需要包含 <functional>
头文件。
#include <iostream>
#include<functional>
using namespace std;
int main() {
function<void(int, int)> add = [] (int a, int b) {
cout << "Sum: " << a + b;
};
add(1, 2);
return 0;
}
输出
Sum: 3
这里,function<void(int, int)>
明确表示 lambda 函数的类型为 void
,并接受两个整数参数。
在 C++14 中,引入了泛型 lambda 以支持 lambda 函数中的泛型模板参数。
例如,
#include <iostream>
using namespace std;
int main() {
auto display = [] (auto s) {
cout << s << endl;
};
display(1);
display(2.5);
display("Kathmandu");
return 0;
}
输出
1 2.5 Kathmandu
在上面的示例中,我们使用单个 lambda 表达式来接受泛型类型参数并打印该值。
auto display = [] (auto s){
cout << s << endl;
};
请注意参数列表中的 auto s
代码。这允许我们将不同类型的值传递给 lambda 表达式。
lambda mutable
关键字允许 lambda 修改**按值捕获**的变量,而不会影响其在封闭函数中的原始值。
但是,**按引用捕获**的变量不受影响。例如,
#include <iostream>
using namespace std;
int main() {
int a = 1;
int b = 1;
cout << "In main():" << endl;
cout << "a = " << a << ", ";
cout << "b = " << b << endl;
cout << endl;
auto add_one = [a, &b] () mutable {
// modify both a & b
a++;
b++;
cout << "In add_one():" << endl;
cout << "a = " << a << ", ";
cout << "b = " << b << endl;
};
add_one();
cout << endl;
cout << "In main():" << endl;
cout << "a = " << a << ", ";
cout << "b = " << b << endl;
return 0;
}
输出
In main(): a = 1, b = 1 In add_one(): a = 2, b = 2 In main(): a = 1, b = 2
在上面的示例中,默认情况下 a 变量是不可修改的,因为它被**按值捕获**。
但是,mutable
关键字允许我们在不更改 main()
中原始值的情况下修改它。
auto add_one = [a, &b] () mutable {
a++;
b++;
...
};
这是因为 lambda 函数中的 a 变量是原始变量的一个独立副本。因此,更改它不会更改 main()
中的原始值。
立即调用的 lambda 表达式是指在其定义后立即调用的 lambda 表达式。例如,
#include<iostream>
using namespace std;
int main(){
int num1 = 1;
int num2 = 2;
// invoked as soon as it is defined
auto sum = [] (int a, int b) {
return a + b;
} (num1, num2);
cout << "The sum of " << num1 << " and " << num2 << " is " << sum;
return 0;
}
输出
The sum of 1 and 2 is 3
在上面的示例中,我们创建了一个 lambda 表达式来计算两个整数的和,该表达式立即使用参数 num1 和 num2 调用。
auto sum = [] (int a, int b) {
return a + b;
} (num1, num2);
C++ Lambda 表达式的扩展语法是
[capture_list] (parameter_list) mutable -> return_type {
// lambda body
}
正如你所见,与我们在教程开头提供的基本语法相比,此语法更加全面。
这是因为它包含了我们到目前为止讨论的所有 lambda 元素
[capture_list]
- 捕获封闭函数的变量(parameter_list)
- 要在 lambda 表达式内部使用的参数mutable
- 允许修改按值捕获的变量(在 lambda 内部)return_type
- 显式定义 lambda 表达式的返回类型