C++ Lambda表达式
什么是Lambda表达式呢?λ演算(λ-calculus)是一个形式化的数学逻辑系统,它基于函数的抽象和应用,使用变量绑定和替换来表示计算。具体请参考:λ-calculus
从C++11开始可以使用Lambda表达式创建匿名函数对象(闭包),它可以作为一个参数传递给另一个函数。语法如下:
1.捕获从句
1.[ ]是捕获从句,lambda可以访问或捕获周围作用域的变量。在c++ 14中,还可以在其函数体中引入新变量。lambda表达式必须以[]开始。用它来指定捕获哪些变量并指明捕获是通过值还是通过引用。有&前缀的变量会以引用的方式被访问,否则就是以值的方式来访问。
- [ ],表示lambda表达式的函数体内不访问任何外部变量。如果此时在函数体内访问了lambda表达式的函数体作用域外的变量就会报错。 如:
int main(int argc, char *argv[])
{
...
Car car("BYD",90000);
int m = 0;
int n = 0;
[] (int a) mutable {
m = ++n + a + car.getPrice(); }(4); // lambda函数体不能捕获m和n变量,因此会报错
...
}
-[arg1,arg2,…],我们也可以指定要捕获的变量和方式(引用或值,若是引用方式就是在变量前加&,函数体对引用变量的改变会影响原值),如
int main(int argc, char *argv[])
{
...
Car car("BYD",90000);
int m = 0;
int n = 0;
[&m,n,car] (int a) mutable {// 以引用方式引用外部变量m,所以m的改动会影响原值,
// n和car都是以值的形式,所以它们的改动不会影响原值。
m = ++n + a + car.getPrice(); }(4);
...
}
- [&]和[=],我们指定一个默认模式去捕获外部变量。[&]表示以引用方式捕获所有外部变量,在函数体中对变量做的操作会影响原值,[=]就是以值方式捕获所有外部变量,在函数体中对变量做的操作不会影响原值。
int main(int argc, char *argv[])
{
...
Car car("BYD",90000);
int m = 0;
int n = 0;
[&] (int a) mutable {// 以引用方式引用外部变量,改动变量会影响原值,
m = ++n + a + car.getPrice(); }(4);
[=] (int a) mutable {// 以值方式引用外部变量,改动变量不会影响原值,
m = ++n + a + car.getPrice(); }(6);
...
}
当指定了默认捕获模式后,也可以用相反模式指定某个变量的捕获,如:
int main(int argc, char *argv[])
{
...
Car car("BYD",90000);
int m = 0;
int n = 0;
[=,&m,&car] (int a) mutable {// 默认以值模式捕获外部变量,对于外部变量m和car则使用引用模式捕获。
m = ++n + a + car.getPrice(); }(4);
...
}
这里还要注意一点,当指定了默认捕获模式,只有在lambda函数体用到的外部变量才会被捕捉。
如果捕获从句(即[ ])中已指定了默认捕获模式为&,即引用,那么[ ]中就不能再指定任何& identifier
,因为指定的默认模式&中,已包含此。例子:
[&, &i]{}; // ERROR: i preceded by & when & is the default
同理,如果指定了默认捕获模式为=
,那么也不能在[]指定任何= identifier
:
[=, this]{}; // ERROR: this when = is the default
一个标识符或this指针在[]捕获从句中只能出现一次,例子:
[=, *this]{ }; // OK: captures this by value. See below.
[i, i]{}; // ERROR: i repeated
对于可变参数的捕获:一个捕获加上一个省略号是一个包的扩展:
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
要在类成员函数体中使用lambda表达式,就要将this指针传递给capture子句,以提供对封闭类的成员函数和数据成员的访问。
int Car::getPrice(){
[this] (int a) mutable {
int aw = price;
}(4);
return price;
}
在c++17之后,this 指针可以通过值的方式被捕获,即[*this]。通过值方式的捕获会拷贝整个闭包到lambda调用处。 所谓的闭包就是匿名函数对象,这个对象封装了lambda表达式。当lambda表达式并行执行或异步操作时,通过值方式的捕获非常有用。
在多线程使用lambda表达式时,要注意:
- 引用型捕获可以改变外部变量,但是值捕获不会。mutable关键字表明允许修改副本,但不允许修改原始副本
- 引用型捕获会引入生命周期依赖,但是值方式的捕获没有生命周期依赖问题。特别重要的一点是当lambda表达式异步执行,如果在异步lambda中通过引用捕获局部变量, 那么那个局部变量随着lambda的运行很容易就被销毁了。我们的代码可能就会在运行时导致访问冲突。
在c++ 14中,可以在capture子句中引入和初始化新变量,而不需要让这些变量存在于lambda函数的封闭作用域中。初始化可以表示为任意表达式,新变量的类型由表达式产生的类型推导而来。该特性允许从周围的作用域捕获仅需移动的变量,并在lambda中使用它们。
QString *ptr;
auto a = [ptr=new QString()]()
{
// use ptr
};
2.参数列表
Lambdas不仅可以捕获变量,还可以接受输入参数. 参数列表对于lambda表达式来说是可选,就是说不一定要有。
auto y = [] (int first, int second)
{
return first + second;
};
在c++ 14中,如果参数类型是泛型的,可以使用auto关键字作为类型说明符。该关键字告诉编译器将函数调用操作符创建为模板。参数列表中auto的每个实例等效于一个不同的类型参数。
auto y = [] (auto first, auto second)
{
return first + second;
};
另外,lambda表达式也可以将另一个lambda表达式作为它的参数。因为参数列表是可选的,所以如果lambda表达式不用传参数,并且lambda表达式的声明符中不包含异常声明、返回类型或mutable关键字,那么括号可以省略。
3.mutable
lambda以值方式捕获的变量,lambda函数体内默认是const的,即不能修改它们。mutable关键字就是取消这一点。加上mutable关键字,能够在函数体内修改以值方式捕获到的变量的副本。
int m = 0;
int n = 0;
[&m,n] (int a) {
m = ++n + a;// 没有mutable关键字,所以++n会报错,因为n是const的。
}(5);
加上mutable关键字就可以修改n的副本的值了。
int m = 0;
int n = 0;
[&m,n] (int a) mutable {
m = ++n + a;// 没有mutable关键字,所以++n会报错,因为n是const的。
}(5);
4.exception
我们以用noexcept表明lambda表达式不会抛出任何异常。
5.Return type
指定lambda表达式的返回类型。lambda表达式的返回类型是自动推导的。如果函数体只有一条return语句,那么会自动推导出返回类型。
auto x1 = [](int i){ return i; }; // OK: return type is int
6.Lambda body
lambda函数体实现。函数体可以访问以下这些变量:
- 从封闭区域捕获到的变量
- 参数
- lambda函数内部本地定义的变量。
- 类成员,当在一个类里定义了lambda表达式,且表达式里捕获了this。
- 任何有静态存储期的变量,如全局变量
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/126458133
- 点赞
- 收藏
- 关注作者
评论(0)