第六章QT基础: Lambda表达式补充
什么是 Lambda 表达式?
Lambda 表达式(匿名函数)是一种可以在不定义函数名称的情况下直接定义并使用的小型函数。在 C++11 及更高版本中,Lambda 表达式使得编写临时函数变得更加简洁和直观。
Lambda 表达式通常用于:
- 传递函数作为参数。
- 在需要临时功能时避免定义额外的函数。
Lambda 表达式的基本语法
Lambda 表达式的基本语法格式如下:
[捕获列表] (参数列表) -> 返回类型 { 函数体 }
组成部分:
-
捕获列表 (Capture List):
捕获列表用于指定外部变量如何在 Lambda 中使用。可以捕获外部作用域的变量,并决定是按值捕获还是按引用捕获。-
[=]
:按值捕获外部作用域中的所有变量。 -
[&]
:按引用捕获外部作用域中的所有变量。 -
[var]
:按值捕获指定的变量var
。 -
[&var]
:按引用捕获指定的变量var
。 -
[=, &var]
:按值捕获所有外部变量,并按引用捕获指定变量var
。
-
-
参数列表 (Parameter List):
Lambda 表达式接受的参数列表,和普通函数一样。 -
返回类型 (Return Type):
-> 返回类型
用于显式声明 Lambda 表达式的返回类型。C++11 之后可以省略返回类型,编译器会自动推断。 -
函数体 (Body):
函数体是 Lambda 表达式的执行逻辑,类似普通函数的实现部分。
Lambda 表达式的捕获列表详细说明
在 C++ 中,捕获列表 是用于指定如何将外部变量传递到 Lambda 表达式中的一部分。捕获列表可以选择按值(=
)或按引用(&
)来捕获变量,也可以只捕获特定的变量。接下来,我们将逐一讲解您提到的几种捕获方式。
1. [=]
:按值捕获外部作用域中的所有变量
[=]
语法表示按值捕获所有外部变量。Lambda 表达式会创建外部变量的副本,并在 Lambda 内部使用这些副本。
- 副本:当 Lambda 表达式调用时,它使用的是外部变量的副本,而不是外部原始变量的地址。因此,对 Lambda 内部捕获变量的修改不会影响外部的原始变量。
#include <iostream>int main() {int x = 10;int y = 20;// 按值捕获外部变量auto print_values = [=]() { std::cout << "x: " << x << ", y: " << y << std::endl;};x = 15; // 修改外部 xy = 25; // 修改外部 yprint_values(); // 输出:x: 10, y: 20(因为按值捕获,使用的是 x 和 y 的副本)return 0;
}
- 输出:
x: 10, y: 20
解释:
- 即使在
print_values
调用时,x
和y
已经被修改,Lambda 仍然使用它们的原始值(10 和 20),因为它们在 Lambda 捕获时是按值捕获的。
2. [&]
:按引用捕获外部作用域中的所有变量
[&]
语法表示按引用捕获所有外部变量。Lambda 表达式将直接使用外部变量的引用。因此,对 Lambda 内部捕获变量的修改会影响外部原始变量。
#include <iostream>int main() {int x = 10;int y = 20;// 按引用捕获外部变量auto modify_values = [&]() { x = 30; // 修改外部 xy = 40; // 修改外部 y};modify_values(); // 调用 Lambda,修改外部变量std::cout << "x: " << x << ", y: " << y << std::endl; // 输出:x: 30, y: 40return 0;
}
- 输出:
x: 30, y: 40
解释:
- Lambda 捕获外部变量
x
和y
时是按引用的,意味着修改 Lambda 内部的变量会直接影响外部变量x
和y
。
3. [var]
:按值捕获指定的变量 var
[var]
语法表示只按值捕获指定的变量 var
。仅捕获指定的变量并创建该变量的副本,其他变量则不会被捕获。
#include <iostream>int main() {int x = 10;int y = 20;// 只按值捕获变量 xauto print_value = [x]() { std::cout << "x: " << x << std::endl;};x = 15; // 修改外部 xprint_value(); // 输出:x: 10(因为按值捕获,Lambda 使用的是 x 的副本)return 0;
}
-
输出:
x: 10
解释:
- 由于
x
是按值捕获的,所以即使在 Lambda 调用时修改了x
,Lambda 使用的仍然是x
的副本。
4. [&var]
:按引用捕获指定的变量 var
[&var]
语法表示只按引用捕获指定的变量 var
,意味着 var
是通过引用捕获的,其他变量则不被捕获。
#include <iostream>int main() {int x = 10;int y = 20;// 只按引用捕获变量 yauto modify_y = [&y]() { y = 30; // 修改 y};modify_y(); // 调用 Lambda,修改外部变量 ystd::cout << "y: " << y << std::endl; // 输出:y: 30return 0;
}
- 输出:
y: 30
解释:
y
是按引用捕获的,因此 Lambda 内部对y
的修改直接影响外部的y
。但x
没有被捕获,因此它没有受到影响。
5. [=, &var]
:按值捕获所有外部变量,并按引用捕获指定变量 var
[=, &var]
语法表示按值捕获所有外部变量,并按引用捕获特定变量 var
。所有未显式列出的变量都会按值捕获,而 var
会按引用捕获。
#include <iostream>int main() {int x = 10;int y = 20;// 按值捕获所有外部变量,按引用捕获 yauto modify_values = [=, &y]() { std::cout << "x: " << x << ", y: " << y << std::endl;y = 50; // 修改 y};modify_values(); // 修改 ystd::cout << "x: " << x << ", y: " << y << std::endl; // 输出:x: 10, y: 50return 0;
}
-
输出:
x: 10, y: 20 x: 10, y: 50
解释:
-
x
是按值捕获的,因此 Lambda 使用的是x
的副本,外部x
的值不会改变。 -
y
是按引用捕获的,因此在 Lambda 内部修改y
时,外部的y
也会被修改。
总结
-
[=]
:按值捕获所有外部变量,变量的副本会传递给 Lambda,修改不会影响外部变量。 -
[&]
:按引用捕获所有外部变量,Lambda 内部的修改会直接影响外部变量。 -
[var]
:按值捕获指定变量,只有指定变量会按值传递给 Lambda。 -
[&var]
:按引用捕获指定变量,只有指定变量会按引用传递给 Lambda。 -
[=, &var]
:按值捕获所有外部变量,并按引用捕获指定变量。
示例 1:基本的 Lambda 表达式
#include <iostream>int main() {// 定义一个 lambda 表达式,计算两个整数的和auto add = [](int a, int b) -> int {return a + b;};int result = add(5, 3); // 调用 Lambda 表达式std::cout << "Result: " << result << std::endl; // 输出:Result: 8return 0;
}
说明:
-
捕获列表:
[]
,没有捕获外部变量。 -
参数列表:
(int a, int b)
,表示 Lambda 表达式接收两个整数作为参数。 -
返回类型:
-> int
,表示 Lambda 表达式返回一个整数。 -
函数体:
return a + b;
,实现了加法操作。
示例 2:按值捕获外部变量
#include <iostream>int main() {int x = 10;int y = 20;// 按值捕获外部变量auto add = [=]() -> int { return x + y; };// 修改 x 和 y 的值x = 15;y = 25;std::cout << "Result: " << add() << std::endl; // 输出:Result: 30return 0;
}
说明:
-
捕获列表:
[=]
,表示按值捕获所有外部变量。 -
返回值:即使
x
和y
在 Lambda 表达式调用时被修改,Lambda 仍然使用它们被捕获时的值。
示例 3:按引用捕获外部变量
#include <iostream>int main() {int x = 10;int y = 20;// 按引用捕获外部变量auto add = [&]() -> int { x += 5; // 修改外部变量 xreturn x + y; };std::cout << "Result: " << add() << std::endl; // 输出:Result: 35std::cout << "x: " << x << ", y: " << y << std::endl; // 输出:x: 15, y: 20return 0;
}
说明:
- 捕获列表:
[&]
,按引用捕获外部变量x
和y
,这意味着在 Lambda 表达式内对这些变量的修改会影响外部的变量。
示例 4:捕获特定变量
#include <iostream>int main() {int x = 10;int y = 20;// 捕获特定变量(按值捕获 x 和按引用捕获 y)auto add = [x, &y]() -> int { y = y + 5; // 修改 yreturn x + y; // 使用捕获的 x 和修改后的 y};std::cout << "Result: " << add() << std::endl; // 输出:Result: 35std::cout << "x: " << x << ", y: " << y << std::endl; // 输出:x: 10, y: 25return 0;
}
说明:
-
捕获列表:
[x, &y]
,表示按值捕获x
,按引用捕获y
。x
的值不会变化,而y
会发生变化。 -
调用结果:Lambda 使用捕获时的
x
(10),同时修改y
的值。
示例 5:无参数的 Lambda 表达式
#include <iostream>int main() {// 一个不带参数的简单 lambda 表达式auto greet = []() { std::cout << "Hello, World!" << std::endl; };greet(); // 输出:Hello, World!return 0;
}
说明:
-
捕获列表:
[]
,没有捕获外部变量。 -
参数列表:
()
,不接受任何参数。 -
函数体:输出
Hello, World!
。
示例 6:使用 Lambda 表达式作为参数
Lambda 表达式常用于作为函数的参数,特别是在 STL 算法中。
#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};// 使用 lambda 表达式作为 std::for_each 的参数std::for_each(numbers.begin(), numbers.end(), [](int num) {std::cout << num << " ";});std::cout << std::endl;return 0;
}
说明:
- Lambda 表达式:
[](int num) { std::cout << num << " "; }
,在std::for_each
中使用 Lambda 遍历容器numbers
,并输出每个元素。
示例 7:Lambda 表达式的返回类型推导
C++14 引入了 Lambda 表达式返回类型推导,编译器会自动推导 Lambda 的返回类型,减少冗余的类型声明。
#include <iostream>int main() {auto add = [](int a, int b) { return a + b; // 自动推导返回类型};std::cout << "Result: " << add(5, 3) << std::endl; // 输出:Result: 8return 0;
}
说明:
- 返回类型推导:没有显式声明返回类型,编译器自动推导返回值的类型(此例为
int
)。
总结
Lambda 表达式在 C++ 中提供了一种简洁的方式来创建匿名函数,它有以下特点:
-
简洁:无须为小功能编写额外的函数。
-
灵活:能够捕获外部变量,通过值或引用捕获外部作用域中的变量。
-
强大:广泛应用于 STL 算法中,作为函数参数传递。
使用场景:
-
临时函数:不需要重复使用的函数可以使用 Lambda 表达式。
-
回调函数:作为回调函数传递给 STL 算法或事件处理器。
-
事件处理:在 GUI 编程中,Lambda 表达式常用于信号和槽的连接。
Lambda 表达式极大地提高了代码的简洁性和可读性,是现代 C++ 编程的重要工具之一。