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
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值