programming in lua (5)

第 8 章。

虽然我们说 lua 是一种解释语言,但是 lua 在执行前会被预编译为一种中间代码。解释执行语言的特殊之处不在于它们不需要编译执行,而是编译器是语言运行时(runtime)本身的一部分,也就是说可以执行在运行过程中动态产生的代码。

例如函数 loadstring() 接收一个字符串,返回一个可被执行的语句块(chunk):

f = loadstring("i = i + 1")

i = 10
f()
print(i) -- 11

又或者是省略赋值操作直接使用 loadstring() 的返回值:

i = 10
loadstring("i = i + 1")()
print(i) -- 11

但是直接使用的话如果出错了出错信息会比较模糊。可以使用 assert() 来获得更明确的出错信息:

assert(loadstring("i = i + 1"))()

像上面的情况,loadstring() 语句功能和下面的函数一样:

function f ()
   i = i + 1
end

使用函数速度会快些,因为函数的代码只会被编译一遍(指把 lua 代码翻译成中间代码),而每次执行 loadstring(),其中的字符串都会被重新编译。

loadstring() 中的语句不受变量作用域的影响,像下面的情况:

i = 32
local i = 12
f = loadstring("i = i + 1; print(i)")
g = function () i = i + 1; print(i) end

f() -- 33
g() -- 13

loadstring() 的典型用法是运行外部代码。例如可以让用户自己编写函数,然后使用 loadstring() 运行用户的代码,要注意的是 loadstring() 的参数是语句。如果想获取表达式的返回值,需要在前面加上“return”:

i = 10
f = loadstring("return " .. "3 + 2")
print("value is: " .. f()) -- 5

lua 把一个块当成是一个接收不定参数的匿名函数的函数体部分,例如,

loadstring("local a = 1")

与函数

function (...) local a = 1 end

等价。

如果出错了,loadstring() 会返回 nil 以及相应的错误信息,但是不会对整个程序有副作用:

loadstring("i i") -- nothing happened
print(loadstring("i i")) -- nil  [string "i i"]:1: '=' expected near 'i' 

loadstring() 将参数编译成中间代码并且把结果作为一个匿名函数返回。在 lua 中,函数定义是一个赋值操作,它发生在运行期而不是编译期。例如有一个 foo.lua 文件:

function foo (x)
   print(x)
end

使用 loadfile() 加载这个文件:

f = loadfile("foo.lua")

这时函数 foo() 被编译了但是还没被定义。如果要定义它必须显式执行 foo.lua 里的内容:

print(foo) -- nil
f() -- defines 'foo'
foo("hello") -- hello

其实也很好理解,因为前面提到,下面两种函数定义方式

function foo () ... end

foo = function () ... end

是等价的,而前一种方式只是后一种方式的“语法糖”而已。

可以使用 package.loadlib() 加载平台相关的函数。package.loadlib() 接收两个参数:第一个参数是动态链接库的绝对路径,第二个是函数名称。

因为 lua 经常被嵌入到另一个程序中使用,出现错误时会结束当前的块并且返回使用这个块的程序中。使用 error() 函数可以在出错时返回一条错误信息:

local n = 10
if n ~= 5 then error("error message: n ~= 5") end

另外也可以使用 assert() 代替“if ... then ... end”:

local n = 10
assert(n == 5, "error message: n ~= 5")

如果 assert() 的第一个表达式为真则返回表达式的结果,否则返回错误信息并退出。要注意的是 assert() 是一个普通的函数,在调用函数之前 lua 会先计算它的表达式参数,因此,如果像下面的调用:

local n = 10
assert(n == 5, "error message: " .. n .. " ~= 5")

无论 n 是否为 5,后面的字符串连接操作都会被执行。在这种情况下使用“if...then”语句会好些。

如果运行过程中出现错误,lua 会结束当前块并且打印错误信息。如果想在程序中处理运行错误可以使用 pcall():

function func ()
   error("err test")
end

if pcall(func) then
   print("OK")
else
   print("err")
end

当在函数 func() 中使用 error() 时,使用 pcall() 调用 func() 并不是像前面的函数那样直接退出当前块,而是返回到调用处并且继续往下执行(没有打印 error() 中的参数并且打印了 else 分支中的内容)。一般来说,pcall() 在保护模式(protected mode)中执行传递给它的函数,如果没有错误,它返回 true 和由函数参数返回的值;否则返回 false 以及错误信息:

function func ()
   error("err test")
end

local status, err_msg = pcall(func)
print(err_msg)

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注