废话不说,直接上定义:
1 | [capture](params) -> ret {something;} |
其中 capture 是捕获列表,params 是参数表,opt 是函数选项,ret 是返回值类型,something是函数体。
二话不讲,上个例子:
1 | auto f = [](int a) -> int { return a + 1; }; |
显然嘛,C++11嘛,类型推导比较简单,所以稍微简化一下:
1 | auto f = [](int a){ return a + 1; }; |
那要是没参数,写[](){return x;}
不是怪怪的嘛,所以括号也省了吧
1 | auto pi = []{ return 3.1415926535; }; |
好,那啥事lambda捕获列表呢?
lambda 表达式可以通过捕获列表捕获一定范围内的变量:
[]
不捕获任何变量。[&]
捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。[=]
捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。[=,&foo]
按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。[bar]
按值捕获 bar 变量,同时不捕获其他变量。[this]
捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
好,上例子:
1 | class A |
另外要记得捕获是瞬间完成的
1 | int a = 0; |
那么如果希望去修改按值捕获的外部变量应当怎么办呢?这时,需要显式指明 lambda 表达式为 mutable:
1 | int a = 0; |
需要注意的一点是,被 mutable 修饰的 lambda 表达式就算没有参数也要写明参数列表。
最后,介绍一下 lambda 表达式的类型。
lambda 表达式的类型在 C++11 中被称为“闭包类型(Closure Type)”。它是一个特殊的,匿名的非 nunion 的类类型。
因此,我们可以认为它是一个带有 operator() 的类,即仿函数。因此,我们可以使用 std::function 和 std::bind 来存储和操作 lambda 表达式:
1 | std::function<int(int)> f1 = [](int a){ return a; }; |
另外,对于没有捕获任何变量的 lambda 表达式,还可以被转换成一个普通的函数指针:
1 | using func_t = int(*)(int); |
lambda 表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终均会变为闭包类型的成员变量。而一个使用了成员变量的类的 operator(),如果能直接被转换为普通的函数指针,那么 lambda 表达式本身的 this 指针就丢失掉了。而没有捕获任何外部变量的 lambda 表达式则不存在这个问题。
这里也可以很自然地解释为何按值捕获无法修改捕获的外部变量。因为按照 C++ 标准,lambda 表达式的 operator() 默认是 const 的。一个 const 成员函数是无法修改成员变量的值的。而 mutable 的作用,就在于取消 operator() 的 const。
需要注意的是,没有捕获变量的 lambda 表达式可以直接转换为函数指针,而捕获变量的 lambda 表达式则不能转换为函数指针。看看下面的代码:
1 | typedef void(*Ptr)(int*); |
上面第二行代码能编译通过,而第三行代码不能编译通过,因为第三行的代码捕获了变量,不能直接转换为函数指针。