1.6 lambda表达式
来源于函数式编程的概念,优点如下:
1 声明式编程风格,就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
2 简洁。
1.6.1 lambda表达式的概念和基本用法
表达式语法:
[ capture ] ( params ) opt -> ret { body; };
capture是捕获列表;params是参数表;opt是函数选项;ret是返回值类型;body是函数体。
举例:
auto f = [](int a) -> int { return a+1; };
// 2
std::cout << f(1) << std::endl;
C++11中允许省略lambda表达式的返回值定义,可自动推导:
auto x1 = [](int i){ return i; };
注意,初始化列表不能用于返回值的推导:
// error
auto x2 = []() { return {1, 2}; };
另外,lambda表达式在没有参数列表时,可以省略参数列表。
auto f1 = []() { return 1; };
auto f2 = [] { return 1; };
捕获列表:
[] 不捕获任何变量
[&] 捕获外部作用域中所有变量,作为引用在函数体内使用
[=] 捕获外部作用域中所有变量,作为副本在函数体内使用
[=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
[bar] 按值捕获bar变量,不捕获其他变量
[this] 捕获类中的this指针,目的是可以在lambda中使用当前类的成员函数和变量,如果已经使用了&或=,则默认添加此选项
class A
{
public:
int i_ = 0;
void func(int x, int y)
{
// error,没有捕获i_
auto x1 = []{ return i_; };
// ok,捕获所有外部变量
auto x2 = [=]{ return i_ + x + y; };
// ok,捕获所有外部变量
auto x3 = [&]{ return i_ + x + y; };
// ok,捕获this指针
auto x4 = [this]{ return i_; };
// error,没有捕获x,y
auto x5 = [this]{ return i_ + x + y; };
// ok,捕获this,x,y
auto x6 = [this,x,y]{ return i_ + x + y; };
// ok,捕获this,并修改了i_成员的值
auto x7 = [this]{ return i_++; };
}
};
容易出错的例子:
int a = 0;
auto f = [=]{ return a; };
a += 1;
// 输出:0
std::cout << f() << std::endl;
捕获时,a的值已经复制到f中了,之后f中存储的a仍然是捕获时的值。如果希望lambda表达式在调用时能够即时访问外部变量,应当使用引用方式捕获。
如果希望修改按值捕获的外部变量,需要显式指明lambda表达式为mutable:
int a = 0;
//error, 修改按值捕获的外部变量
auto f1 = [=]{ return a++; };
//ok, mutable
auto f2 = [=]() mutable { return a++; }
注意,被mutable修饰的lambda表达式就算没有参数也要写明参数列表。
lambda表达式类型在C++11中被称为"闭包类型",可以认为它是一个带有operator()的类,即仿函数。因此,我们可以用std::function和std::bind来操作lambda表达式:
std::function<int(int)> f1 = [](int a){ return a; };
std::function<int(void)> f2 = std::bind([](int a){ return a; }, 123);
另外,对于没有捕获任何变量的lambda表达式,可以被转换为一个普通的函数指针:
using func_t = (int)(*)(int);
func_t f = [](int a){ return a };
f(123);
解释下为何按值捕获无法修改捕获的外部变量。因为lambda表达式额operator()默认是const的,一个const成员函数是无法修改成员变量的值的。而mutable的作用是取消operator()的const。
1.6.2 声明式编程风格,简洁
简化了标准库算法的调用,例如在C++11之前,要调用for_each函数将vector中的偶数打印出来:
class CountEven
{
int& count_;
public:
CountEven(int& count_): count_(count)
{}
void operator()(int val)
{
if (val % 2 ==0)
{
++count_;
}
}
};
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
int even_count = 0;
for_each(v.begin(), v.end(), CountEven(even_count));
std::cout << even_count << std::endl;
使用lambda后:
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
int even_count = 0;
for_each(v.begin(), v.end(), [&even_count](int val)
{
if (val % 2 ==0)
{
++even_count;
}
});
std::cout << even_count << std::endl;