目录
写在前面
最近在学习C++的一些特性,也试图结合输出的方式对知识点进行全面学习。C++特性中一个很实用的就是Lambda表达式,然而,对于新手来说,在庞杂的互联网搜索这样一个概念的时候,往往会面对一些比较复杂的例子。这些例子在理解的深度上是有追求的,但是对新手来说,有一定的阅读门槛。
于是我决定写这一篇文章,一方面面向初次接触Lambda表达式的新手,另一方面也尝试对该知识点进行梳理,确认自己的掌握程度。本文将在每一个小节的开端提出问题供读者带着问题阅读,本人才疏学浅,文中若有疏漏,欢迎指出,我将及时改正。
预祝阅读愉快。
一、什么是Lambda表达式?
为什么要使用Lambda表达式?
使用Lambda表达式和使用普通函数有什么区别?
大部分新的知识,都是对旧知识的更新和完善。Lambda表达式对标的就是普通的函数。让我们考虑一个简单的例子:如何使用Lambda表达式和不使用Lambda表达式来实现两个整数的相加操作。
1.1 不使用Lambda表达式:
#include <iostream>
int add(int a, int b) {
return a + b;
}
int main() {
int a = 5, b = 3;
int result = add(a, b);
std::cout << "Sum: " << result << std::endl;
return 0;
}
1.2 使用Lambda表达式:
#include <iostream>
int main() {
int a = 5, b = 3;
auto add = [](int a, int b) {
return a + b;
};
int result = add(a, b);
std::cout << "Sum: " << result << std::endl;
return 0;
}
在这个例子中,可以一目了然地看出Lambda表达式充当了什么样的一个功能。使用Lambda表达式可以使我们的代码更加简洁,无需定义单独的add函数。通过将Lambda表达式赋值给add变量,我们可以像调用普通函数一样调用它。此外,Lambda表达式还可以访问main函数内的局部变量,这在某些情况下可能非常有用。
二、Lambda表达式的基本用法
如何写出一个最简单的Lambda表达式?
本节旨在让读者对Lambda表达式有一个初步的了解,明白如何以最简单的方式对这一个工具进行使用。
2.1 基本语法
Lambda表达式可以接受参数,就像普通函数一样。参数列表的语法与普通函数相同,完整的Lambda表达式如下:
[捕获列表] (参数列表) -> 返回类型 { 函数体 }
这是Lambda表达式的完整结构,但在第一部分整数相加的例子中,并没有使用捕获列表,也没有声明返回类型。其实,捕获列表、参数列表、返回类型和函数体都是可选的。下面依托于例子,来看一下各部分的用法。
2.2 参数列表和函数体
参数列表和函数体都很好理解的,只要有使用普通函数的经验,对于这两个部分都不会陌生。参数列表的语法与普通函数完全相同,Lambda表达式的函数体也可以包含任何合法的C++代码。下面是一个例子:
auto add = [](int x, int y) { return x + y; };
2.3 捕获列表
捕获列表定义了Lambda表达式可以访问的外部变量,在第一部分的基础上,把整数相加的情况改成如下:
#include <iostream>
int main() {
int a = 5, b = 3;
int offset = 2;
auto addWithOffset = [offset](int a, int b) {
return a + b + offset;
};
int result = addWithOffset(a, b);
std::cout << "Sum with offset: " << result << std::endl;
return 0;
}
在这个例子中可以很清晰地明了捕获列表的用法,其在Lambda表达式前面添加了一个捕获列表[offset],这样我们就可以在Lambda表达式内部使用offset变量。捕获列表允许我们在Lambda表达式中访问局部变量,否则,我们将无法直接访问它们。
注意:如果需要捕获多个变量,可以使用逗号分隔它们,如[var1, var2, var3],这一点将在后续小节补充。
2.4 返回类型
Lambda表达式可以指定返回类型。如果未指定返回类型,编译器会自动推导返回类型。在第一部分的Lambda表达式中,返回类型未被指定,编译器自动推导了返回值类型。
auto add = [](int a, int b) -> int {
return a + b;
};
2.5 *无参数的Lambda表达式
作为补充,Lambda表达式也可以不带参,如下:
auto print_hello = []() { std::cout << "Hello, Lambda!" << std::endl; };
print_hello();
三、捕获列表的简单使用
捕获列表有哪几种传递方式,不同的传递方式可以混合使用吗?
值传递和引用传递的方式如何实现,可以举出例子吗?
3.1 捕获列表的形式
捕获列表定义了Lambda表达式可以访问的外部变量。捕获列表可以有以下几种形式:
形式 | 含义 |
---|---|
[] | 不捕获任何变量。(参见例1) |
[=] | 以值捕获所有变量。 |
[&] | 以引用捕获所有变量。 |
[x, &y] | 混合捕获。捕获变量x的值和变量y的引用。 |
下面将详细介绍以下五个捕获列表形式有何不同,此部分因为过分详细而显冗长,如果已经明了则可直接跳转至3.7小结。
1. auto add_value_capture = [a, b]() {return a + b; };
2. auto add_value_capture = [&a, &b]() {return a + b; };
3. auto add_reference_capture = [=]() {return a + b; };
4. auto add_reference_capture = [&]() {return a + b; };
5. auto add_value_capture = [a, &b]() {return a + b; };
3.2 值传递方式(传统版)
关键语句:
auto add_value_capture = [a, b]() {return a + b; };
完整代码:
#include <iostream>
int main() {
int a = 5;
int b = 6;
auto add_value_capture = [a, b]() {
// 以值传递的方式捕获 a 和 b
return a + b;
};
int result1 = add_value_capture(); // result1 = 11
std::cout << "Result1: " << result1 << std::endl; // 输出:Result1: 11
a = 10;
b = 20;
int result2 = add_value_capture(); // result2 = 11
std::cout << "Result2 : " << result2 << std::endl;
// 输出:Result2: 11
return 0;
}
3.3 引用传递方式(传统版)
关键语句:
auto add_reference_capture = [&a, &b]() {return a + b; };
完整例子:
#include <iostream>
int main() {
int a = 5;
int b = 6;
auto add_reference_capture = [&a, &b]() {
// 以引用传递的方式捕获 a 和 b
return a + b;
};
int result1 = add_reference_capture(); // result1 = 11
std::cout << "Result1: " << result1 << std::endl; // 输出:result1: 11
a = 10;
b = 20;
int result2 = add_reference_capture(); // result2 = 30
std::cout << "Result2 : " << result2 << std::endl;
// 输出:result2: 30
return 0;
}
3.4 值传递方式(简洁版)
关键语句:
auto add_value_capture = [=]() {return a + b; };
完整代码:
#include <iostream>
int main() {
int a = 5;
int b = 6;
auto add_value_capture = [=]() {
// 以值传递的方式捕获 a 和 b
return a + b;
};
int result1 = add_value_capture(); // result1 = 11
std::cout << "Result1: " << result1 << std::endl; // 输出:Result1: 11
a = 10;
b = 20;
int result2 = add_value_capture(); // result2 = 11
std::cout << "Result2 : " << result2 << std::endl;
// 输出:Result2: 11
return 0;
}
3.5 引用传递方式(简洁版)
关键语句:
auto add_reference_capture = [&]() {return a + b; };
完整例子:
#include <iostream>
int main() {
int a = 5;
int b = 6;
auto add_reference_capture = [&]() {
// 以引用传递的方式捕获 a 和 b
return a + b;
};
int result1 = add_reference_capture(); // result1 = 11
std::cout << "Result1: " << result1 << std::endl; // 输出:result1: 11
a = 10;
b = 20;
int result2 = add_reference_capture(); // result2 = 30
std::cout << "Result2 : " << result2 << std::endl;
// 输出:result2: 30
return 0;
}
3.6 混合使用
关键语句:
auto add_value_capture = [a, &b](){return a + b; };
完整例子:
#include <iostream>
int main() {
int a = 5;
int b = 6;
auto add_value_capture = [a, &b]() {
// a 以值传递的方式捕获
// b 以引用传递的方式捕获
return a + b;
};
int result = add_value_capture(); // result will be 11
std::cout << "Result: " << result << std::endl; // 输出:Result: 11
a = 10;
b = 20;
result = add_value_capture(); // result will now be 26
std::cout << "Result after changing a and b: " << result << std::endl; // 输出:Result after changing a and b: 26
return 0;
}
3.7 小结
auto add_value_capture = [a, b]() {return a + b; };
这个 Lambda 表达式捕获了变量 a 和 b,它们都是以值传递的方式被捕获。因此,即使在外部更改了 a 和 b 的值,Lambda 表达式中的 a 和 b 的值仍然保持不变。auto add_value_capture = [&a, &b]() {return a + b; };
这个 Lambda 表达式捕获了变量 a 和 b,它们都是以引用传递的方式被捕获。因此,当我们在外部修改 a 和 b 的值时,Lambda 表达式中的 a 和 b 的值也会相应地改变。auto add_reference_capture = [=]() {return a + b; };
这个 Lambda 表达式使用值传递的方式捕获所有父作用域的变量。在这个例子中,它捕获了变量 a 和 b。即使在外部更改了 a 和 b 的值,Lambda 表达式中的 a 和 b 的值仍然保持不变。auto add_reference_capture = [&]() {return a + b; };
这个 Lambda 表达式使用引用传递的方式捕获所有父作用域的变量。在这个例子中,它捕获了变量 a 和 b。因此,当我们在外部修改 a 和 b 的值时,Lambda 表达式中的 a 和 b 的值也会相应地改变。auto add_value_capture = [a, &b]() {return a + b; };
这个 Lambda 表达式捕获了变量 a 和 b,其中 a 是以值传递的方式被捕获,而 b 是以引用传递的方式被捕获。因此,即使在外部更改了 a 的值,Lambda 表达式中的 a 值仍然保持不变。然而,当我们修改 b 的值时,Lambda 表达式中的 b 值也会相应地改变。
四、捕获列表与参数列表区别
Lambda表达式中捕获列表和参数列表为什么不直接和函数一样用参数列表解决所有问题?
Lambda表达式的捕获列表和参数列表有不同的目的和用途,它们之间存在明显的区别:
4.1 目的不同:
捕获列表:捕获列表用于让Lambda表达式访问其所在作用域的外部变量。通过捕获列表,Lambda表达式可以捕获外部作用域的变量,使其在表达式内部可用。这使得Lambda表达式具有闭包(closure)的特性,可以记住并使用定义它的作用域的变量。
参数列表:参数列表与普通函数的参数列表类似,用于向Lambda表达式传递参数。它定义了在调用Lambda表达式时需要传递的参数类型和数量。
4.2 作用范围不同:
捕获列表:捕获的变量来源于Lambda表达式的外部作用域,使得Lambda表达式可以使用这些外部变量。
参数列表:参数列表中的变量来源于调用Lambda表达式时传递的实际参数。这些参数在Lambda表达式内部使用,与外部作用域无关。
将捕获列表和参数列表分开使得Lambda表达式更加灵活,可以更好地满足不同的使用场景。捕获列表让Lambda表达式可以访问外部作用域的变量,从而实现闭包的功能,而参数列表允许Lambda表达式像普通函数一样接收参数。这种设计可以让Lambda表达式在不同的情况下具有更丰富的功能和表现力。
五、总结
本文非常浅显地讨论了 Lambda 表达式的相关概念、语法和用法。
总结来说,Lambda 表达式是一种创建匿名函数对象的简洁方法。它可以让我们将函数作为参数传递给其他函数,或者将函数作为对象存储在容器中。
- Lambda 表达式的基本语法是:捕获列表 -> 返回类型 {函数体}。捕获列表用于捕获外部作用域的变量,参数列表用于传递参数,返回类型是可选的,如果省略,编译器会自动推断。
- 捕获列表:捕获列表用于捕获外部作用域的变量,可以使用值传递或引用传递的方式。捕获列表可以包含具体的变量名,也可以使用 [=] 或 [&] 来表示捕获所有变量(分别表示值传递和引用传递)。
- 参数列表:Lambda 表达式的参数列表与普通函数的参数列表类似,可以接受任意数量的参数,并可以指定参数的类型。
- 返回类型:Lambda 表达式的返回类型可以由编译器自动推断,也可以在 Lambda 表达式中使用 -> 语法显式指定返回类型。
优势:Lambda 表达式通常用于创建简短的、一次性使用的函数,可以在算法、函数参数或作为容器中的元素等场景中使用。Lambda 表达式可以让代码更简洁、可读性更高。 - 值传递和引用传递:通过捕获列表,Lambda 表达式可以以值传递或引用传递的方式捕获外部作用域的变量。值传递意味着在 Lambda 表达式内部对捕获的变量进行修改不会影响外部作用域的变量,而引用传递意味着在 Lambda 表达式内部对捕获的变量进行修改会影响外部作用域的变量。