编译

简介: 编译

此前,我们已经介绍过函数 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 小得多(编译在某种程度上相比其他操作开销更大)。


函数 load 与函数 loadfile 类似,不同之处在于该函数从一个字符串函数中读取代码段,而不是从文件中读取。


提示

Lua5.1 中,函数 loadstring 用于完成 load 所完成的从字符串中加载代码的功能。


例如,考虑如下代码:

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


在这段代码执行后,变量 f 就会变成一个被调用执行 i = i + 1 的函数:

i = 0
f(); print(i)     --> 1
f(); print(i)     --> 2


注意

尽管函数 load 的功能很强大,但还是应该谨慎地使用。相对于其他可选的函数而言,该函数的开销较大并且可能会引起诡异的问题。请先确认当下已经找不到更简单的解决方式后再使用该函数。


如果要编写一个用后即弃的 dostring 函数(例如加载并运行一段代码),那么我们可以直接调用函数 load 的返回值:

load(s)()


不过,如果代码中有语法错误,函数 load 就会返回 nil 和形如“**试图调用一个nil值(attempt to call a nil value)**”的错误信息。为了更清楚地展示错误信息,最好使用函数 assert

assert(load(s))()


通常,用函数 load 来加载字符串常量是没有意义的。例如,如下的两行代码基本等价:

f = load("i = i + 1")
f = function () i = i + 1 end


但是,由于第二行代码会与其外层的函数一起被编译,所以其执行速度要快得多。与之对比,第一段代码在调用函数 load 时会进行一次独立的编译。


由于函数 load 在编译时不涉及词法定界,所以上述示例的两端代码可能并不完全等价。为了清晰地展示他们之间的区别,让我们稍微修改以下上面的例子:

i = 32
local i = 0
f = load("i = i + 1; print(i)")
g = function () i = i + 1; print(i) end
f()                                         --> 33
g()                                         --> 1


函数 g 像我们所预期地那样操作局部变量 i ,但函数 f 操作的却是全局变量 i ,这是由于函数 load 总是在全局环境中编译代码段


函数 load 最典型的用法是执行外部代码(即那些来自程序本身之外的代码段)或动态生成的代码。例如,我们可能想运行用户定义的函数,由用户输入函数的代码后调用函数 load 对其求值。


注意

函数 load 期望的输入是一段程序,也就是一系列语句。如果需要对表达式求值,那么可以在表达式前添加 return ,这样才能构成一条返回指定表达式值的语句。

print "enter your expression:"
local line = io.read()
local func = assert(load("return "..line))
print("the value of your expression is "..func())


由于函数 load 所返回的函数就是一个普通函数,因此可以反复对其进行调用:

print "enter function to be plotted (with variable 'x'):"
local line = io.read()
local f = assert(load("return "..line))
for i = 1, 20 do
  x = i -- 全局的'x'(当前代码段内可见)
  print(string.rep("*", f()))
end


我们也可以使用读取函数作为函数 load 的第一个参数。读取函数可以分几次返回一段程序,函数 load 会不断地调用读取函数知道读取函数返回 nil (表示程序段结束)。作为示例,以下的调用与函数 loadfile 等价:

f = load(io.lines(filename, "*L"))


调用 io.lines(filename, "*L") 返回一个函数,这个函数每次被调用时就从指定文件返回一行。因此,函数 load 会一行一行地从文件中读出一段程序。以下的版本与之相似但效率稍高:

f = load(io.lines(filename, 1024))


这里,函数 io.lines 返回的迭代器会以 1024 字节为块读取源文件。


Lua 语言将所有独立的代码段当做匿名可变长参数函数的函数体。例如, load("a = 1") 的返回值与以下表达式等价:

function (...) a = 1 end


像其他任何函数一样,代码段中可以声明局部变量:

f = load("local a = 10; print(a + 20)")
f()       --> 30


使用这个特性,可以在不使用全局变量 x 的情况下重写之前运行用户定义函数的示例:

print "enter function to be plotted (with variable 'x'):"
local line = io.read()
local f = assert(load("local x = ...; return " .. line))
for i = 1, 20 do
  print(string.rep("*", f(i)))
end


在上述代码中,在代码段开头增加了 "local x = ..." 来将 x 声明为局部变量。之后使用参数 i 调用函数 f ,参数 i 就是可变长参数表达式的值( ... )。


函数 load 和函数 loadfile 从来不引发错误。当有错误发生时,他们会返回 nil 及错误信息:

print(load("i i"))    --> nil   [string "i i"]:1: '=' expected near 'i'点击复制复制失败已复制


此外,这些函数没有任何副作用,他们既不改变或创建变量,也不向文件写入等。这些函数只是将程序段编译为一种中间形式,然后将结果作为匿名函数返回。一种常见的误解是认为加载一段程序也就是定义了函数,但实际上在 Lua 语言中函数定义是在运行时而不是在编译时发生的一种赋值操作。例如,假设有一个文件 foo.lua

-- 文件'foo.lua'
function foo(x)
  print(x)
end


当执行:

f = loadfile("foo.lua")


时,编译 foo 的命令并没有定义 foo ,只有运行代码才会定义它:

f = loadfile("foo.lua")
print(foo)                --> nil
f()                       -- 运行代码
foo("ok")                 --> ok


这种行为可能看上去有些奇怪,但如果不使用语法糖对其进行重写则看上去会清晰很多:

-- 文件'foo.lua'
foo = function (x)
  print(x)
end


如果线上产品级别的程序需要执行外部代码,那么应该处理加载程序段时报告的所有错误。此外,为了避免不愉快的副作用发生,可能还应该在一个受保护的环境中执行这些代码。

目录
相关文章
|
程序员 Linux Android开发
libYuv编译
安卓使用NDK编译libYuv库
349 0
|
缓存 Java Shell
ThingsBoard详细编译指南2.4.3
ThingsBoard详细编译指南2.4.3
460 0
C++程序的编译过程
C++程序的编译过程
|
自然语言处理 编译器 C语言
C/C++程序的编译过程
C/C++程序的编译过程
188 0
C/C++程序的编译过程
jpegNPP编译为so
jpegNPP编译为so
69 0
|
Web App开发 存储 缓存
V8 编译浅谈
本文是一个 V8 编译原理知识的介绍文章,旨在让大家感性的了解 JavaScript 在 V8 中的解析过程。
V8 编译浅谈
|
开发工具 C++ git
DCMTK-001-3.6.6编译
DCMTK-001-3.6.6编译
254 0
DCMTK-001-3.6.6编译
|
编译器 开发工具 IDE
编译问题汇总
每次在编译各类第三方库的时候都会碰到很多问题,基本上都是通过Google解决,其实简单反思一下就会发现是对于系统了解的不够,对于各类标准也是了解甚少。所以一而再再而三发生的问题其实是可以尽量去避免的。
1935 0
|
Linux 开发工具 Android开发
libyuv编译【转】
转自:http://blog.csdn.net/kl222/article/details/41309541 版权声明:本文为博主原创文章,未经博主允许不得转载。 下载代码(要FQ): git clone http://git.
1418 0