從使用的角度來看,一個(gè)模塊就是一個(gè)程序庫,可以通過Lua自身提供的require來加載。然后便得到一個(gè)全局變量,表示一個(gè)table。這個(gè)table就是像一個(gè)名字空間,其內(nèi)容就是模塊導(dǎo)出的所有東西,例如函數(shù)和常量。簡單的說,Lua中的模塊就是一個(gè)table,table中可以包括任何東西。本文首先詳細(xì)介紹模塊相關(guān)的require函數(shù),包括該函數(shù)的執(zhí)行流程以及查找模塊的路徑,然后介紹了實(shí)現(xiàn)模塊的三種方法,并給出相應(yīng)的優(yōu)缺點(diǎn)。
require函數(shù)
該函數(shù)用來加載一個(gè)模塊,即按指定的路徑和傳入的參數(shù),查找要加載的模塊。函數(shù)原型如下:
require (modname)
該函數(shù)的執(zhí)行流程如下:
I、查找表package.loaded,看modname是否已經(jīng)加載過了。若是,則require函數(shù)直接返回package.loaded[modname],否則繼續(xù)執(zhí)行,尋找模塊的加載器(loader)。
II、為了尋找加載器,require使用了數(shù)組package.searchers(Lua 5.2引入的,在之前的版本叫做package.loaders,實(shí)質(zhì)兩者只是名字不同而已),數(shù)組中每個(gè)元素是一個(gè)函數(shù)。
第一個(gè)函數(shù)用來是搜索表package.preload,若存在,則返回相應(yīng)的加載器。
第二個(gè)函數(shù)是用來獲取Lua模塊的加載器,其搜索路徑存儲(chǔ)在package.path中,它是一個(gè)字符串,比如:
復(fù)制代碼 代碼如下:
/usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;/usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;./?.lua
會(huì)用模塊名來替換每個(gè)”?”,然后根據(jù)替換的結(jié)果來檢測(cè)是否存在這樣的一個(gè)文件。這個(gè)工作是通過函數(shù)package.searchpath來做的。package.searchpath函數(shù)原型如下:
package.searchpath (name, path [, sep [, rep]])
參數(shù)path是要查找的字符串,用分號(hào)隔開;name是要查找的文件;參數(shù)sep(默認(rèn)值是”.”)可用于name中,在查找過程中用rep(默認(rèn)值是系統(tǒng)目錄的分隔符)替換。比如path是
復(fù)制代碼 代碼如下:
"./?.lua;./?.lc;/usr/local/?/init.lua"
要查找foo.a,則會(huì)嘗試查找文件
復(fù)制代碼 代碼如下:
./foo/a.lua, ./foo/a.lc, 和/usr/local/foo/a/init.lua
也就是說Lua支持具有層級(jí)性的模塊名。
第三個(gè)函數(shù)是用來獲取C模塊的加載器,其搜索路徑存儲(chǔ)在package.cpath中,它也是一個(gè)字符串,比如:
復(fù)制代碼 代碼如下:
/usr/local/lib/lua/5.2/?.so;/usr/local/lib/lua/5.2/loadall.so;./?.so
同樣會(huì)用模塊名來替換每個(gè)”?”,然后根據(jù)替換的結(jié)果來檢測(cè)是否存在這樣的一個(gè)文件。這個(gè)工作也是通過函數(shù)package.searchpath來做的。
第四個(gè)函數(shù)是用all-in-one 加載器,使用這個(gè)功能,可以使得一個(gè)包里面包含多個(gè)C子模塊。除了第一個(gè)外,其他三個(gè)除了返回加載器外,還會(huì)返回找到的文件名作為額外的值。
III、找到加載器后,require將用兩個(gè)參數(shù)調(diào)用這個(gè)加載器,一個(gè)是傳入的參數(shù)modname,另外一個(gè)是返回的額外值。如加載器返回一個(gè)不是nil的值,則把這個(gè)值賦值給package.loaded[modname]。如果加載返回返回一個(gè)nil并且加載器執(zhí)行完后package.loaded[modname]還為空,則把package.loaded[modname]賦值為true。不管那種情況,require都會(huì)返回package.loaded[modname]。如果在這個(gè)過程有任務(wù)錯(cuò)誤,require函數(shù)就產(chǎn)生一個(gè)錯(cuò)誤給調(diào)用者。
最后關(guān)于require函數(shù),值得注意的幾點(diǎn)是:
I、如果require找到的是一個(gè)lua文件,則通過loadfile來加載代碼,如果找到的是一個(gè)C程序庫,就通過loadlib來加載。注意,loadfile和loadlib都實(shí)質(zhì)上加載代碼,并沒有運(yùn)行他們。為了運(yùn)行他們,require會(huì)用模塊名作為參數(shù)來調(diào)用這些代碼。
II、若要強(qiáng)制使require對(duì)同一庫加載兩次,可以簡單刪除package.loaded中的模塊條目,即賦值相應(yīng)的條目為nil。
III、通過上面的加載過程分析知道,要加載自己的lua文件或C庫,可以通過修改package.path或package.cpath的值,把要搜索的路徑加載進(jìn)去。
IV、也可以定義自己的加載函數(shù)(除了已有的loadlib和loadfile等),比如加載ZIP文件,甚至從web上下載一個(gè)文件。
編寫模塊的方法
方法一:對(duì)于Lua5.0和5.1來說,編寫模塊最簡單的方法是使用Lua自身提供的module函數(shù)(注意在Lua 5.2中被刪除了),比如要編寫一個(gè)模塊foo,模塊文件foo_file.lua如下:
復(fù)制代碼 代碼如下:
module("foo", package.seeall)
function test()
end
則在其他文件要使用這個(gè)模塊,方式如下:
復(fù)制代碼 代碼如下:
require(“foo_file.lua”)
foo.test()
并且執(zhí)行require后,則會(huì)把模塊foo就是全局環(huán)境的一個(gè)變量了,在其他地方也可以使用。module函數(shù)原型如下:
復(fù)制代碼 代碼如下:
module (name [, ···])
module在創(chuàng)建模塊table之前,會(huì)先檢查package.loaded是否已包括了這個(gè)模塊,或者是否已存在與模塊同名的變量。如果由此找到了這個(gè)table,它就會(huì)復(fù)用這個(gè)table做為模塊。也就是說,可以用module來打開一個(gè)已創(chuàng)建的模塊。
對(duì)于module函數(shù)來說,有以下問題,比如在模塊文件module0_test中有:
復(fù)制代碼 代碼如下:
module("mymodule", package.seeall)
function foo()
print("Hello World!")
end
在另外一個(gè)文件可以這樣使用這個(gè)模塊:
復(fù)制代碼 代碼如下:
require "module0_test"
mymodule.foo() --Hello World!
mymodule.print("example") --example
對(duì)于第二個(gè)調(diào)用不是報(bào)錯(cuò)的,并且是非常奇怪的,這時(shí)因?yàn)閙odule機(jī)制是在模塊中找不的成員,則去_G全局變量找,實(shí)現(xiàn)方式類似如下:
do
local globaltbl = _G
local newenv = setmetatable({}, {
__index = function (t, k)
local v = t[k]
if v == nil then return globaltbl[k] end
return v
end,
__newindex = M,
})
if setfenv then
setfenv(1, newenv) -- for 5.1
else
_ENV = newenv -- for 5.2
end
end
在模塊找不到的成員,則到_G中去查找,并且這樣訪問也是非常低效的,因?yàn)橐ㄟ^元表來訪問成員。
方法二:該方法的基本思想是讓模塊的主程序有一個(gè)獨(dú)占的環(huán)境,這樣所有函數(shù)或變量都共享這個(gè)table,并且所有的全局變量都記錄在這個(gè)table中,當(dāng)然局部變量是不會(huì)的。代碼片段如下:
local modename = ...
local M = {}
_G[modename] = M
package.loaded[modname] = M
if setfenv then
setfenv(1, newenv) -- for 5.1
else
_ENV = newenv -- for 5.2
end
如果這樣實(shí)現(xiàn),在模塊訪問_G中的變量時(shí),需要加上前綴,比如_G.print。為了解決這個(gè)問題,有幾種方法,各有優(yōu)缺點(diǎn):
I、設(shè)置M的元表,即setmetable(M, {__index = _G})這樣做后,訪問全局變量,都要通過元表,開銷比較大。
II、設(shè)置local _G = _G,這樣做后,訪問全局變量仍然要加上前綴,但速度更快。
III、把模塊需要的全局變量都設(shè)置為局部變量,比如local io = io。這樣做會(huì)比較繁瑣,但是速度最快。
方法三:同樣是使用環(huán)境的概念。比如模塊文件如下:
復(fù)制代碼 代碼如下:
function foo()
print("Hello World!")
end
為了使用它,方法如下:
local function Import(filename)
f = loadfile(filename)
local M = {}
setmetatable(M, {__index = _G})
setfenv(f,M)()
return M
end
local FOO = Import("module2_test.lua")
FOO.foo() --output “Hello World!”
用這種方法,只需調(diào)用Import方法,其返回值就是模塊,該方法把模塊相關(guān)訪問工作,放在使用的模塊的地方了。
以上所述就是本文的全部內(nèi)容了,希望能夠?qū)Υ蠹覍W(xué)習(xí)lua有所幫助。
您可能感興趣的文章:- Lua中的模塊與module函數(shù)詳解
- Lua模塊和模塊載入淺析
- Lua中的模塊(module)和包(package)詳解
- Lua極簡入門指南(六):模塊
- Lua模塊與包學(xué)習(xí)筆記