闭包
闭包是什么?——一个函数与其周围状态绑定在一起就是闭包。
概述
闭包通常出现在支持头等函数、垃圾回收的编程语言中,如果函数f中定义了函数g,函数g中使用了自由变量,则函数g构成闭包。
- 头等函数(First-class function):指函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。
- 自由变量(Free variables):自由变量是指在一个特定作用域内未被定义但被引用的变量。
- 约束变量(Bound variables):约束变量是指在一个特定作用域内已经被定义的变量。
以Python代码为例
1 | def f(): |
为什么提出闭包
最初闭包是为了解决函数式编程中一些问题,包括隐藏保存一些私有数据、保存上下文(例如计数器)、事件编程模型中的回调机制。这些封装私有、函数的组合等优秀功能同样契合面向对象语言,所以目前大多数语言都支持闭包。
闭包的用途
- 封装数据:闭包可以用于封装数据和行为,将相关的变量和函数组合在一起。它可以隐藏内部实现细节,而只暴露特定的接口,提供更清晰、模块化的代码结构。
- 保持状态信息:闭包可以存储状态信息,并在每次调用闭包时保持该状态。这对于需要记住先前的操作或保留某些值的场景非常有用。例如,在计数器应用程序中,闭包可以用来跟踪和增加计数器的值。
- 延迟计算:闭包可以延迟计算,即在需要的时候才进行实际的计算过程。这种惰性求值的特性可以提高性能和资源的使用效率,尤其在处理大数据集或复杂计算时特别有用。
- 实现私有变量和函数:通过闭包,我们可以创建具有私有成员的“类似”对象。它们可以包含仅在闭包内部可见的私有变量和函数,并提供对外部用户公开的接口。这样可以实现信息隐藏和封装的概念。
- 高阶函数和装饰器:闭包是实现高阶函数和装饰器的基础。高阶函数是接受函数作为参数或返回函数的函数,而装饰器是用于修改或增强其他函数行为的函数。闭包提供了一种方便的方式来实现这些功能,允许动态地添加额外的逻辑或功能。
对于Python来讲,几乎所有的装饰器都是闭包(除了下面这种情况,几乎也没人会这样写)。
1 | def test_closure(func): |
不支持垃圾回收的语言中的闭包
闭包会延长函数内部变量的生命周期,因此对于不支持垃圾回收的语言容易发生内存泄漏。本质上来说是将闭包函数中绑定的自由变量置于堆中,因此对于这类语言,存在一个问题,栈中的变量如何复制到堆中。
- 在C语言中,并不支持闭包,只能以结构体来模拟闭包
1 | #include <stdio.h> |
- 在C++98中也没有严格意义上的闭包,但是可以通过重载
()
运算符来将对象模拟闭包函数。 - 在C++11中,通过匿名函数来创建闭包,因为Lambda表达式可以捕获周围的变量。但是对于两个闭包共同使用一个自由变量时,不能完成真正的共享,因为本质上是对栈中的变量进行值拷贝。当使用引用拷贝时虽然可以完成共享,但仅限于在该自由变量的生命周期内,若自由变量所在函数出栈,该闭包将引发报错。因此对于这种情况不仅要使用引用拷贝,还要将其声明为
static
变量或者对于x直接采用智能指针。 - 在C++14中,可以将自由变量绑定到匿名函数对象中。
Lambda表达式
1 | int x = 5; |
默认情况下,在{ ... }
函数体内,只能以只读的方式访问x
变量。若要对x
进行修改,需要使用mutable
关键字修饰
1 | int x = 5; |
此外对于[]
中的捕获方式分为值捕获和引用捕获,具体语法如下:
[]
不捕获任何变量[var]
指定值捕获变量[&]
引用捕获周围全部外部变量[=]
值捕获周围全部外部变量[&, var]
所有变量按引用捕获,var
变量按值捕获[=, &var]
所有变量按值捕获,var
变量按引用捕获
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.