第一版是针对 Lua 5.0 编写的。虽然在很大程度上仍然适用于后续版本,但有一些不同之处。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目。
![]() |
用 Lua 编程 | ![]() |
| 第一部分。语言 第 8 章。编译、执行和错误 |
尽管我们称 Lua 为解释型语言,但 Lua 在运行之前始终会将源代码预编译为中间形式。(这不是什么大问题:大多数解释型语言都会这样做。)在 Lua 这样的解释型语言中,编译阶段的存在听起来似乎有些格格不入。然而,解释型语言的显著特征并不是它们没有编译,而是任何编译器都是语言运行时的一部分,因此可以(并且很容易)执行动态生成的代码。我们可以说,像 dofile 这样的函数的存在使得 Lua 可以被称为解释型语言。
之前,我们介绍了 dofile,它是一种运行 Lua 代码块的原始操作。dofile 函数实际上是一个辅助函数;loadfile 负责繁重的工作。与 dofile 一样,loadfile 也从文件中加载一个 Lua 块,但它不会运行该块。相反,它只编译该块,并将编译后的块作为函数返回。此外,与 dofile 不同,loadfile 不会引发错误,而是返回错误代码,以便我们可以处理错误。我们可以将 dofile 定义如下
function dofile (filename)
local f = assert(loadfile(filename))
return f()
end
请注意,如果 loadfile 失败,则使用 assert 引发错误。
对于简单的任务,dofile 很方便,因为它可以在一次调用中完成整个工作。但是,loadfile 更灵活。如果发生错误,loadfile 会返回 nil 和错误消息,这使我们可以以自定义方式处理错误。此外,如果我们需要多次运行一个文件,我们可以调用 loadfile 一次,然后多次调用其结果。这比多次调用 dofile 要便宜得多,因为程序只编译一次文件。
loadstring 函数类似于 loadfile,不同之处在于它从字符串中读取其块,而不是从文件中读取。例如,在代码之后
f = loadstring("i = i + 1")
f 将是一个函数,当调用时,它会执行 i = i + 1
i = 0
f(); print(i) --> 1
f(); print(i) --> 2
loadstring 函数功能强大;必须小心使用。它也是一个昂贵的函数(与它的替代函数相比),并且可能导致代码难以理解。在使用它之前,请确保没有更简单的方法来解决手头的问题。
Lua 将任何独立块视为匿名函数的主体。例如,对于块 "a = 1",loadstring 返回等效于
function () a = 1 end与任何其他函数一样,块可以声明局部变量和返回值
f = loadstring("local a = 10; return a + 20")
print(f()) --> 30
loadstring 和 loadfile 都不会引发错误。在出现任何类型的错误的情况下,这两个函数都会返回 nil 加上错误消息
print(loadstring("i i"))
--> nil [string "i i"]:1: `=' expected near `i'
此外,这两个函数都不会产生任何副作用。它们仅将块编译为内部表示形式,并以匿名函数的形式返回结果。一个常见的错误是假设 loadfile(或 loadstring)定义函数。在 Lua 中,函数定义是赋值;因此,它们是在运行时进行的,而不是在编译时进行的。例如,假设我们有一个文件 foo.lua,如下所示
-- file `foo.lua'
function foo (x)
print(x)
end
然后我们运行命令
f = loadfile("foo.lua")
此命令后,foo 已编译,但尚未定义。要定义它,您必须运行块
f() -- defines `foo'
foo("ok") --> ok
如果您想快速而粗略地执行 dostring(即加载并运行一个块),则可以直接调用 loadstring 的结果
loadstring(s)()但是,如果存在任何语法错误,
loadstring 将返回 nil,最终错误消息将是 "attempt to call a nil value"。要获得更清晰的错误消息,请使用 assert
assert(loadstring(s))()
通常,对文字字符串使用 loadstring 没有意义。例如,代码
f = loadstring("i = i + 1")
大致相当于
f = function () i = i + 1 end但第二个代码要快得多,因为它仅在块编译时编译一次。在第一个代码中,每次调用
loadstring 都涉及一个新的编译。但是,这两个代码并不完全等效,因为 loadstring 不使用词法作用域进行编译。为了了解差异,让我们稍微更改一下前面的示例
local i = 0
f = loadstring("i = i + 1")
g = function () i = i + 1 end
g 函数按预期操作局部 i,但 f 操作全局 i,因为 loadstring 始终在其全局环境中编译其字符串。
loadstring 最典型的用法是运行外部代码,即来自程序外部的代码片段。例如,您可能想绘制用户定义的函数;用户输入函数代码,然后您使用 loadstring 对其进行评估。请注意,loadstring 需要一个块,即语句。如果您想评估一个表达式,您必须在前面加上 return,以便获得一个返回给定表达式值的语句。请参见示例
print "enter your expression:"
local l = io.read()
local func = assert(loadstring("return " .. l))
print("the value of your expression is " .. func())
loadstring 返回的函数是一个常规函数,因此您可以多次调用它
print "enter function to be plotted (with variable `x'):"
local l = io.read()
local f = assert(loadstring("return " .. l))
for i=1,20 do
x = i -- global `x' (to be visible from the chunk)
print(string.rep("*", f()))
end
在需要运行外部代码的生产级程序中,您应该处理 loadstring 报告的任何错误。此外,如果代码不可信,您可能希望在受保护的环境中运行新的块,以避免在运行代码时产生不愉快的副作用。
| 版权所有 © 2003–2004 Roberto Ierusalimschy。保留所有权利。 | ![]() |