《Programming in Lua 3》读书笔记(十一)

日期:2014.7.11

Part Ⅱ Modules and Packages

模块(module)是一些(既不是lua也不是c)能被函数require加载的代码,这些代码的作用在于创建并返回table。这个模块输出的函数、常量等都是定义在这个table中,其工作原理类似于命名空间。

Lua中所有的标准库都是模块,使用方法:
e.g.
local  m = require "math"
print(m.sin(3.14))

Lua中可以将模块存储在table中,或者作为函数的参数使用;

require函数的限制在于,不能给要加载的模块传递参数。

模块本身只能加载一次


The require Function
用require函数load模块的时候,该函数首先会从package.loaded这个table中检查该模块是否已经被加载了:此时模块的名字假定叫做modname
如果该模块已经被加载了,则返回已经被加载的值。所以,一旦某个模块已经被加载了,其他任何再需要加载该模块都是返回同样的值,不会再重新加载。
而如果该模块未被加载,则该函数会根据该模块的名字去搜索Lua文件,如果寻找到了就用loadfile函数加载,loadfile函数加载文件会返回一个函数,我们称该函数为一个loader。
如果require函数未从Lua文件中寻找到该模块,则会转向C库去寻找。如果在C库中寻找到了,该函数会用package.loadlib,调用一个luaopen_modname的函数去加载,此时的loader是loadlib的执行结果,而函数luaopen_modename则代表一个lua函数。
require函数通过两个参数调用loader:模块的名字和得到loader的文件的名字。require函数会返回loader所返回的任何值,并且将值存储至package.loaded 这个table中,今后所有对该同名模块的加载都会从这个table中得到。如果loader没有返回任何值,系统为了防止之后再一次加载该模块,require函数会认为该模块返回了true。
当我们想强制重新加载一次一个已加载了的模块,最简单的做法是将该模块的值从package.loaded 这个table中擦除掉:

这样在下一次需要加载该模块的时候,就会重新加载一次。


Renaming a module
重命名一个模块

一般情况下我们会用模块的原有名字来使用模块,但是为了防止命名冲突我们不得不重新命名某些模块。模块命名的时候采用连字符。Lua会require内部的操作函数会自动使用连字符后面的命名来操作模块的加载,如模块命名为:
e.g.
m = require "v1-mod"

而实际上内部的luaopen_(这里是模块名)函数会使用这样的命名:
luaopen_mod


Path searching
路径搜索

require的搜索路径与一般情况下的不同。ANSI C(lua 运行的平台)中没有目录这一概念。因此require所用的路径是一串模板,每个模板都指定了一个将模块名转换成文件名的方式。具体来看就是一个一个问号组成,每个模板下require都会将模块名曲代替这些问号,然后去检测是否符合要求:
?;?.lua?c:\windows\?
--假如此时模块名为 mod ,将会被转换成 
mod;mod.lua;c\windows\mod
Lua代码中path是从 package.path 中得到的
而在C库中搜索用到的path 则是从 package.cpath 中得到的。

函数package.searchpath 替搜索库将上述各种规则都封装好了,该函数接受两个参数,一个是模块名,一个是搜索路径。遵循以上的搜索规则执行搜索工作。该函数的返回值是:如果该模块存在则返回其存在的信息,否则返回nil加上错误信息。
e.g.
path = ".\\?.dll;C:\\ProgramFiles\\Lua502\\dll\\?.dll"
print(package.searchpath("X",path))
--打印信息
nil    
     no file '.\X.dll'
     no file 'C:\ProgramFiles\Lua502\dll\X.dll'


Searchers
事实上require函数比我们上述介绍的要更为复杂。这里要引申出一个概念 searchers。一个searcher就是一个函数,接受模块名作为参数然后为该模块返回一个loader或者当该模块不存在的时候返回nil。
package.searchers 里面列举了require函数所用到的searchers。require就是用这些searchers去寻找的。



The Basic Approach for Wirting Modules in Lua
Lua中写模块的方法

最简单的方法便是新创建一个table,然后将我们要用到的函数存储至该table,然后返回这个table。

简单的实现一个模块:
local M = {}

function M.new(r,i) return {r = r,i = i} end
--define constant "i"
M.i = M.new(0,1)

function M.add(c1,c2)
     return M.new(c1.r + c2.r,c1.i + c2.i)
end

function M.sub(c1,c2)
     return M.new(c1.r - c2.r,c1.i -c2.i)
end

function M.mul(c1,c2)
     return M.new(c1.r*c2.r - c1.i*c2.i,c1.r*c2.i + c1.i*c2.r)
end

local function inv(c)
     local n = c.r^2 +c.i^2
     return M.new(c.r/n,-c.i/n)
end

function M.div(c1,c2)
     return M.mul(c1,inv(c2))
end

function M.tostring(c)
     return "(" .. c.r .. "," .. c.i .. ")"
end

return M           --返回模块

--其实不是很懂。。。。


Using Environment
在模块中通过改变环境来避免对全局环境带来影响。
要注意当我们在自己的模块中改变了_ENV的时候,将会对其余的模块产生影响,因此我们在改变_ENV之前需要做一些处理。
e.g.
local M = {}
setmetatabel(M,{__index = _G})
_ENV = M 

这种情况下,M模块就包含了所有的全局变量了。
or
local M = {}
local _G = _G
_ENV = M


这种方式下,我们使用全局变量的时候要加上前缀_G,这种方式也带来了更快的运行速度,因为没有涉及到元方法的使用。

还有一种方法就是将模块中需要使用的函数定义为局部的
e.g.
local M = {}

--将需要使用的定义为局部变量
local sqrt = math.sqrt
local io = io
<span style="font-family: Verdana; font-size: 18px; orphans: 2; widows: 2;">_ENV = nil</span>

这种方式也能带来更快的运行速度。


Submodules and Packages
子模块

Lua 允许使用句点符号来实现模块的分层。如,一个模块mod.sub 就是mod模块的子模块。一个package就是一个模块的树。
reuqire函数在搜索的时候,会将句点符号做一定的转换-使用系统目录分隔符(UNIX下使用"/",windows下使用"\")

 

posted @ 2014-07-17 22:58  Le Ciel  阅读(202)  评论(0编辑  收藏  举报