第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但仍存在一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助支持 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
,该参数是封闭函数 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 是按钮按下时要调用的回调函数。(它实际上是一个闭包,因为它访问了 upvalue 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现在,我们将旧版本保存在一个私有变量中;访问它的唯一方法是通过新版本。
您可以使用相同的特性来创建安全环境,也称为沙盒。在运行不受信任的代码(例如服务器通过 Internet 接收的代码)时,安全环境至关重要。例如,为了限制程序可以访问的文件,我们可以使用闭包重新定义 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。保留所有权利。 |