1 Lambda表达式介绍
在C++中,lambda表达式(也称为闭包)是一种可以定义匿名函数对象的便捷方式。它们能够捕获所在作用域中的局部变量,并且可以在需要函数对象的地方使用。Lambda表达式为C++提供了更简洁、更灵活的函数式编程方式。
Lambda表达式的基本语法如下:
[capture](parameters) mutable-> return_type { body_of_lambda }
其中,
-
capture
:捕获子句,用于指定lambda可以访问的外部变量的列表。捕获可以是按值(=
)或按引用(&
),或者显式列出要捕获的变量。 -
parameters
:lambda函数的参数列表,与普通函数的参数列表类似。 -
mutable
:可选的。如果指定了mutable
,那么lambda体内部可以修改通过值捕获的外部变量的值。 -
return_type
:返回类型。如果省略,编译器会根据lambda体中的返回语句自动推断返回类型。 -
body_of_lambda
:lambda函数的主体,即包含函数要执行的代码块。
例如,计算两个整数之和
#include <iostream>
#include <functional>
int main()
{
int num1 = 5;
int num2 = 10;
// 使用lambda表达式计算两数之和,并显式指定参数和返回类型
// std::function<int(int, int)>也可以直接采用auto进行声明定义sum
std::function<int(int, int)> sum = [](int x, int y) -> int
{
return x + y;
};
// 调用lambda表达式并输出结果,显式声明变量类型
int res = sum(num1, num2);
std::cout << "两数之和:" << res << std::endl;
return 0;
}
其中,std::function
来存储Lambda表达式,因为它是一个通用、多态的函数包装器,可以存储、复制和调用任何可调用的目标,包括Lambda表达式、函数指针和其他函数对象。
2 Lambda表达式捕获列表
Lambda捕获列表决定了Lambda表达式可以访问哪些外围作用域的变量以及以何种方式访问它们。捕获列表决定了Lambda表达式内部对外部变量的访问权限和生命周期。
捕获列表的可以是以下几种形式之一:
-
空捕获列表
[]
:不捕获任何外部变量。Lambda表达式内部不能访问任何外部作用域的变量(除了全局变量和静态变量)。 -
按值捕获
[=]
:以值的方式捕获所有外部变量。Lambda表达式内部可以访问并修改这些变量的副本,但修改不会影响原始变量。 -
按引用捕获
[&]
:以引用的方式捕获所有外部变量。Lambda表达式内部可以访问并修改这些变量的原始值,对变量的修改会影响到原始变量。 -
显式捕获:可以显式指定要捕获的变量及其捕获方式。例如,
[x, &y]
表示按值捕获变量x
,按引用捕获变量y
。 -
混合捕获:可以在同一个捕获列表中混合使用按值和按引用捕获。例如,
[=, &z]
表示按值捕获所有外部变量,但z
是按引用捕获的。
除此之外,捕获列表还可以包含mutable
关键字,它允许在const环境中修改Lambda捕获的按值捕获的变量。
2.1 空捕获列表
当使用空捕获列表[]时,Lambda表达式不能访问任何外部作用域的变量(除了全局变量和静态变量)。这意味着Lambda表达式内部只能使用其参数和内部定义的局部变量。
#include <iostream>
int main()
{
int x = 10;
auto lambda = []()
{
// 不能访问x,因为使用了空捕获列表
// std::cout << x << std::endl; // 错误
std::cout << "Lambda表达式空捕获列表" << std::endl;
};
lambda();
return 0;
}
2.2 按值捕获
按值捕获[=]是Lambda表达式将捕获所有外部作用域的变量,并以副本的形式在Lambda内部使用。这些副本是在Lambda创建时创建的,因此Lambda内部对副本的修改不会影响原始变量。
#include <iostream>
int main()
{
int x = 10;
int y = 20;
auto lambda = [=]()
{
// 访问的是x和y的副本
std::cout << "Lambda表达式按值捕获 x: " << x << std::endl;
std::cout << "Lambda表达式按值捕获 y: " << y << std::endl;
};
// 修改x和y的值
x = 30;
y = 40;
// 输出x和y的原始值,而不是修改后的值
lambda();
return 0;
}
2.3 按引用捕获
按引用捕获[&]是Lambda表达式将捕获所有外部作用域的变量的引用。Lambda内部可以直接访问和修改这些变量的原始值,修改将反映到原始变量上。
#include <iostream>
int main()
{
int x = 10;
int y = 20;
// 访问的是x和y的引用
auto lambda = [&]()
{
// 修改x和y的值
x = 30;
y = 40;
std::cout << "Lambda表达式按引用捕获x: " << x << std::endl;
std::cout << "Lambda表达式按引用捕获y: " << y << std::endl;
};
// 输出修改后的x和y的值
lambda();
std::cout << "Lambda表达是修改后的x: " << x << std::endl;
std::cout << "Lambda表达是修改后的y: " << y << std::endl;
return 0;
}
2.4 显式捕获
显式捕获允许你选择性地捕获特定的外部变量,并可以为每个变量指定捕获方式(按值或按引用)。
#include <iostream>
int main()
{
int x = 10;
int y = 20;
int z = 30;
auto lambda = [x, &y](int a)
{
// x是按值捕获的
// y是按引用捕获的
// z没有被捕获,因此不可访问
std::cout << "Lambda表达式显示捕获x : " << x << std::endl;
std::cout << "Lambda表达式显示捕获y: " << y << std::endl;
// std::cout << "z: " << z << std::endl; // 错误,z没有被捕获
};
y = 40; // 修改y的值
lambda(5); // 输出x的原始值和修改后的y的值
return 0;
}
2.5 混合捕获
混合捕获允许你在同一个捕获列表中同时使用按值和按引用捕获。这提供了一种灵活的方式来决定哪些变量应该被按值捕获,哪些应该被按引用捕获。
#include <iostream>
#include <vector>
int main()
{
int a = 10; // 一个不会被修改的变量,可以按值捕获
int b = 20; // 一个可能会被Lambda内部修改的变量,应该按引用捕获
std::vector<int> vec = {1, 2, 3, 4, 5}; // 一个容器,我们可能想要修改它的内容,应该按引用捕获
// 使用混合捕获:a按值捕获,b和vec按引用捕获
auto lambda = [a, &b, &vec]()
{
std::cout << "Lambda表达式混合捕获a (按值方式): " << a << std::endl;
std::cout << "Lambda表达式显示捕获b (按引用方式),修改前值: " << b << std::endl;
std::cout << "Lambda表达式显示捕获vec(按照引用方式),原始数组长度: " << vec.size() << std::endl;
// 修改按引用捕获的变量和容器
b = 30;
vec.push_back(6);
std::cout << "Lambda表达式显示捕获b (按引用方式),修改后值: " << b << std::endl;
std::cout << "Lambda表达式显示捕获vec(按照引用方式),修改后数组长度: " << vec.size() << std::endl;
};
// 调用Lambda
lambda();
// 输出Lambda外部修改后的b的值和vec的大小,以验证Lambda内部对它们的修改是有效的
std::cout << "调用Lambda表达式后,b的值是: " << b << std::endl;
std::cout << "调用Lambda表达式后,数组长度是: " << vec.size() << std::endl;
return 0;
}
3 Lambda表达式作为返回值
Lambda表达式可以像其他任何对象一样被返回。我们需要定义一个返回函数对象(比如闭包)的函数时特别有用。Lambda表达式可以捕获其所在作用域的变量,并且返回后可以在其他地方调用。
#include <iostream>
#include <functional>
// 一个函数,返回一个Lambda表达式
std::function<int(int)> GetMultiplier(int factor)
{
return [factor](int x)
{
return factor * x;
};
}
int main()
{
// 获取一个Lambda表达式,该表达式表示乘以3的操作
std::function<int(int)> multiplier = GetMultiplier(3);
// 使用返回的Lambda表达式
std::cout << "乘以3的结果: " << multiplier(5) << std::endl;
// 获取另一个Lambda表达式,这次表示乘以5的操作
multiplier = GetMultiplier(5);
// 使用新的Lambda表达式
std::cout << "乘以5的结果: " << multiplier(5) << std::endl; // 输出 25
return 0;
}
代码示例中,GetMultiplier
函数接受一个整数factor
,并返回一个Lambda表达式,该表达式接受另一个整数x
并返回factor
与x
的乘积。Lambda表达式捕获了factor
变量,并可以在调用时用它来计算乘积。
4 Lambda表达式注意事项
在使用Lambda表达式时,需要注意以下事项:
-
捕获列表的使用:
-
按值捕获:如果Lambda表达式捕获了按值传递的局部变量,并且这些变量在Lambda被调用时不再有效(例如,它们位于Lambda定义的作用域之外,并且已经被销毁),则会出现错误。通常,最好捕获值类型的常量或不可变数据。
-
按引用捕获:按引用捕获的变量必须确保在Lambda表达式被调用时仍然有效。如果Lambda的生命周期超过了捕获的变量的生命周期,这将会导致悬挂引用,是非常危险的。
-
避免捕获大对象:按值捕获大对象或容器可能会导致不必要的性能开销,因为会创建这些对象的副本。如果可能,最好通过引用捕获或使用指针。
-
-
Lambda表达式的生命周期:
-
Lambda表达式具有其自己的生命周期,并与它被定义的位置和作用域无关。它们可以被存储在变量中、作为参数传递或返回。
-
需要确保Lambda表达式使用的所有资源(如动态分配的内存)在Lambda不再需要时得到正确释放。
-
-
闭包行为:Lambda表达式可以形成闭包,这意味着它们可以访问并操作其定义时所在的词法作用域中的变量。必须谨慎处理这些变量的修改,以避免意外的副作用。
-
可调用性:Lambda表达式是可调用的对象,因此它们可以作为函数参数传递,也可以赋值给函数指针或
std::function
对象。但是,传递给它们的参数类型和返回类型必须与Lambda表达式的签名匹配。 -
类型推断:Lambda表达式的返回类型通常是自动推断的。如果Lambda体中的代码路径不一致(例如,有些路径返回int,有些路径返回float),则可能会导致编译错误。
-
异常安全性:如果Lambda表达式可能抛出异常,需要确保调用它的代码能够妥善处理这些异常。避免在可能抛出异常的代码块中使用Lambda,除非已经正确实现了异常处理。
-
可读性和可维护性:Lambda表达式可以提高代码的简洁性和灵活性,但过度使用可能会降低代码的可读性和可维护性。在适当的地方使用Lambda表达式,并在必要时添加注释来解释其行为。
-
线程安全性:如果Lambda表达式将在多线程环境中使用,必须确保它是线程安全的。这包括避免共享可变状态、使用线程安全的容器和算法,以及正确处理同步问题。
欢迎您同步关注我们的微信公众号!!!
标签:27,变量,int,捕获,C++,按值,表达式,Lambda From: https://blog.csdn.net/whccf/article/details/143506932