闭包 (计算机科学)

在计算机科学中，闭包（英语：Closure），又称词法闭包（Lexical Closure）或函数闭包（function closures），是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在，即使已经离开了创造它的环境也不例外。所以，有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例，不同的引用环境和相同的函数组合可以产生不同的实例。
闭包的概念出现于60年代，最早实现闭包的程序语言是Scheme。之后，闭包被广泛使用于函数式编程语言如ML语言和LISP。很多命令式程序语言也开始支持闭包。
在一些语言中，在函数中可以（嵌套）定义另一个函数时，如果内部的函数引用了外部的函数的变量，则可能产生闭包。运行时，一旦外部的 函数被执行，一个闭包就形成了，闭包中包含了内部函数的代码，以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。
闭包一词经常和匿名函数混淆。这可能是因为两者经常同时使用，但是它们是不同的概念。

词源

彼得·兰丁在1964年将术语“闭包”定义为一种包含环境成分和控制成分的实体，用于在他的SECD机器上对表达式求值。Joel Moses认为是Landin发明了“闭包”这一术语，用来指代某些其开放绑定（自由变量）已经由其语法环境完成闭合（或者绑定）的lambda表达式，从而形成了闭合的表达式，或称闭包。这一用法后来于1975年被Sussman和Steele在定义Scheme语言的时候予以采纳。 并广为流传。

闭包和状态表达

闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中，这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数，因此无法从其它程序代码部分进行访问。不过，变量的生存期是可以很长，在一次函数调用期间所创建所生成的值在下次函数调用时仍然存在。正因为这一特点，闭包可以用来完成信息隐藏，并进而应用于需要状态表达的某些编程范型中。
不过，用这种方式来使用闭包时，闭包不再具有引用透明性，因此也不再是纯函数。即便如此，在某些“近似于函数式编程语言”的语言，例如Scheme中，闭包还是得到了广泛的使用。

闭包和第一类函数

典型的支持闭包的语言中，通常将函数当作第一类对象——在这些语言中，函数可以被当作参数传递、也可以作为函数返回值、绑定到变量名、就像字符串、整数等简单类型。例如以下Scheme代码：
; Return a list of all books with at least THRESHOLD copies sold.(define (best-selling-books threshold)
  (filter
    (lambda (book)
      (>= (book-sales book) threshold))
    book-list))
在这个例子中，lambda表达式(lambda  (book) (>= (book-sales book) threshold))出现在函数best-selling-books中。当这个lambda表达式被执行时，Scheme创造了一个包含此表达式以及对threshold变量的引用的闭包，其中threshold变量在lambda表达式中是自由变量。
这个闭包接着被传递到filter函数。这个函数的功能是重复调用这个闭包以判断哪些书需要增加到列表哪些书需要丢弃。因为闭包中引用了变量threshold，所以它在每次被filter调用时都可以使用这个变量，虽然filter可能定义在另一个文件中。
下面是用ECMAScript （JavaScript）写的同一个例子：
// Return a list of all books with at least 'threshold' copies sold.function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );}
这里，关键字function取代了lambda，Array.filter方法取代了filter函数，但两段代码的功能是一样的。
一个函数可以创建一个闭包并返回它，如下述JavaScript例子：
// Return a function that approximates the derivative of f// using an interval of dx, which should be appropriately small.function derivative(f, dx) {
  return function (x) {
    return (f(x + dx) - f(x)) / dx;
  };}
因为在这个例子中闭包已经超出了创建它的函数的范围，所以变量f和dx将在函数derivative返回后继续存在。在没有闭包的语言中，变量的生命周期只限于创建它的环境。但在有闭包的语言中，只要有一个闭包引用了这个变量，它就会一直存在。清理不被任何函数引用的变量的工作通常由垃圾回收完成，但对于C++ 这种没有垃圾收集（起码当前仍没有一个为语言本身所认可的）的语言而言也不是难事——通过一些细致而琐碎的步骤。

闭包的实现

典型实现方式是定义一个特殊的数据结构，保存了函数地址指针与闭包创建时的函数的词法环境表示（那些非局部变量的绑定）。使用函数调用栈的语言实现闭包比较困难，因而这也说明了为什么大多数实现闭包的语言是基于垃圾收集机制——当然，不使用垃圾收集也可以做到。
闭包的实现与函数对象很相似。
通过将自由变量放进参数表、并扩大函数名字的作用域，可以把一个闭包/ 匿名 / 内部函数变成一个普通的函数，这叫做“Lambda 提升”。例：
void G(void){
    const std::wstring wstr=L"Hello, world!";
    std::function<wchar_t(size_t)> fn=[&wstr](size_t ui)->wchar_t{
        return wstr[ui%wstr.length()];
    };
    std::wcout<<fn(3)<<std::endl;//'l'}//那么 fn 是一个闭包，指向那个匿名函数。//这里 wstr 是自由变量，首先将其放入参数表：void G(void){
    const std::wstring wstr=L"Hello, world!";
    std::function<wchar_t(size_t, const std::wstring &)> fn=[](size_t ui, const std::wstring &wstr)->wchar_t{
        return wstr[ui%wstr.length()];
    };
    std::wcout<<fn(3, wstr)<<std::endl;//'l'}//现在 fn 中没有自由变量了。把这个匿名函数取个名之后放到全局命名空间里：wchar_t fn(size_t ui, const std::wstring &wstr){
    return wstr[ui%wstr.length()];}void G(void){
    const std::wstring wstr=L"Hello, world!";
    std::wcout<<fn(3, wstr)<<std::endl;//'l'}//这就把fn“提升”成了一个普通的函数。

C语言的回调函数

在C语言中，支持回调函数的库有时在注册时需要两个参数：一个函数指针，一个独立的void*指针用以保存用户数据。这样的做法允许回调函数恢复其调用时的状态。这样的惯用法在功能上类似于闭包，但语法上有所不同。

gcc对C语言的扩展

gcc编译器对C语言实现了一种闭包的程序特性。

C语言扩展：Blocks

C语言 (使用LLVM编译器或苹果修改版的GCC)支持块。闭包变量用__block标记。同时，这个扩展也可以应用到Objective-C与C++中。
typedef int (^IntBlock)();IntBlock downCounter(int start) {
 __block int i = start;
 return Block_copy( ^int() {
 return i--;
 });
 }IntBlock f = downCounter(5);printf("%d", f());printf("%d", f());printf("%d", f());Block_release(f);

C++函数对象

C++早期标准允许通过重载operator()来定义函数对象。这种对象的行为在某种程度上与函数式编程语言中的函数类似。它们可以在运行时动态创建、保存状态，但是不能如闭包一般方便地隐式获取局部变量，并且有“专物专用”的繁琐问题——对于每一段闭包代码都要单独写一个函数对象类。
C++11标准已经支持了闭包，这是一种特殊的函数对象，由特殊的语言结构——lambda表达式自动构建。C++闭包中保存了其代码内全部向外引用的变量的拷贝或引用。如果是对外界环境中的对象的引用，且闭包执行时该外界环境的变量已经不存在（如在调用栈上已经展开），那么可导致未定义行为，因为C++并不扩展这些被引用的外界环境的变量的生命期。示例代码如下：
void foo(string myname) {
typedef vector<string> names;
int y;
names n;
// ...
names::iterator i =
 find_if(n.begin(), n.end(), [&](const string& s){return s != myname && s.size() > y;});
// 'i' 现在是'n.end()'或指向'n'中第一个
// 不等于'myname'且长度大于'y'的字符串}