lua加载函数require和dofile

lua加载函数require和dofile

Lua提供高级的require函数来加载运行库。粗略的说require和dofile完成一样的功能但有两点不一样:html

1. require会搜索目录加载文件;python

2. require会判断是否文件已经加载避免重复加载同一文件。windows

因为上述特征,require在Lua中是加载库的更好的函数。函数

(一) requirepost

  require使用的路径和普通咱们看到的路径还有些区别,咱们通常见到的路径都是一个目录列表。require的路径是一个模式列表,每个模式指明一种由虚文件名(require的参数)转成实文件名的方法。更明确地说,每个模式是一个包含可选的问号的文件名。匹配的时候Lua会首先将问号用虚文件名替换,而后看是否有这样的文件存在。若是不存在继续用一样的方法用第二个模式匹配。例如,路径以下:ui

?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua

调用require "test"时会试着打开这些文件:lua

test
test.lua
c:\windows\test
/usr/local/lua/test/test.lua

require关注的问题只有分号(模式之间的分隔符)和问号,其余的信息(目录分隔符,文件扩展名)在路径中定义。url

  为了肯定路径,Lua首先检查全局变量LUA_PATH是否为一个字符串,若是是则认为这个串就是路径;不然require检查环境变量LUA_PATH的值,若是两个都失败;require使用固定的路径(典型的"?;?.lua")spa

  require的另外一个功能是避免重复加载同一个文件两次。Lua保留一张全部已经加载的文件的列表(使用table保存)。若是一个加载的文件在表中存在, 则require简单的返回;表中保留加载的文件的虚名,而不是实文件名。因此若是你使用不一样的虚文件名require同一个文件两次,将会加载两次该文件。好比require "foo"和require "foo.lua",路径为"?;?.lua"将会加载foo.lua两次。咱们也能够经过全局变量_LOADED访问文件名列表,这样咱们就能够判断文件是否被加载过;一样咱们也可使用一点小技巧让require加载一个文件两次。好比,require "foo"以后_LOADED["foo"]将不为nil,咱们能够将其赋值为nil,require "foo.lua"将会再次加载该文件。code

一个路径中的模式也能够不包含问号而只是一个固定的路径,好比:

?;?.lua;/usr/local/default.lua

  这种状况下,require没有匹配的时候就会使用这个固定的文件(固然这个固定的路径必须放在模式列表的最后才有意义)。在require运行一个chunk之前,它定义了一个全局变量_REQUIREDNAME用来保存被required的虚文件的文件名。咱们能够经过使用这个技巧扩展require的功能。举个极端的例子,咱们能够把路径设为"/usr/local/lua/newrequire.lua",这样之后每次调用require都会运行newrequire.lua,这种状况下能够经过使用_REQUIREDNAME的值去实际加载required的文件。

(二) dofile

  咱们知道一个lua文件是做为一个代码块(chunk)存在的,其实质就是一个函数,那么最简单的,我在一个外部lua文件中写一段代码,而后在主lua文件中用dofile调用,外部文件的代码块就会执行了。

外部lua文件在编译时并无涉及词法域:

  形式上很相似C语言的#include<...>,在其余地方定义的函数,经这么引入文件以后就能够调用了。不过lua并非定义和实现分离的语言,这样是把整个定义部分都加载进来了。加载过程大体上是: lua先加载这个外部文件,而后运行它。实际上这段外部代码是能够有返回值的,它的返回值就是require的返回值。这里咱们什么返回值都没有,执行这个外部代码的结果就是定义了这么个全局函数。注意是全局函数,虽然一般咱们直接定义的函数 都是全局函数因此都没怎么注意过,要是非要定义个局部函数在主程序块里可就看不到了。另外和前面的情形同样,外部代码块只认识“雷叔”,“牛叔”是谁它根本不知道。
        能够在外部文件里定义一堆函数,而后全都加到全局环境下。不过全局的东西用起来要当心,有一个原则是对全局的“污染”越小越好。那么天然就引入了“模块” 的概念。在lua中,模块由万能的table来充当。最天然的想法就是定义一个table,而后把要定义的函数放在这个table里,最后返回这个 table就好了。

