跳到主要内容

Lambda 表达式

Lambda 表达式,通常简称为“lambda”,是一种方便地以对象形式编写代码片段的方式,以便将其作为函数参数发送并稍后重用。Lambda 主要用于

  • 在函数内部创建命名对象,这些对象可以像函数一样重用,而不会污染全局命名空间。
  • 创建可以发送到其他函数(例如标准库算法)的“匿名”代码片段。

我们建议查看简单示例实际用法以获取一些示例。

匿名函数、函数对象

Lambda 通常被称为匿名函数函数对象函数符。这些名称都不完全正确,尽管在谈论 lambda 时可以使用它们。事实上,lambda 创建了一个不可见的对象,尽管它们本身只是表达式。由于 lambda 的工作方式(创建了一个神奇的、不可见的、未知类型的对象),要将它们分配给一个对象,我们需要使用关键字auto,或者使用标准库类型——std::function(我们将在课程后面学习它)。

语法

The syntax of a lambda

lambda 必须有一个主体,我们将在其中编写代码和一个捕获列表(可以为空)。参数列表是可选的,尽管经常使用。我们可以向 lambda 表达式添加各种其他内容,如属性、显式返回类型等,尽管它们既不是强制性的也不常用,所以我们将在课程后面讨论它们。

捕获列表

正如我们从函数课程中了解到的,局部变量(例如来自main函数)在任何其他函数的主体中都是未知的。同样的情况也适用于 lambda 表达式。函数中的局部变量在 lambda 表达式内部是不可见的,这就是为什么它们需要被捕获捕获列表中。

带捕获列表的 lambda
int five = 5;
auto get7 = [five] () { return five + 2; };

std::cout << get7();
结果(控制台)
7
注意

如果 lambda 表达式的参数列表为空,则可以省略括号

int five = 5;
auto get7 = [five] { return five + 2; };

std::cout << get7();
修改捕获的变量

捕获列表中捕获的变量目前不能被修改。有一种方法可以做到这一点,但我们将在第二课中讨论。

参数列表

lambda 表达式中的参数列表就像我们从函数中了解到的那样工作。它允许我们声明 lambda 应该用哪些参数调用,然后将参数传递给它。

带参数列表的 lambda。
auto multplyBy7 = [] (int a) { return a * 7; }; // a lambda with a parameter of type int
std::cout << multplyBy7(5); // the lambda called with an argument 5
结果(控制台)
35

lambda 主体

一个我们已经知道的常规代码块。在这里我们声明变量,对对象进行操作等。我们可以在 lambda 主体内部使用return语句。

简单示例

lambda 和每次调用都返回 5 的函数的比较

Lambda
#include <iostream>

int main()
{
auto five = [] { return 5; };
std::cout << five();
}
函数
#include <iostream>

int five()
{
return 5;
}

int main()
{
std::cout << five();
}

返回其参数的平方的 lambda

带参数的 Lambda 表达式
auto square = [](int x) { return x*x; };
std::cout << square(5);
结果(控制台)
25

用于代码重用的 Lambda

函数中的 lambda 函数
void print3Hellos(std::string name) {
auto print_hello = [name](std::string hello) {
std::cout << hello << ", " << name << "!\n";
}

print_hello("Hello");
print_hello("Welcome");
print_hello("Hi");
}
// ...
print3Hellos("Mark");
结果(控制台)
Hello, Mark!
Welcome, Mark!
Hi, Mark!

常见错误

尝试使用未捕获的变量

尝试使用未捕获的变量
int main()
{
int A = 5;

// ❌ Variable A is not known inside addToA ❌
// auto addToA = [] (int b) { return A + b; };

// ✅ Proper lambda declaration ✅
auto addToA = [A] (int b) { return A + b; };
std::cout << addToA(5) << "\n";
}

尝试修改捕获的变量

尝试修改捕获的变量
int main()
{
int A = 5;

// ❌ We can't modify the variable A ❌
// auto addToA = [A] (int b) { A += b; };

// ✅ For now, we can make use of the fact that we can return values.
// You will learn how to modify captured variables later in the course. ✅
auto addToA = [A] (int b) { return A + b; };
std::cout << addToA(5) << "\n";
}

实际用法

C++ 版本

我们建议使用最新的 C++ 版本(正式名称为标准)——C++20,因为它提供了许多方便的功能。如果由于某些原因您不能使用 C++20,我们还提供了适用于旧版本的示例。

