本文最后更新于:星期二, 八月 2日 2022, 9:32 晚上

闭包是什么?如果你与我有同样的疑问,敬请阅读。

什么是闭包?

这个问题困扰了我很长时间。

第一次接触闭包这个概念,是在“形式语言”这门课上。好像“离散数学”这门课上也教过闭包,但是这都不重要,因为我们这里讨论的闭包与数学上的闭包没什么关系。本文讨论的闭包,是程序设计语言中的闭包。

专业概念:

闭包是在其词法上下文中引用了自由变量的函数,自由变量是指除局部变量以外的变量。

又有一种说法是闭包不是函数,而是由函数和与其相关的引用环境组合而成的实体。

维基百科)的解释:闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。

看到这里我彻底懵逼了。是是是,你们说的都对!

身为新手小白,我需要通过判断闭包是做什么的,之后再讨论为什么叫做闭包。

闭包有什么用?

如果你是从C++来的,那么阅读下面没有什么障碍。如果不是也没有关系,反正各种语言的设计原理都是类似的,只要你掌握的语言有匿名函数的功能即可。

我们都知道,C++11标准引入了lambda表达式,就是一个匿名函数。这个函数长成这样:

[](const string&a, const string&b) {
    return a.size() < b.size();
};

上面的这个匿名函数负责比较两个字符串的大小。匿名函数的好处就是节省代码。

比如我现在想要实现自定义字符串排序函数,按照字符串长度从小到大排序,而不是按照字典排序。这个排序函数就可以用lambda表达式定义。

stable_sort(words.begin(), words.end(), 
            [](const string&a, const string&b) {
                return a.size() < b.size();
};)

lambda前面的中括号是干啥的?是用来捕获外部变量的。比如我想判断字符串长度有没有大于阈值threshold,这个threshold是在函数外面定义的。按照C++的语法,一般的函数不能访问函数外部的变量。但是lambda可以把外部的变量“捕获”,就像下面这样:

int threshold = 10;
[threshold](const string& a) {
    return a.size() > threshold;
};

可以看到,这个lambda不但使用了lambda内部的变量和参数,而且还“偷取”了不属于它的全局变量threshold。我们把lambda表达式定义的这种函数叫做闭包。

为什么叫做闭包?

有人说这不是脑子有坑吗,闭包哪里“闭”了?这明明比普通函数更“开放”好吧?是不是名字起错了?

其实不然。闭包并不是对内部封闭,而是给当前外部环境取了个快照,相当于封闭了外部状态。下面是著名营养快线经销商vczh的回答:

Python中的闭包

Python中写闭包就要方便多了,毕竟Python的设计哲学就是“一切皆对象”,函数都是对象。

我们来看这样一个问题:利用闭包和生成器返回一个计数器函数,每次调用它返回递增整数。

# 利用闭包和生成器返回一个计数器函数,每次调用它返回递增整数。
def createCounter():    
    [...]

# 检验部分
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
else:
    print('测试失败!')

你想怎么写?我能想到的,就是在函数内部定义一个生成器,每次调用生成一个整数;然后利用next函数构造一个迭代器,每次调用让这个整数+1,最后返回这个迭代器。

def createCounter():    
    def counter():
        '''定义一个生成器
        '''
        n = 0
        while 1:
            n += 1
            yield n
    g = counter() # 取生成器
    def g_fn():
        '''定义一个迭代器,利用next迭代生成器g
        '''
        return next(g)
    return g_fn # 返回这个迭代器

我们看一下上面这个函数,函数内部定义的g_fn函数,它使用了外部变量g,也就是说g_fn是个闭包。

总结一下:

引用了自由变量的函数,就是闭包。


notes      C++ Python closure lambda

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!