匿名函数--lambda函数
匿名函数或者匿名类这种语法在其他语言(如lisp,java中)早有应用。在C++11中正式引入了lambda函数,在很多场景下让程序变得更加直观,下面我们将C++中的lambda函数语法规则做一个梳理。
首先先看一段代码,直观感受一下lambda的使用。
#include <iostream>
template <typename T, typename R>
void foo(T func, R param)
{
func(param);
}
void bar(int param)
{
std::cout << "function param is :" << param << std::endl;
}
struct foobar
{
void operator ()(int param)
{
std::cout << "functor param is :" << param << std::endl;
}
};
int main()
{
foo([](int param)->void
{
std::cout << "lambda param is :"<<param<<std::endl;
}, 123);
foo(bar, 456);
foo(foobar(), 789);
return 0;
}
上面代码展示了一个模板函数接受三类可执行代码(函数、仿函数和lambda函数)的示例。foo为模板函数,它接受两个参数,第一参数是可执行代码,第二参数是第一参数的参数。我们的调用代码展示了它的用法,它可以接受lambda函数、普通函数和仿函数。可以看出,接受lambda函数版本的调用直接在调用处展开,这种方式比较直观,如果习惯了java语言的同学应该更有体会。
lambda的基础语法定义如下:
[capture](parameters) mutable ->return-type{statement}
其中,
1、[capture]:捕获列表。它总是出现在lambda函数的开始位置。在编译器看来[]是lambda的引出符号,编译器正式通过它来判断接下来的代码是否是lambda函数。捕获列表能够捕捉当前上下文中的变量供给lambda函数使用。具体的capture列表中的语法,下面还会详细讲述。
2、(parameters):参数列表。它跟一般函数的参数列表一样,使用规则也相同。在lambda中,如果不需要传入参数,可以省略。
3、mutable:修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消它的常量属性。显示指定mutable修饰符的时候,参数列表不能省略。
4、->return-type:返回值类型。->这个同C++11新引入的追踪返回值类型的声明是一致的,语法也是一致的。不同的是,处于方便,lambda函数在没有返回值的情况下,可以省略掉(在某些编译器可以推导出返回值类型的情况亦可省略)。
5、{statement}:函数体。与一般函数的函数体一致,额外可以使用捕获列表中捕获的变量。
上面就是lambda函数的语法,可以看出2和3、4都是可选的,所以一个lambda函数可以简化成为下面的代码
[]{std::cout<<"Hello Lambda\n";}
lambda函数相较普通函数调用最便捷之处就是其捕获列表,它可以通过值传递捕获或者引用传递方式捕获,直接在函数体内访问到上下文(一个代码块内)的变量。如果是普通函数的话,这些都要以参数形式传递进去,使代码十分冗长。那么捕获列表的具体语法可以归纳如下:
1、[a]表示值传递捕获变量a(多个参数可以用逗号分隔)
2、[=]表示值传递捕获上下文所有变量
3、[&a]表示引用传递捕获变量a
4、[&]表示引用传递捕获上下文所有变量
5、[this]表示值传递捕获当前的this指针
6、[=, &a, &b]表示值传递捕获上下文所有变量,但是a、b变量以引用传递方式捕获。
7、[&, a, this]表示引用传递捕获上下文所有变量,但是a和this指针以值传递方式捕获。
char c = 'a';
float d = 1.11f;
foo([=](int param)->void
{
std::cout << "lambda param is :"<< param <<std::endl;
std::cout << "lambda cap is :" << c << std::endl;
std::cout << "lambda cap2 is :" << d << std::endl;
}, 123);
上面代码展示了捕获列表的值传递用法,其余方式大同小异。
lambda函数和仿函数
在C++98STL库的实现中,大量的函数需要传入回调,而这些回调基本都是以模板参数的形式传入。这样的好处是既可以接受C编程习惯中的回调函数,又可以接受C++方式的functor(仿函数)。现在有了lambda函数,我们有了第三种选择。文章最开始的例子实际上已经展示了三种方式传入给模板函数的情况,我们现在以STL中的for_each为例再来看一下lambda函数和仿函数之间的对比。
struct for_each_functor
{
void operator ()(int param)
{
std::cout << param + 5 << std::endl;
}
};
int main()
{
std::vector<int> vec_foo;
for (int i = 0; i < 10; ++i)
{
vec_foo.push_back(i);
}
std::for_each(vec_foo.begin(), vec_foo.end(), for_each_functor());
std::for_each(vec_foo.begin(), vec_foo.end(),
[](int param)->void
{
std::cout << param + 15 << std::endl;
}
);
return 0;
}
看一下上面的代码,两种for_each的调用,是不是感觉lambda版本的调用看起来更加简洁,并且实现起来也比functor版本简洁了一些。
最后
lambda函数的语法比较简单,但是它跟实际应用结合在一起就会发挥很大的作用,给程序编写带来很大的便利。后面我们将会介绍的基于C++11的线程池中,会极致利用lambda函数的便利性,使得我们可以用很少的代码编写一个极其方便、功能极其强大的线程池类。
网友评论