在計算機科學中，閉包，又稱詞法閉包或函式閉包，是在支援頭等函式的程式語言中實現詞法繫結的一種技術。閉包在實現上是一個結構體，它儲存了一個函式（通常是其入口地址）和一個關聯的環境（相當於一個符號查詢表）。環境裡是若干對符號和值的對應關係，它既要包括約束變數（該函式內部繫結的符號），也要包括自由變數（在函式外部定義但在函式內被引用），有些函式也可能沒有自由變數。閉包跟函式最大的不同在於，當捕捉閉包的時候，它的自由變數會在捕捉時被確定，這樣即便脫離了捕捉時的上下文，它也能照常執行。捕捉時對於值的處理可以是值複製，也可以是名稱引用，這通常由語言設計者決定，也可能由使用者自行指定（如 C）。 概述 閉包的概念出現於 60 年代，最早實現閉包的程式語言是 Scheme。之後，閉包被廣泛使用於函數語言程式設計語言如 ML 語言和 LISP。很多命令式程式語言也開始支援閉包。 在支援頭等函式的語言中，如果函式 f 內定義了函式 g，那麼如果 g 存在自由變數，且這些自由變數沒有在編譯過程中被最佳化掉，那麼將產生閉包。 閉包和匿名函式經常被用作同義詞。但嚴格來說，匿名函式就是字面意義上沒有被賦予名稱的函式，而閉包則實際上是一個函式的例項，也就是說它是存在於記憶體裡的某個結構體。如果從實現上來看的話，匿名函式如果沒有捕捉自由變數，那麼它其實可以被實現為一個函式指標，或者直接內聯到呼叫點，如果它捕捉了自由變數那麼它將是一個閉包；而閉包則意味著同時包括函式指標和環境兩個關鍵元素。在編譯最佳化當中，沒有捕捉自由變數的閉包可以被最佳化成普通函式，這樣就無需分配閉包結構體，這種編譯技巧被稱為。 詞源 閉包的概念是在 1960 年代為採用 lambda 演算的表示式的機器求值而開發的，它首次在 1970 年於 PAL 程式語言中完全實現，用來支援詞法作用域的頭等函式 David A Turner 2012 Some History of Functional Programming Languages Trends in Functional Programming 12 Section 2 note 8 contains the claim about Mexpressions。 彼得蘭丁（Peter Landin）在 1964 年將術語 閉包 定義為一種包含環境成分和控制成分的實體，用於在他的 SECD 機器上對錶達式求值。 認為是 Landin 發明了 閉包 這一術語，用來指代某些其開放繫結（自由變數）已經由其語法環境完成閉合（或者繫結）的 lambda 表示式，從而形成了閉合的表示式，或稱閉包。。這一用法後來於 1975 年被 Sussman 和 Steele 在定義 Scheme 語言的時候予以採納，並廣為流傳。 語義 閉包和狀態表達 閉包可以用來在一個函式與一組 私有 變數之間建立關聯關係。在給定函式被多次呼叫的過程中，這些私有變數能夠保持其永續性。變數的作用域僅限於包含它們的函式，因此無法從其它程式程式碼部分進行訪問。不過，變數的生存期是可以很長，在一次函式呼叫期間所建立所生成的值在下次函式呼叫時仍然存在。正因為這一特點，閉包可以用來完成資訊隱藏，並進而應用於需要狀態表達的某些程式設計範型中。 不過，用這種方式來使用閉包時，閉包不再具有，因此也不再是純函式。即便如此，在某些非純函數語言程式設計語言，例如 Scheme 中，閉包還是得到了廣泛的使用。 閉包和頭類函式 典型的支援閉包的語言中，通常將函式當作頭等函式 在這些語言中，函式可以被當作引數傳遞、也可以作為函式返回值、繫結到變數名、就像字串、整數等簡單型別。例如以下 Scheme 程式碼： Return a list of all books with at least THRESHOLD copies sold define bestsellingbooks threshold filter lambda book booksales book threshold booklist 在這個例子中，lambda 表示式 lambda book booksales book threshold 出現在函式 bestsellingbooks 中。當這個 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 bookListfilter function book return booksales threshold 這裡，關鍵字 function 取代了 lambda，Arrayfilter 方法取代了 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 這種沒有垃圾收集（起碼目前仍沒有一個為語言本身所認可的）的語言而言也不是難事 透過一些細緻而瑣碎的步驟。 閉包的用途 因為閉包只有在被呼叫時才執行操作（暫且不論用於生成這個閉包物件本身的開銷，比如 C 中按值捕獲意味著執行復制建構函式），即 惰性求值，所以它可以被用來定義控制結構。例如：在 Smalltalk 語言中，所有的控制結構，包括分支條件（ifthenelse）和迴圈（while 和 for），都是透過閉包實現的。使用者也可以使用閉包定義自己的控制結構。 多個函式可以使用一個相同的環境，這使得它們可以透過改變那個環境相互交流。比如在 Scheme 中： define foo f define bar f let secretmessage none set foo lambda msg set secretmessage msg set bar lambda secretmessage display bar prints none newline foo meet me by the docks at midnight display bar prints meet me by the docks at midnight 閉包可以用來實現物件系統。 閉包的實現 典型實現方式是定義一個特殊的資料結構，儲存了函式地址指標與閉包建立時的函式的詞法環境表示（那些非區域性變數的繫結）。使用函式呼叫棧的語言實現閉包比較困難，因而這也說明了為什麼大多數實現閉包的語言是基於垃圾收集機制 當然，不使用垃圾收集也可以做到。 閉包的實現與函式物件很相似。 透過將自由變數放進引數表、並擴大函式名字的作用域，可以把一個閉包 匿名 內部函式變成一個普通的函式，這叫做 。例： void G void const stdwstring wstrLHello world stdfunctionwchar_t size_t fnwstrsize_t uiwchar_t return wstr ui wstrlength stdwcoutfn 3stdendll 那麼 fn 是一個閉包，指向那個匿名函式。 這裡 wstr 是自由變數，首先將其放入引數表： void G void const stdwstring wstrLHello world stdfunctionwchar_t size_t const stdwstring fnsize_t ui const stdwstring wstrwchar_t return wstr ui wstrlength stdwcoutfn 3 wstrstdendll 現在 fn 中沒有自由變量了。把這個匿名函式取個名之後放到全域性命名空間裡： wchar_t fn size_t ui const stdwstring wstrreturn wstr ui wstrlength void G void const stdwstring wstrLHello world stdwcoutfn 3 wstrstdendll 這就把 fn 提升 成了一個普通的函式。 各種語言中（類似）閉包的結構 C 語言的回撥函式 在 C 語言中，支援回撥函式的庫有時在註冊時需要兩個引數：一個函式指標，一個獨立的 void 指標用以儲存使用者資料。這樣的做法允許回撥函式恢復其呼叫時的狀態。這樣的慣用法在功能上類似於閉包，但語法上有所不同。 gcc 對 C 語言的擴充套件 gcc 編譯器對 C 語言實現了一種閉包的程式特性。 C 語言擴充套件：Blocks C 語言 使用 LLVM 編譯器或蘋果修改版的 GCC 支援塊。閉包變數用__block 標記。同時，這個擴充套件也可以應用到 ObjectiveC 與 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 來定義函式物件。這種物件的行為在某種程度上與函數語言程式設計語言中的函式類似。它們可以在執行時動態建立、儲存狀態，但是不能如閉包一般方便地隱式獲取區域性變數，並且有 專物專用 的繁瑣問題 對於每一段閉包程式碼都要單獨寫一個函式物件類。 C11 標準已經支援了閉包，這是一種特殊的函式物件，由特殊的語言結構 lambda 表示式自動構建。C 閉包中儲存了其程式碼內全部向外引用的變數的複製或引用。如果是對外界環境中的物件的引用，且閉包執行時該外界環境的變數已經不存在（如在呼叫棧上已經展開），那麼可導致未定義行為，因為 C 並不擴充套件這些被引用的外界環境的變數的生命期。示例程式碼如下： void foo string myname typedef vectorstring names int y names n namesiterator i find_if nbegin nend const string sreturn s myname ssize y i 現在是 nend 或指向 n 中第一個 不等於 myname 且長度大於 y 的字串 參考文獻 外部連結 跨越邊界：閉包 IBM DeveloperWorks 閉包的概念、形式與應用 IBM DeveloperWorks 深入理解 Javascript 閉包 closure Felix Woo Category 程式語言概念 Category 子程式