闭包 (计算机科学)

在電腦科學中，閉包（英語：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'的字符串}