第一版是針對 Lua 5.0 編寫的。雖然在很大程度上仍然適用於後續版本,但仍有些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買這本書,您也可以幫助 支援 Lua 專案。
![]() |
Lua 程式設計 | ![]() |
第一部分。語言 第 6 章。更多關於函式 |
當一個函式寫在另一個函式內時,它可以完全存取封閉函式的局部變數;這個功能稱為詞彙範圍。雖然這聽起來很明顯,但事實並非如此。詞彙範圍加上一級函式是程式語言中強大的概念,但很少有語言支援這個概念。
讓我們從一個簡單的範例開始。假設您有一個學生姓名清單和一個將姓名與成績關聯的表格;您想要根據成績(較高成績在前)對姓名清單進行排序。您可以這樣執行這項工作
names = {"Peter", "Paul", "Mary"} grades = {Mary = 10, Paul = 7, Peter = 8} table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end)現在,假設您想要建立一個函式來執行這項工作
function sortbygrade (names, grades) table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end) end範例中有趣的地方在於傳遞給
sort
的匿名函式存取參數 grades
,而 grades
是封閉函式 sortbygrade
的局部變數。在這個匿名函式內,grades
不是全域變數也不是局部變數。我們稱它為外部局部變數或upvalue。(「upvalue」這個術語有點誤導,因為 grades
是變數,而不是值。然而,這個術語在 Lua 中有歷史淵源,而且比「外部局部變數」簡潔。)
為什麼這很有趣?因為函式是一級值。考慮以下程式碼
function newCounter () local i = 0 return function () -- anonymous function i = i + 1 return i end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2現在,匿名函式使用一個 upvalue
i
來維持它的計數器。然而,當我們呼叫匿名函式時,i
已經超出範圍,因為建立那個變數的函式 (newCounter
) 已經傳回。儘管如此,Lua 透過使用閉包的概念正確地處理那個情況。簡單來說,閉包是一個函式加上它正確存取其 upvalue 所需的一切。如果我們再次呼叫 newCounter
,它將建立一個新的局部變數 i
,因此我們將取得一個新的閉包,作用於那個新變數
c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2因此,
c1
和 c2
是同一個函式的不同封閉,且每個函式作用於區域變數 i
的獨立實例。技術上來說,Lua 中的值是封閉,而不是函式。函式本身只是封閉的原型。不過,只要沒有混淆的可能,我們仍會繼續使用「函式」一詞來指稱封閉。
封閉在許多情況下提供有價值的工具。正如我們所見,它們可用作高階函式的引數,例如 sort
。封閉對於建立其他函式的函式也很有價值,例如我們的 newCounter
範例;此機制允許 Lua 程式納入函式世界中的精緻程式設計技巧。封閉也可用於 回呼 函式。典型的範例發生在您在典型的 GUI 工具組中建立按鈕時。每個按鈕都有回呼函式,當使用者按下按鈕時會呼叫該函式;您希望不同的按鈕在按下時執行稍微不同的動作。例如,數位計算器需要十個類似的按鈕,每個按鈕代表一個數字。您可以使用類似下一個函式建立每個按鈕
function digitButton (digit) return Button{ label = digit, action = function () add_to_display(digit) end } end在此範例中,我們假設
Button
是建立新按鈕的工具組函式;label
是按鈕標籤;action
是按下按鈕時要呼叫的回呼函式。(它實際上是一個封閉,因為它會存取上值 digit
。)回呼函式可以在 digitButton
完成其任務並且區域變數 digit
超出範圍很長一段時間後呼叫,但它仍然可以存取該變數。
閉包在完全不同的情況下也很有價值。由於函式儲存在一般變數中,我們可以輕鬆地在 Lua 中重新定義函式,甚至是預先定義的函式。此功能是 Lua 如此靈活的原因之一。然而,當您重新定義函式時,您經常需要在新的實作中使用原始函式。例如,假設您想要重新定義函式 sin
,使其以度為單位運算,而不是弧度。此新函式必須轉換其引數,然後呼叫原始 sin
函式來執行實際工作。您的程式碼看起來像
oldSin = math.sin math.sin = function (x) return oldSin(x*math.pi/180) end執行此操作的更簡潔方法如下
do local oldSin = math.sin local k = math.pi/180 math.sin = function (x) return oldSin(x*k) end end現在,我們將舊版本保存在私人變數中;唯一可以存取它的方法是透過新版本。
您可以使用相同的特點來建立安全環境,也稱為「沙盒」。當執行不受信任的程式碼(例如伺服器透過網際網路接收的程式碼)時,安全環境至關重要。例如,若要限制程式可以存取的檔案,我們可以使用閉包重新定義 open
函式(來自 io
函式庫)
do local oldOpen = io.open io.open = function (filename, mode) if access_OK(filename, mode) then return oldOpen(filename, mode) else return nil, "access denied" end end end此範例的優點在於,在重新定義之後,程式無法呼叫不受限制的
open
,只能透過新的受限制版本呼叫。它將不安全的版本作為閉包中的私人變數,無法從外部存取。有了此功能,您可以使用 Lua 本身在 Lua 中建構沙盒,並享有通常的優點:靈活性。Lua 沒有提供一體適用的解決方案,而是提供一個元機制,讓您可以根據特定安全性需求調整您的環境。
版權所有 © 2003–2004 Roberto Ierusalimschy。保留所有權利。 | ![]() |