loadfile,加载文件,编译文件,并且返回一个函数,不运行
dofile其实就是包装了Loadfile,根据loadfile的返回函数运行一遍
require加载文件的时候,不用带目录,有lua自己的搜索加载目录的路径,并且会判断文件是否加载过,加载过则不加载.
下面是详细介绍:摘自《Lua程序设计第二版》
虽然我们把 Lua 当作解释型语言,但是 Lua 会首先把代码预编译成中间码然后再执
行(很多解释型语言都是这么做的)。在解释型语言中存在编译阶段听起来不合适,然而,
解释型语言的特征不在于他们是否被编译,而是编译器是语言运行时的一部分,所以,
执行编译产生的中间码速度会更快。我们可以说函数 dofile 的存在就是说明可以将 Lua
作为一种解释型语言被调用。
dofile:把它当作 Lua 运行代码的 chunk 的一种原始的操作。
实际上是一个辅助的函数。真正完成功能的函数是 loadfile;与 dofile 不同的是 loadfile
编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码;另外 loadfile
不会抛出错误信息而是返回错误代。.我们可以这样定义 dofile:
function dofile (filename)
local f = assert(loadfile(filename)) --如果 loadfile 失败 assert 会抛出错误。
return f() -- 根据loadfile的返回函数运行一遍
end
完成简单的功能 dofile 比较方便,他读入文件编译并且执行。然而 loadfile 更加灵活。
在发生错误的情况下,loadfile 返回 nil 和错误信息,这样我们就可以自定义错误处理。
另外,如果我们运行一个文件多次的话,loadfile 只需要编译一次,但可多次运行。dofile
却每次都要编译。
loadstring 与 loadfile:
loadstring 与 loadfile 相似,只不过它不是从文件里读入 chunk,而是从一个串中读入。
例如:
f = loadstring("i = i + 1")
f 将是一个函数,调用时执行 i=i+1。
i = 0
f(); print(i) --> 1
f(); print(i) --> 2
loadstring 函数功能强大,但使用时需多加小心。确认没有其它简单的解决问题的方
法再使用。
Lua 把每一个 chunk 都作为一个匿名函数处理。例如:chunk "a = 1",loadstring 返
回与其等价的 function () a = 1 end
与其他函数一样,chunks 可以定义局部变量也可以返回值:
f = loadstring("local a = 10; return a + 20")
print(f()) --> 30
loadfile 和 loadstring 都不会抛出错误,如果发生错误他们将返回 nil 加上错误信息:
print(loadstring("i i"))
--> nil [string "i i"]:1: '=' expected near 'i'
另外,loadfile 和 loadstring 都不会有边界效应产生,他们仅仅编译 chunk 成为自己
内部实现的一个匿名函数。通常对他们的误解是他们定义了函数。Lua 中的函数定义是
发生在运行时的赋值而不是发生在编译时。假如我们有一个文件 foo.lua:
-- file `foo.lua'
function foo (x)
print(x)
end
当我们执行命令 f = loadfile("foo.lua")后,foo 被编译了但还没有被定义,如果要定
义他必须运行 chunk:
print(foo)-->nil
f() -- 定义`foo'
foo("ok") --> ok
如果你想快捷的调用 dostring(比如加载并运行),可以这样
loadstring(s)()
调用 loadstring 返回的结果,然而如果加载的内容存在语法错误的话,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 编
译的时候不关心词法范围:
i = 32
local i = 0
f = loadstring("i = i + 1")
g = function () i = i + 1 end
f() -->33
g() -->1
这个例子中,和想象的一样 g 使用局部变量 i,然而 f 使用全局变量 i;loadstring 总
是在全局环境中编译他的串。
loadstring 通常用于执行外部的代码,也就是那些位于程序之外的代码,比如运行用户自定义的代码。注意:
loadstring 期望输入的是一个程序块,即语句。如果想要加载表达式,需要在表达式前加 return,这样才能构成一条语句
那样将返回表达式的值。
require 函 数:
粗略的说 require 和 dofile 完成同样的功
能但有两点不同:
1. require 会搜索目录加载文件
2. require 会判断是否文件已经加载避免重复加载同一文件。
对于require 而言,一个模块就是一段定义了一些值的代码。
要加载一个模块,只需要简单地调用require “<模块名>”。 该调用会返回一个由模块函数组成的table,并且还会定义一个包含该table的全局变量。然而这些行为都是由模块完成的,而非require。所以有些模块会选择返回其他值,或者具有其他的效果。
即使做的某些用到的模块可能已经加载了,但只要用到require就是一个良好的编程习惯,可以将标准库排除在此规则之外,因为Lua总是会预先加载它们。不过有些用户还是喜欢为标准库中的模块使用显式的require
local m = require "io"
m.write("hello world\n")
以下代码详细说明了require的行为:
function require(name)
if not package.loaded[name] then --模块是否已加载?
local loader = findloader(name)
if loader == nil then
error("unable to load module"..name)
end
package.loaded[name] = true --将模块标记为已加载
local res = loader(name) --初始化模块
if res ~=nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
首先,它在table package.loaded中检查模块是否已加载。如果已加载,require就返回相应的值。
因此只要一个模块已加载过,后续的require调用都将返回同一个值,不会再次重复加载它。
如果模块尚未加载,require尝试为该模块找一个加载器,会先在tablepackage.preload中查询传
入的模块名。如果在其中找到了一个函数,就以该函数作为模块的加载器。通常这个table不会
找到有关指定模块的条目,那么require就会尝试从Lua文件或C程序库中加载模块。
如果require为指定模块找到了一个Lua文件,它就通过loadfile来加载该文件;而如果找到的是
一个C程序库,就通过loadlib来加载。注意,loadfile和loadlib都只是加载了代码,并没有运行它们。
为了运行代码,require会以模块名作为参数来调用这些代码(如上面代码中:local res= loader(name) 这句调用了loadfile和loadlib返回的函数,也就是运行了loadfile或loadlib加载的代码)。如果加载器有返回值(res~=nil),require就将这个返回值存储到table package.loaded中(package.loaded[name] = res)。如果加载器没有返回值require就会返回table package.loaded中的值。在本章后面会看到,一个模块还可以将返回给require的值直接放入package.loaded中。
上述代码有个重要的细节,就是在调用加载器前,require先将true赋予了package.loaded中的对应字段,以这是因为如果一个模块要求加载另外一个模块,而后者又要递归地加载前者。那么后者的require调用就会马上返回,从而避免了无限循环。
在搜索一个文件时,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名
的方式。进一步说,这种路径中的每项都是一个文件名,每项中还可以包含一个可选的问号。require
会用模块名来替换这个问号,然后根据替换的结果来检查是否存在这样一个文件。路径中的每项以分
号隔开。所以require函数只处理了分号和问号。
require用于搜索Lua文件的路径存放在变量package.path中,搜索C程序库的路径存在在变量package.cpath中。