lambda表达式是C++11很是重要也是很经常使用的特性之一,来源于函数式编程的概念,也是现代编程语言的一个特色。它有以下特色:编程
lambda表达式定义一个匿名函数,而且能够捕获必定范围内的变量,其定义以下:闭包
[captrue] (params) opt -> ret {body};
其中,capture是捕获列表;params是参数列表;opt是函数选项;ret是返回值类型;body是函数体。less
咱们来作一个简单的例子,定义一个简单的封包,用来将输入的结果+1并返回。编程语言
auto f = [](int a) -> int {return a + 1; }; std::cout << f(1) << std::endl;
lambda表达式的返回值经过返回值后置语法来定义,因此不少时候能够省略返回值类型,编译器根据return语句自动推导返回值类型。函数式编程
auto f = [] (int a) {return a + 1;};
可是初始化列表不能做为返回值的自动推导,须要显示给出具体的返回值类型。函数
auto f1 = [] (int a) {return a + 1;}; //ok,return type is int auto f2 = [] () {return {1, 2}}; //error:没法推导出返回值类型
lambda表达式在没有参数列表的时候,参数列表能够省略。this
auto f1 = [] () {return 1;}; auto f2 = [] {return 1;} //省略空参数表
lambda表达式能够经过捕获列表捕获必定范围内的变量:spa
class A { public: int mi = 0; void func(int x, int y) { auto x1 = []{return mi;}; //error,没有捕获外部变量 auto x2 = [=] {return mi + x + y;}; //ok,按值捕获全部外部变量 auto x3 = [&] {return mi + x + y;}; //ok,按引用捕获全部外部变量 auto x4 = [this] {return mi;}; //ok,捕获this指针 auto x5 = [this] {return mi + x + y;}; //error,没有捕获x,y auto x6 = [this,x,y] {return mi + x + y;}; //ok,捕获this,x,y auto x7 = [this] {return mi++;}; //ok,捕获this指针,并修改为员的值 } }; int a = 0, b = 2; auto f1 = [] {return a;} ; //error,没有捕获外部变量 auto f2 = [&] {return a++;}; //ok,按引用捕获全部外部变量,并对a执行自加运算 auto f3 = [=] {return a;}; //ok,按值捕获全部外部变量,并返回a auto f4 = [=] {return a++;}; //error,按值引用不能改变值 auto f5 = [a] {return a + b;}; //error,没有捕获b auto f6 = [a, &b] {return a + (b++);}; //ok,捕获a和b的值,并对b作自加运算 auto f7 = [=, &b] {return a + (b++);}; //ok,捕获全部外部变量和b的引用,并对b作自加运算
从例子中能够看到,lambda表达式的捕获列表精细的控制表达式可以访问的外部变量,以及如何访问这些变量。须要注意的是,默认状态下的lambda表达式没法修改经过复制方式捕获的外部变量,若是但愿修改这些变量,须要用引用的方式进行修改。可是按值引用的话,若是延迟调用,那么在调用该lambda表达式的时候,其捕获的变量值仍是在lambda定义的时候的值。指针
int a = 0; auto f = [=] {return a;}; //按值捕获外部变量 a += 1; //修改值 std::cout << f() << std::endl; //输出0
这个例子中,lambda表达式按值捕获了全部外部变量,在捕获的一瞬间a的值就已经被赋值在其中,以后a值修改并不会改变lambda表达中存的a的值,所以最终结果输出仍是0。若是但愿lambda表达式在调用的时候能即时访问外部变量,应当使用引用的方式捕获。code
若是但愿去修改按值捕获的外部变量,须要显示指明lambda表达式为mutable,可是被mutable修饰的lambda表达式有一个特色,就是不管有没有参数都要写明参数列表。
int a = 0; auto f1 = [=] {return a++;}; //error,不能修改按值捕获的变量 auto f2 = [=] mutable {return a++; }; //ok,mutable
lambda表达式实际上是一个带有operator()的类,即仿函数,所以咱们可使用std::bind和std::function来存储和操做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(),若是能直接转换为普通的函数指针,那lambda表达式自己的this指针会丢失,没有捕获任何外部变量的lambda表达式则不存在这个问题,因此按值捕获的外部变量没法修改。由于lambda表达式中的operator()默认是const的,一个const成员函数没法修改为员变量的值,而mutable则是取消operator()的const。
因此,没有捕获变量的lambda表达式能够直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。
typedef void(*Ptr)(int *); Ptr p1 = [](int *p) {delete p;}; //ok,没有捕获的lambda表达式能够转换为函数指针 Ptr p2 = [&](int *p){delete p;}; //error,有捕获的lambda表达式不能直接转换为函数指针,不能经过编译
就地定义匿名函数,再也不须要定义函数对象,大大简化了标准库的调用。好比咱们使用for_each将vector中的偶数打印出来。
class CountEven { private: int &micount; public: CountEven(int &count) : micount(count) {} void operator()(int val) { if(!(val & 1)) { ++micount; } } }; std::vector<int> v = {1, 2, 3, 4, 5, 6}; int count = 0; for_each(v.begin(), v.end(), CountEven(count)); std::cout << "The number:" << count << std::endl;
这样写比较繁琐,若是用lambda表达式,使用闭包概念来替换这里的仿函数。
std::vector<int> v = {1, 2, 3, 4, 5, 6}; int count = 0; for_each(v.begin(), v.end(), [&count] (int val) { if(!(val & 1)) { ++count; } });
在以前的例子中,使用std::bind组合多个函数,实现了计算集合中大于5小于10的元素个数。
using std::placeholders::_1; auto f = std::bind(std::logical_and<bool>(), std::bind(std::greater<int>(), std::placeholders::_1, 5), std::bind(std::less_equal<int>(), std::placeholders::_1, 10)); int count = std::count_if(coll.begin(), coll.end(), f);
经过lambda表达式能够轻松的实现相似的功能:
//查找大于5小于10的元素个数
int count = std::count_if(coll.begin(), coll.end(), [](int x) {return x > 5 && x < 10;})
lambda表达式比std::bind更加灵活和简洁,若是简单的逻辑处理,用lambda表达式来代替function,提高开发效率,效果会更好。