lua中dofile,loadfile,require的区别

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来加载。注意,loadfileloadlib都只是加载了代码,并没有运行它们。

为了运行代码,require会以模块名作为参数来调用这些代码(如上面代码中:local res= loader(name) 这句调用了loadfileloadlib返回的函数,也就是运行了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对同一个库加载两次的话,可以简单地删除package.loaded中的模块条目。
package.loaded["foo"] = nil
require "foo"

在搜索一个文件时,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名

的方式。进一步说,这种路径中的每项都是一个文件名,每项中还可以包含一个可选的问号。require

会用模块名来替换这个问号,然后根据替换的结果来检查是否存在这样一个文件。路径中的每项以分

号隔开。所以require函数只处理了分号和问号。

require用于搜索Lua文件的路径存放在变量package.path中,搜索C程序库的路径存在在变量package.cpath中。

在搜索一个文件时,require所使用的路径与传统的路径有所不同。大部分程序所使用的路径就是一连串目录,指定了 某个文件的具体位置。然而,ANSI C却没有任何关于目录的概念。所以,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名的方式。进一步说,这种路径中的每项都是一个文件名,每项中还可以包含一个可选的问号。require会用模块名来替换每个"?",然后根据替换的结果来检查是否存在这样一个文件。如果不存在,就会尝试下一项。路径中的每一项以分号隔开。例如,假设路径为:
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
那么,调用require "sql"就会试着打开以下文件:
sql
sql.lua
c:\windows\sql
/usr/local/lua/sql/sql.lua
require函数只处理了分号和问号。其他例如目录分隔符或者文件扩展名,都由路径自己定义。
require用于搜索Lua文件的路径存放在变量package.path中。当Lua启动后, 便以环境变量LUA_PATH的值来初始化这个变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化它。在使用LUA_PATH时,Lua会将其中所有的子串";;"替换成默认路径。例如,假设LUA_PATH为“mydir/?.lua;;”,那么最终路径就是"mydir/?.lua",并紧随默认路径。
如果require无法找到与模块名相符的Lua文件,它就会找C程序库。这类搜索会从变量package.cpath获取路径。而这个变量则是通过环境变量LUA_CPATH来初始化的。
当找到一个C程序库后,require就会通过package.loadlib来加载它。C程序库和Lua程序块是不同的,它没有定义一个单一的主函数,而是导出了几个C函数。具有良好行为的C程序库应该导出一个名为“luaopen_<模块名>”的函数。require会在链接完程序库后,尝试调用这个函数。
一般通过模块的名称来使用它们。但有时候必须将一个模块改名,以避免冲突。一种典型的情况就是,在测试中需要加载 同一模块的不同版本。对于一个Lua模块来说,其内部名称不是固定的,可以轻易的编辑它以改变其名称。但是却无法编辑一个二进制数据模块中的luaopen_*函数的名称。为了允许这种重命名,require用到一个小技巧:如果一个模块名中包含了连字符,require就会用连字符后面的内容来创建luaopen_* 函数名。例如,若一个模块名为a-b,require就认为它的open函数名为luaopen_b,而不是luaopen_a-b。




猜你喜欢

转载自blog.csdn.net/u012861978/article/details/54667179