第一版是针对 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。保留所有权利。 |