Block、闭包、lambda表达式和匿名函数

简介

在函数式编程时,经常会使用到闭包。在很多编程语言中,都有类似闭包的概念。比如Objective-C,Ruby中有Block,C++ 11、LISP、Python和Java中有lambda表达式,Lua和JavaScript中有闭包。
和普通函数相比,Lambda、closure和block是一个东西,只是不同语言的不同称呼,它们都是匿名函数。若匿名函数捕获了一个外部变量,那么它就是一个closure。
用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。顾名思义,所谓匿名函数就是不带有名称的函数。

作用

简单说,引入它们的作用有2个:

简洁

首先说简洁,匿名函数可以在其他函数内部声明与定义,不用另外写个命名函数。比如一个方法只在这一处使用,我们就没有必要创建方法然后调用,我们可以直接在其他函数内部声明定义,这样也无需在其他地方查找。

捕获变量

捕获变量就是让匿名函数可以使用匿名函数外定义的变量,但是匿名函数内的函数外变量是外部变量的一个克隆。Objective-C 中有__block,也可以直接改变外部变量的值,

Objective-C 中的 block

下面从 OC 的角度理解一下 block,在《Objective-C高级编程》中详细介绍了 block 的实现原理。我们可以利用clang这个工具将 block 代码转换成C++源码来分析。我们在 main.m 文件中实现一段简单的 block 代码:

int main () {
  void (^blk)() = ^() {
      int a = 0;
  };
  blk();
}

接下来,我们使用 clang -rewrite-objc main.m 命令转换为C++源码:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// block 的结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block 实现的方法,impl.FuncPtr 指向该结构体
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = 0;
 }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// main 函数
int main () {
  // 创建了一个__main_block_impl_0结构体的一个实例
  void (*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  // 通过函数指针 blk 调用函数 FnucPtr,传入的参数为指针 blk 本身
  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}

从源码中可以看到,block 本质是一个指针结构体,执行block,相当于执行block 里面 FnucPtr 里面的函数指针。

详细的资料参考《Objective-C高级编程》或者blog https://www.cnblogs.com/chenxianming/p/5554395.html

其他语言使用

匿名函数、lambda、closure在各个语言中的使用方式:

Objective-C

在ObjectC中,匿名函数被称为blocks(块),即可以改变捕获的原值、又可以捕获克隆、但不能改变克隆值的值。捕获并改变外部值,需要用__block,否则复制语句会报错,使用代码如下:

__block int a = 10;
int (^blockFunc)(int p) = ^(int p) {
    a += p;
    return a;
};

NSLog(@"blockFunc:%d, a:%d", blockFunc(4), a);

闭包可能会存在循环引用,需要使用弱引用.

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;

  if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

Swift

Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)

// 闭包表达式语法
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

// 根据上下文推断类型
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

// 单表达式闭包隐式返回
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// 参数名称缩写
reversedNames = names.sorted(by: { $0 > $1 } )

// 运算符方法
reversedNames = names.sorted(by: >)

闭包中也可能存在循环引用,Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例不保持强引用。这样实例能够互相引用而不产生循环强引用。

  • 如果捕获(比如 self)可以被设置为 nil,也就是说它可能在闭包前被销毁,那么就要将捕获定义为 weak。
  • 如果它们一直是相互引用,即同时销毁的,那么就可以将捕获定义为 unowned。

本质上来说,闭包作为一个引用类型,解决循环引用的方式和解决类对象之间的循环引用是一样的,如果引起循环引用的"捕获"不能为nil,就把它定义为unowned,否则,定义为weak。而指定“捕获”方式的地方,叫做闭包的薄或列表。

Javascript

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

JavaScript中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。在我们的例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用,而 displayName 实例仍可访问其词法作用域中的变量,即可以访问到 name。

Python

在Python中的匿名函数被称为lambda,只能捕获值,且不能改变值。以map()函数为例,计算f(x)=x^2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

匿名函数lambda x: x * x实际上就是:

def f(x):
    return x * x

匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

f = lambda x: x * x

同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容