什么是Lambda表达式呢?λ演算(λ-calculus)是一个形式化的数学逻辑系统,它基于函数的抽象和应用,使用变量绑定和替换来表示计算。具体请参考:​​λ-calculus​​ 从C++11开始可以使用Lambda表达式创建匿名函数对象(闭包),它可以作为一个参数传递给另一个函数。语法如下:

C++ 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表达式时,要注意:

  1. 引用型捕获可以改变外部变量,但是值捕获不会。mutable关键字表明允许修改副本,但不允许修改原始副本
  2. 引用型捕获会引入生命周期依赖,但是值方式的捕获没有生命周期依赖问题。特别重要的一点是当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函数体实现。函数体可以访问以下这些变量:

  1. 从封闭区域捕获到的变量
  2. 参数
  3. lambda函数内部本地定义的变量。
  4. 类成员,当在一个类里定义了lambda表达式,且表达式里捕获了this。
  5. 任何有静态存储期的变量,如全局变量