导航:
lambda函数介绍和个人理解(2)——lambda与仿函数
lambda函数介绍和个人理解(3)——lambda的语法甜点
什么是lambda函数?
其实,lambda函数我个人更愿意称为lambda运算(lambdacalculus),它是用来表示一种匿名函数。这个严格意义上属于“函数式编程”(Functional
Programming)的范畴。当然还是先解释下函数式编程的概念的好。按照当时冯·诺依曼机器的基本计算模型,所谓通过一条条机器指令逐步把输入数据加工成最终的计算结果,而在计算过程中会有大量的内存单元被反复修改。函数式编程的思想就是把所有的这些状态看做一个全集,程序的基本组成部分就是这些状态之间的映射,其实也就是广义上的函数(广义上的函数的概念是,只要存在广义上的集合A,B,并且存在从A映射到B的映射关系,这个映射关系就被称作广义上的函数。在计算机编程领域,广义的函数包含过程,也就是Pascal语言中有名的Procedure)。通过这些函数之间的复杂运算,把输入数据映射到最终结果,这就是函数式编程。回到一开始的lambda运算,lambda运算其实我们就可以理解成一个简单的运算方法,就像加法减法乘法除法一样,就是一个运算规则:
lambda运算 := 传入数据 –>一系列的运算 -> 传出结果
这就是神龙见首不见尾的lambda运算。他其实就是一个运算规则,说的高雅点,叫做lambda算子(lambda
operator)。这时候,如果我们结合理解函数式编程的理念,我们就知道为什么这个东西叫lambda函数了,因为满足我上文中所讲到的广义函数的定义啊!广义集合A(传入数据)和广义集合B(传出数据)还有他们的映射关系(一系列的运算),所以其实这个运算就是一个函数。至此,我们终于了解了为什么叫lambda函数。
“匿名的”?匿名的!
可是为什么又要提到,lambda函数是一个匿名函数呢?什么叫“匿名的”函数呢?一般而言,我们都知道,所谓的函数,不论是哪一种编程语言,都要给自定义的函数进行命名处理,看似天经地义。然而只有一种例外,那便是lambda函数。lambda函数没有名字,对的,没有函数名。你可能要问:不对啊?你刚刚不是说,这个lambda函数满足上述的广义函数定义,他不就是一个函数么?
其实这个问题不难解决。没错,他是函数,但没有任何人规定:“一个函数,必须有一个名字与其对应。”你肯定会说,呵呵!你又跟我玩文字游戏。那你解释下lambda函数他究竟是个什么?OK!交给我了:
先给大家看几个典型的lambda函数使用的例子:
/****
*@PoloShen
*Title: lambda 01
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main(){
auto total = [](int a, int b)->int{return a+b; }; //生成一个lambda函数,求两数之和
cout<<total(3,5)<<endl; //输出8, 3+5=8
vector<int> nums;
for (int i = 0; i < 5; i++) nums.push_back(i); //将nums初始化
auto print = [&]{ //生成一个lambda函数,用于打印nums
for (auto s: nums){cout<<s<<"\t";}
cout<<endl;
};
print(); //打印nums
for_each(nums.begin(), nums.end(), [=](int &i){ //将nums每一个元素加10
i += 10;
});
print(); //再次打印nums
return 0;
}
//编译选项:: g++ -std=c++11 lambda01.cpp
对我们这些程序员而言,lambda函数是一个“不定态事物”,它既可以写成一个类似变量的形式,又可以写在函数指针的接口上,还可以当做一个类似于“局部函数”的东西使用。但实际上,从最简单易懂的地方去理解,lambda函数只会出现在两个地方:变量等号后面,还有函数指针的接口上。例子1中,变量total和变量print后面就分别接了一个lambda函数,分别用于计算两数之和和打印数表;另外一个就是在for_each方法中的那个lambda函数,用于使数表中的元素值增加10。看上去,total、print就像他们的函数名一样,但实际上则不然。严格意义上,他们依然是变量,只不过这个类型很复杂;但是实际上,他们就是一个“传令兵”,只是在引用中免得你找不到而已。更多情况下,按照所谓的函数式编程的理念,“万物皆函数”(笑),lambda函数更多的应当出现在最需要他的地方,也就是有函数指针的地方。所以不必在意他是什么名字,只需要考虑如何好好地完成任务即可。就比方说我们看到的在for_each中所使用的lambda函数一样,不用关心他的名字是什么,我们关心的是实现这个运算的方法。如此一来就不难理解lambda函数的匿名性了。
至此,最难啃的两块骨头已经被我们吃下去了,剩下的就是慢慢消化。
lambda函数的语法定义
lambda函数的语法定义如下:
[capture](parameters) mutable -> return-type{statsment}
其中
- [capture]捕捉列表。捕捉列表总是出现在lambda函数的开始处。事实上,[]是lambda函数的引出符。编译器根据该引出符判断接下来的代码是否是lambda函数。捕捉列表能够捕捉上下文中的变量以供lambda函数使用。
- (parameters)参数列表。其实就是和普通额函数参数列表一致。如果不需要参数传递,则可以连同括号一起省略。
- mutablemutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略,即使参数为空。
- ->return-type返回类型。同追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值的时候也可以连同符号->一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器自行对返回类型进行推导。
- {statsment}函数体。内容与普通函数一样,不过除了可以使用的参数之外,还可以使用所有捕获的变量。
以上便是lambda函数的定义了。和其他常规的函数不同,lambda函数可以去使用通过捕获列表中的数据。语法上,捕获列表有多个捕捉项组成,并以逗号分隔。以下便是捕获列表的形式:
- [var]表示值传递方式捕获变量var
- [=]表示值传递方式捕获所有父作用域的变量(包括this)
- [&var]表示引用传递方式捕获变量var
- [&]表示引用传递方式捕获所有父作用域的变量(包括this)
- [this]表示值传递方式捕获当前的this指针
但是请注意!捕获列表不允许变量的重复传递。比如说:
- [=, a] 重复捕获a,错误。
- [&, &this] 重复捕获this,错误
Ok,到这里,所有语法上的lambda函数说明都完成了,给一些简单的例子,然后慢慢畅游吧!
/****
*@PoloShen
*Title: lambda 02
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main(){
[]{}; //最简lambda函数
int a = 3, b = 4;
[=]{return a+b;}; //省略参数列表和返回类型
auto fun1 = [&](int c){b = a + c;}; //省略了返回类型,无返回值
auto fun2 = [=, &b](int c)->int{return b += a + c;};//各部分都很完整的lambda函数
auto fun3 = [a, &b]()->int{return a+b;}; //其他传递方式的例子
return 0;
}
//编译选项:: g++ -std=c++11 lambda02.cpp