(三) lua中的require机制
    为了方便代码管理,一般会把lua代码分红不一样的模块,而后在经过require函数把它们加载进来。如今看看lua的require的处理流程。


一、require机制相关的数据和函数
  package.path : 保存加载外部模块(lua中"模块"和"文件"这两个概念的分界比较含糊,由于这个值在不一样的时刻会扮演不一样的角色)的搜索路径,这种路径是"模板式的路径",它里面会包含可替代符号"?", 这个符号会被替换,而后lua查找这个文件是否存在,若是存在就会调用其中特定的接口。典型的值为:

 "./?.lua;./?.lc;/usr/local/?/init.lua"

若是lua代码中调用:require("hello.world"), 那么lua会依次查找:

./hello/world.lua -- 这里"hello.world"变成了"hello/world",并替换了模型"./?.lua" ./hello/world.lc .....

  (这种处理方式和python相似,只不过不须要__init__.py,也有调用python中的__init__.py) package.path在虚拟机启动的时候设置,若是存在环境变量LUA_PATH,那么就用该环境变量做为它的值,并把这个环境变量中的";;"替换为luaconf.h中定义的默认值,若是不存在该变量就直接使用luaconf.h定义的默认值.   
    package.cpath:做用和packag.path同样,但它是用于加载第三方c库的。它的初始值能够经过环境变量LUA_CPATH来设置;
    package.loadlib(libname, func):至关与手工打开c库libname, 并导出函数func返回,loadlib实际上是ll_loadlib;
   
2.require的处理流程:

require(modelname)

require(在lua中它是ll_require函数)的查找顺序以下:
       a. 首先在package.loaded查找modelname,若是该模块已经存在,就直接返回它的值;
       b. 在package.preload查找modelname, 若是preload存在,那么就把它做为loader,调用loader(L);
       c. 根据package.path的模式查找lua库modelname,这个库是经过module函数定义的,对于顶层的lua库,文件名和库名是同样的并且不须要调用显式地在lua文件中调用module函数(在ll_require函数中能够看处处理方式),也就是说lua会根据lua文件直接完成一个loader的初始化过程;
       d. 根据package.cpath查找c库,这个库是符合lua的一些规范的(export具备必定特征的函数接口),lua先已动态的方式加载该c库,而后在库中查找并调用相应名字的接口,例如:luaopen_hello_world;
       e. 以第一个"."为分割,将模块名划分为:(main, sub)的形式,根据package.cpath查找main,若是存在,就加载该库并查询相应的接口:luaopen_main_sub,例如:先查找hello库,并查询luaopen_hello_world接口
       f. 获得loder后,用modname做为惟一的参数调用该loader函数。固然参数是经过lua的栈传递的,因此loader的原型必须符合lua的规范:int LUA_FUNC(lua_State *L)
         
       ll_require会将这个loader的返回值赋给package.loaded[modelname],若是loader不返回值同时package.loaded[modelname]不存在时, ll_require就会把package.loaded[modelname]设为true。最后ll_reuqire把package.loaded[modelname]返回给调用者。

3.module的处理流程

module(name, cb1, cb2, ...)

  a. 若是package.loaded[name]是一个table,那么就把这个table做为一个mod
  b. 若是全局变量name是一个table,就把这个全局变量做为一个mod
  c. 建立table:t = {[name]=package.loaded[name], ["_NAME"]=name, ["_M"]=t, ["_PACKAGE"]=*name*(删除了最后的".XXXX"部分)}. 若是name是一个以点分割的串,那么获得的mod相似这个样子:

 hello.world -- {["hello"]={["world"]={XXXXXXX}}}

  d. 依次调用cbs:

cb1(mod), cb2(mod),...

  e. 将当前模块的环境设置为mod,同时把package.loaded[name] = mod   

相关文章
相关标签/搜索