将 lambda 与 transform 算法一起使用

要使用此算法,您必须包含algorithm头文件。

#include <algorithm>

目标

在这个例子中,我们将创建一个数字向量,并使用 transform 算法将其中的每个数字平方。

方法

transform算法可以传递一个函数、函数对象或 lambda。由于这是关于 lambda 的课程,我们将使用 lambda。我们的 lambda 将接受一个int类型的参数并返回相同类型的值。

std::transform

ranges命名空间

从 C++20 开始,我们可以使用位于ranges命名空间中的更方便的算法版本,这就是为什么我们必须写std::ranges::transform,而不是std::transform

1. 源

第一个参数是数据源——在我们的例子中是一个整数向量。

std::vector<int> data = {1, 2, 3, 4, 5};
std::ranges::transform(data, [...]);

2. 目的地

第二个参数是我们想要保存数据的容器的开头。另一个容器必须与源容器具有相同或更大的大小。我们可以使用来自data向量的迭代器,或者来自其他一些迭代器。

std::vector<int> result;

result.resize(data.size());
std::ranges::transform(data, result.begin(), [...]);

// Also correct ✅
std::ranges::transform(data, data.begin(), [...]);

3. Lambda

算法最重要的部分,第三个参数。我们发送一个 lambda,它

  • 接受一个与源容器类型相同的参数(本例中为int
  • 返回一个与目标容器类型相同的值(本例中也为int
auto square = [](int a) { return a * a; };
std::ranges::transform(data, result.begin(), square);

// We can also pass the lambda directly, without first saving it into an object:
std::ranges::transform(data, result.begin(), [](int a) { return a * a; });

4. 完整示例

平方向量
#include <iostream>
#include <vector>
#include <algorithm>

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

std::cout << "Before using the algorithm:\n";
for(auto elem : data)
{
std::cout << elem << " ";
}
std::cout << "\n\n";

auto square = [](int a) { return a * a; };
std::ranges::transform(data, data.begin(), square);

std::cout << "After using the algorithm:\n";
for(auto elem : data)
{
std::cout << elem << " ";
}
}
结果(控制台)
Before using the algorithm:
1 2 3 4 5


After using the algorithm:
1 4 9 16 25

我们将在第二课中学习更多算法。

Lambda 表达式

Lambda 表达式,通常简称为“lambda”,是一种方便地以对象形式编写代码片段的方式,以便将其作为函数参数发送并稍后重用。Lambda 主要用于

  • 在函数内部创建命名对象,这些对象可以像函数一样重用,而不会污染全局命名空间。
  • 创建可以发送到其他函数(例如标准库算法)的“匿名”代码片段。

我们建议查看简单示例实际用法以获取一些示例。

匿名函数、函数对象

Lambda 通常被称为匿名函数函数对象函数符。这些名称都不完全正确,尽管在谈论 lambda 时可以使用它们。事实上,lambda 创建了一个不可见的对象,尽管它们本身只是表达式。由于 lambda 的工作方式(创建了一个神奇的、不可见的、未知类型的对象),要将它们分配给一个对象,我们需要使用关键字auto,或者使用标准库类型——std::function(我们将在课程后面学习它)。

语法

The syntax of a lambda

lambda 必须有一个主体,我们将在其中编写代码和一个捕获列表(可以为空)。参数列表是可选的,尽管经常使用。我们可以向 lambda 表达式添加各种其他内容,如属性、显式返回类型等,尽管它们既不是强制性的也不常用,所以我们将在课程后面讨论它们。

捕获列表

正如我们从函数课程中了解到的,局部变量(例如来自main函数)在任何其他函数的主体中都是未知的。同样的情况也适用于 lambda 表达式。函数中的局部变量在 lambda 表达式内部是不可见的,这就是为什么它们需要被捕获捕获列表中。

带捕获列表的 lambda
int five = 5;
auto get7 = [five] () { return five + 2; };

std::cout << get7();
结果(控制台)
7
注意

如果 lambda 表达式的参数列表为空,则可以省略括号

int five = 5;
auto get7 = [five] { return five + 2; };

std::cout << get7();
修改捕获的变量

捕获列表中捕获的变量目前不能被修改。有一种方法可以做到这一点,但我们将在第二课中讨论。

参数列表

lambda 表达式中的参数列表就像我们从函数中了解到的那样工作。它允许我们声明 lambda 应该用哪些参数调用,然后将参数传递给它。

带参数列表的 lambda。
auto multplyBy7 = [] (int a) { return a * 7; }; // a lambda with a parameter of type int
std::cout << multplyBy7(5); // the lambda called with an argument 5
结果(控制台)
35

lambda 主体

一个我们已经知道的常规代码块。在这里我们声明变量,对对象进行操作等。我们可以在 lambda 主体内部使用return语句。

简单示例

lambda 和每次调用都返回 5 的函数的比较

Lambda
#include <iostream>

int main()
{
auto five = [] { return 5; };
std::cout << five();
}
函数
#include <iostream>

int five()
{
return 5;
}

int main()
{
std::cout << five();
}

返回其参数的平方的 lambda

带参数的 Lambda 表达式
auto square = [](int x) { return x*x; };
std::cout << square(5);
结果(控制台)
25

用于代码重用的 Lambda

函数中的 lambda 函数
void print3Hellos(std::string name) {
auto print_hello = [name](std::string hello) {
std::cout << hello << ", " << name << "!\n";
}

print_hello("Hello");
print_hello("Welcome");
print_hello("Hi");
}
// ...
print3Hellos("Mark");
结果(控制台)
Hello, Mark!
Welcome, Mark!
Hi, Mark!

常见错误

尝试使用未捕获的变量

尝试使用未捕获的变量
int main()
{
int A = 5;

// ❌ Variable A is not known inside addToA ❌
// auto addToA = [] (int b) { return A + b; };

// ✅ Proper lambda declaration ✅
auto addToA = [A] (int b) { return A + b; };
std::cout << addToA(5) << "\n";
}

尝试修改捕获的变量

尝试修改捕获的变量
int main()
{
int A = 5;

// ❌ We can't modify the variable A ❌
// auto addToA = [A] (int b) { A += b; };

// ✅ For now, we can make use of the fact that we can return values.
// You will learn how to modify captured variables later in the course. ✅
auto addToA = [A] (int b) { return A + b; };
std::cout << addToA(5) << "\n";
}

实际用法

C++ 版本

我们建议使用最新的 C++ 版本(正式名称为标准)——C++20,因为它提供了许多方便的功能。如果由于某些原因您不能使用 C++20,我们还提供了适用于旧版本的示例。

将 lambda 与 transform 算法一起使用

要使用此算法,您必须包含algorithm头文件。

#include <algorithm>

目标

在这个例子中,我们将创建一个数字向量,并使用 transform 算法将其中的每个数字平方。

方法

transform算法可以传递一个函数、函数对象或 lambda。由于这是关于 lambda 的课程,我们将使用 lambda。我们的 lambda 将接受一个int类型的参数并返回相同类型的值。

std::transform

ranges命名空间

从 C++20 开始,我们可以使用位于ranges命名空间中的更方便的算法版本,这就是为什么我们必须写std::ranges::transform,而不是std::transform

1. 源

第一个参数是数据源——在我们的例子中是一个整数向量。

std::vector<int> data = {1, 2, 3, 4, 5};
std::ranges::transform(data, [...]);

2. 目的地

第二个参数是我们想要保存数据的容器的开头。另一个容器必须与源容器具有相同或更大的大小。我们可以使用来自data向量的迭代器,或者来自其他一些迭代器。

std::vector<int> result;

result.resize(data.size());
std::ranges::transform(data, result.begin(), [...]);

// Also correct ✅
std::ranges::transform(data, data.begin(), [...]);

3. Lambda

算法最重要的部分,第三个参数。我们发送一个 lambda,它

  • 接受一个与源容器类型相同的参数(本例中为int
  • 返回一个与目标容器类型相同的值(本例中也为int
auto square = [](int a) { return a * a; };
std::ranges::transform(data, result.begin(), square);

// We can also pass the lambda directly, without first saving it into an object:
std::ranges::transform(data, result.begin(), [](int a) { return a * a; });

4. 完整示例

平方向量
#include <iostream>
#include <vector>
#include <algorithm>

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

std::cout << "Before using the algorithm:\n";
for(auto elem : data)
{
std::cout << elem << " ";
}
std::cout << "\n\n";

auto square = [](int a) { return a * a; };
std::ranges::transform(data, data.begin(), square);

std::cout << "After using the algorithm:\n";
for(auto elem : data)
{
std::cout << elem << " ";
}
}
结果(控制台)
Before using the algorithm:
1 2 3 4 5


After using the algorithm:
1 4 9 16 25

我们将在第二课中学习更多算法。