2. 環(huán)境與模塊

2018-02-24 16:10 更新

(1) 全局變量與環(huán)境

lua 中真正存儲(chǔ)全局變量的地方不是在 _G 里面,而是在setfenv(i,table)的table中,所有當(dāng)前的全局變量都在這里面找,只不過(guò)在程序開(kāi)始時(shí)lua會(huì)默認(rèn)先設(shè)置一個(gè)變量 _G= 這個(gè)里面的table而已。所以在新設(shè)置環(huán)境后,如果還想找到之前的全局變量,通常需要附加上為新的table設(shè)置元表{_index=_G}

下面的幾個(gè)例子:

a=1
print(a)
print(_G.a)
--正常情況,輸出1,1

a=1
setfenv(1,{})
print(a)
print(_G.a)
--這時(shí)會(huì)出錯(cuò)說(shuō)找不到print,因?yàn)楫?dāng)前的全局變量表示空的,啥也找不到的

a=1
setfenv(1,{_G=_G})
_G.print(_G.a)

print(a)
--這時(shí)_G.print(_G.a)可以正常嗎,因?yàn)榭梢栽谛碌膖able中找到一個(gè)叫_G的表,這個(gè)_G有之前的奈爾全局變量,但是下面的print(a)則找不到print,因?yàn)楫?dāng)前的table{_G=_G}沒(méi)有一個(gè)叫print的東西

local mt={__index=_G}
local t={}
setmetatable(t,mt)
setfenv(1,t)
print(a)
print(_G.a)
--這是正確輸出,因?yàn)樾碌娜直聿捎弥暗谋碜稣也坏綍r(shí)的索引,原先的表里面存在print 、_G、 a這些東西

setfenv的第一個(gè)參數(shù)可以是當(dāng)前的堆棧層次,如1代表當(dāng)前代碼塊,2表調(diào)用當(dāng)前的上一層,也可以是具體的那個(gè)函數(shù)名,表示在那個(gè)函數(shù)里。

每個(gè)新創(chuàng)建的函數(shù)都將繼承創(chuàng)建它的那個(gè)函數(shù)的全局環(huán)境。

從 Lua 5.1 開(kāi)始,Lua 加入了標(biāo)準(zhǔn)的模塊管理機(jī)制,可以把一些公用的代碼放在一個(gè)文件里,以 API 接口的形式在其他地方調(diào)用,有利于代碼的重用和降低代碼耦合度。

(3) 創(chuàng)建模塊

其實(shí) Lua 的模塊是由變量、函數(shù)等已知元素組成的 table,因此創(chuàng)建一個(gè)模塊很簡(jiǎn)單,就是創(chuàng)建一個(gè) table,然后把需要導(dǎo)出的常量、函數(shù)放入其中,最后返回這個(gè) table 就行。格式如下:

-- 定義一個(gè)名為 module 的模塊
module = {}

-- 定義一個(gè)常量
module.constant = "this is a constant"

-- 定義一個(gè)函數(shù)
function module.func1()
    io.write("this is a public function!\n")
end

local function func2()
    print("this is a private function!")
end

function module.func3()
    func2()
end

return module

由上可知,模塊的結(jié)構(gòu)就是一個(gè) table 的結(jié)構(gòu),因此可以像操作調(diào)用 table 里的元素那樣來(lái)操作調(diào)用模塊里的常量或函數(shù)。不過(guò)上面的 func2 聲明為程序塊的局部變量,即表示一個(gè)私有函數(shù),因此是不能從外部訪問(wèn)模塊里的這個(gè)私有函數(shù),必須通過(guò)模塊里的共有函數(shù)來(lái)調(diào)用。

最后,把上面的模塊代碼保存為跟模塊名一樣的 lua 文件里(例如上面是 module.lua),那么一個(gè)自定義的模塊就創(chuàng)建成功。

(4) 加載模塊

Lua 提供一個(gè)名為 require 的函數(shù)來(lái)加載模塊,使用也很簡(jiǎn)單,它只有一個(gè)參數(shù),這個(gè)參數(shù)就是要指定加載的模塊名,例如:

require("<模塊名>")
-- 或者是
-- require "<模塊名>"

然后會(huì)返回一個(gè)由模塊常量或函數(shù)組成的 table,并且還會(huì)定義一個(gè)包含該 table 的全局變量。

或者給加載的模塊定義一個(gè)別名變量,方便調(diào)用:

local m = require("module")

print(m.constant)

m.func3()

require 的意義就是導(dǎo)入一堆可用的名稱,這些名稱(非local的)都包含在一個(gè)table中,這個(gè)table再被包含在當(dāng)前的全局表(“通常的那個(gè)_G”)中,這樣訪問(wèn)一個(gè)模塊中的變量就可以使用_G.table.**了,初學(xué)者可能會(huì)認(rèn)為模塊里的名稱在導(dǎo)入后直接就是在 _G 中了。

m=require module 

的 m 取決于這個(gè)導(dǎo)入的文件的返回值,沒(méi)有返回值時(shí)true,所以在標(biāo)準(zhǔn)的情況下模塊的結(jié)尾應(yīng)該 return 這個(gè)模塊的名字,這樣 m 就是這個(gè)模塊的table了。

(5) 加載機(jī)制

對(duì)于自定義的模塊,模塊文件不是放在哪個(gè)文件目錄都行,函數(shù) require 有它自己的文件路徑加載策略,它會(huì)嘗試從 Lua 文件或 C 程序庫(kù)中加載模塊。

require 用于搜索 Lua 文件的路徑是存放在全局變量 package.path 中,當(dāng) Lua 啟動(dòng)后,會(huì)以環(huán)境變量 LUA_PATH 的值來(lái)初始這個(gè)環(huán)境變量。如果沒(méi)有找到該環(huán)境變量,則使用一個(gè)編譯時(shí)定義的默認(rèn)路徑來(lái)初始化。

當(dāng)然,如果沒(méi)有 LUA_PATH 這個(gè)環(huán)境變量,也可以自定義設(shè)置,在當(dāng)前用戶根目錄下打開(kāi) .profile 文件(沒(méi)有則創(chuàng)建,打開(kāi) .bashrc 文件也可以),例如把 "~/lua/" 路徑加入 LUA_PATH 環(huán)境變量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

文件路徑以 ";" 號(hào)分隔,最后的 2 個(gè) ";;" 表示新加的路徑后面加上原來(lái)的默認(rèn)路徑。

接著,更新環(huán)境變量參數(shù),使之立即生效:

source ~/.profile

這時(shí)假設(shè) package.path 的值是:

/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua

那么調(diào)用 require("module") 時(shí)就會(huì)嘗試打開(kāi)以下文件目錄去搜索目標(biāo)

/Users/dengjoe/lua/module.lua;
./module.lua
/usr/local/share/lua/5.1/module.lua
/usr/local/share/lua/5.1/module/init.lua
/usr/local/lib/lua/5.1/module.lua
/usr/local/lib/lua/5.1/module/init.lua

如果找過(guò)目標(biāo)文件,則會(huì)調(diào)用 package.loadfile 來(lái)加載模塊。否則,就會(huì)去找 C 程序庫(kù)。搜索的文件路徑是從全局變量 package.cpath 獲取,而這個(gè)變量則是通過(guò)環(huán)境變量 LUA_CPATH 來(lái)初始。搜索的策略跟上面的一樣,只不過(guò)現(xiàn)在換成搜索的是 so 或 dll 類型的文件。如果找得到,那么 require 就會(huì)通過(guò) package.loadlib 來(lái)加載它。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)