什么是Lambda?

C++ 11增加了一个很重要的特性——Lambda表达式。营里(戴维营)的兄弟都对Objective-C很熟悉,很多人多block情有独钟,将各种回调函数、代理通通都用它来实现。甚至有人选择用FBKVOController、BlocksKit等开源框架将KVO、控件事件处理都改为通过block解决。原因就是简单、方便、直观,函数的定义和使用出如今同一个地方。这里的Lambda表达式实际上和block很类似,当然假设你用它和Swift语言的闭包比較,那就是一回事了。以下先看几个Lambda和block的演示样例代码。

1 .Objective-C的block演示样例代码,使用​​^​​表示block类型,整体来说与函数指针的定义类似。



#import <Foundation/Foundation.h>

int main()
{
void (^block)() = ^void() {
NSLog(@"In block");
};

block();
return 0;
}


编译执行:



$ clang main.m -framework Foundation
$ ./a.out
2015-01-28 14:17:52.763 a.out[9901:165707] In block


2 .Swift的闭包,參数列表、返回值类型等都写在花括号​​{}​​内部。



let closure = {
() -> Void in
println("In swift")
}

closure()


编译执行:



$ swiftc main.swift 
$ ./main
In swift


測试Swift也能够直接使用Playground或REPL(Read-Eval-Print-Loop)环境。

3 .C++的Lambda表达式。



#include <iostream>

int main()
{
auto lambda = []() -> void{
std::cout << "In lambda" << std::endl;
};
lambda();
return 0;
}


编译执行,注意使用C++ 11的特性进行编译:



$ clang++ main.cpp -std=c++11
$ ./a.out
In lambda


Lambda、block实际上都是一个闭包(closure),它们都类似于一个匿名的函数,可是拥有捕获所在作用域中变量的能力;可以将函数做为对象一样使用。通经常使用它们来实现回调函数、代理等功能。

Lambda用法

1 .基本形式

语法

序号

​[ 捕获列表 ] ( 形參数列表 ) mutable(可选) 异常属性 -> 返回值类型 { 函数体 }​

(1)

​[ capture-list ] ( params ) -> ret { body }​

(2)

​[ capture-list ] ( params ) { body }​

(3)

​[ capture-list ] { body }​

(4)


  • (1)为完整的形式,包括变量捕获列表、形參列表、可变属性(可选)和返回值类型等。
  • (2)省略了mutable,表示Lambda不能改动捕获的变量。
  • Lambda的返回值类型假设能够由函数体中的实际返回值推导出,能够省略。
  • 假设没有形參,能够省略圆括号。


2 .捕获列表

lambda表达式中能够获取(捕获)它所在作用域中的变量值,而且有两种捕获方式:引用和值。我们能够在捕获列表中设置各变量的捕获方式。假设没有设置捕获列表,lambda默认不能捕获不论什么的变量,这点与block不同。



#include <iostream>

int main()
{
int a = 123;

auto lambda = []()->void{
std::cout << "In lambda: " << a << std::endl;
};
lambda();

return 0;
}


编译执行:



$ clang++ main.cpp -std=c++11
main.cpp:8:33: error: variable 'a' cannot be implicitly captured in a lambda
with no capture-default specified
std::cout << "In lambda: " << a << std::endl;
^
main.cpp:5:6: note: 'a' declared here
int a = 123;
^
main.cpp:7:16: note: lambda expression begins here
auto lambda = []()->void{
^
1 error generated.


在​​[]​​​中设置捕获列表,就能够在lambda中使用变量​​a​​​了,这里使用按值(​​=​​, by value)捕获。



#include <iostream>

int main()
{
int a = 123;

auto lambda = [=]()->void{
std::cout << "In lambda: " << a << std::endl;
};
lambda();

return 0;
}


编译执行:



$ clang++ main.cpp -std=c++11
$ ./a.out
In lambda: 123


捕获列表的设置方式:

|设置方式|结果| |-|-| |[]|不捕获| |[=]|所有按值捕获| |[&]|所有按引用捕获| |[a, b]|按值捕获变量a和b| |[&a, b]|按引用捕获a,按值捕获b| |[&, a]|按值捕获a,其他变量所有按引用捕获| |[=, &a]|按引用捕获a,其他所有按值捕获|

注意: 捕获列表没有先后顺序;捕获列表中的变量不能出现反复申明,比方​​[&, &a]​​ ​​在​​&​​​中已经包括了​​&a​​​的申明了,因此不能再出现​​&a​​。

详细的捕获类型,能够通过打印变量地址进行查看。

  • 按值捕获:



auto lambda = [=]()->void{
std::cout << "In lambda: " << &a << std::endl;
};


执行结果为:



$ clang++ main.cpp -std=c++11
$ ./a.out
0x7fff555c69b8
In lambda: 0x7fff555c69b0


  • 按引用捕获:



auto lambda = [&]()->void{
std::cout << "In lambda: " << &a << std::endl;
};


执行结果为:



$ clang++ main.cpp -std=c++11
$ ./a.out
0x7fff58a9b9b8
In lambda: 0x7fff58a9b9b8


3 .可变类型(mutable)

按值传递到lambda中的变量,默认是不可变的(immutable),假设须要在lambda中进行改动的话,须要在形參列表后加入​​mutable​​keyword(按值传递无法改变lambda外变量的值)。



#include <iostream>

int main()
{
int a = 123;
std::cout << a << std::endl;

auto lambda = [=]() mutable ->void{
a = 234;
std::cout << "In lambda: " << a << std::endl;
};
lambda();

std::cout << a << std::endl;

return 0;
}


编译执行结果为:



$ clang++ main.cpp -std=c++11
lishan:c_study apple$ ./a.out
123
In lambda: 234 #能够改动
123 #注意这里的值,并没有改变


假设没有加入​​mutable​​,则编译出错:



$ clang++ main.cpp -std=c++11
main.cpp:9:5: error: cannot assign to a variable captured by copy in a
non-mutable lambda
a = 234;
~ ^
1 error generated.


4 .返回值类型。

lambda的返回值类型能够省略,编译器会依据实际返回值的类型自己主动推导。



#include <iostream>

int main()
{
int a = 123;
std::cout << a << std::endl;

auto lambda = [=]() mutable{
a = 234;
std::cout << "In lambda: " << a << std::endl;
return a;
};
int b = lambda();

std::cout << b << std::endl;

return 0;
}


编译执行:



$ clang++ main.cpp -std=c++11
$ ./a.out
123
In lambda: 234
234