国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Lua - Lua中的模塊(module)和包(package)詳解

Lua中的模塊(module)和包(package)詳解

2020-04-14 10:48果凍想 Lua

這篇文章主要介紹了Lua中的模塊(module)和包(package)詳解,本文講解了require函數、寫一個模塊、package.loaded、module函數等內容,需要的朋友可以參考下

前言

從Lua5.1版本開始,就對模塊和包添加了新的支持,可是使用require和module來定義和使用模塊和包。require用于使用模塊,module用于創建模塊。簡單的說,一個模塊就是一個程序庫,可以通過require來加載。然后便得到了一個全局變量,表示一個table。這個table就像是一個命名空間,其內容就是模塊中導出的所有東西,比如函數和常量,一個符合規范的模塊還應使require返回這個table。現在就來具體的總結一下require和module這兩個函數。

require函數

Lua提供了一個名為require的函數用來加載模塊。要加載一個模塊,只需要簡單地調用require “<模塊名>”就可以了。這個調用會返回一個由模塊函數組成的table,并且還會定義一個包含該table的全局變量。但是,這些行為都是由模塊完成的,而非require。所以,有些模塊會選擇返回其它值,或者具有其它的效果。那么require到底是如何加載模塊的呢?

首先,要加載一個模塊,就必須的知道這個模塊在哪里。知道了這個模塊在哪里以后,才能進行正確的加載。當我們寫下require “mod”這樣的代碼以后,Lua是如何找這個mod的呢?這里面就有說道了,我這里就詳細的說一說。

在搜索一個文件時,在windows上,很多都是根據windows的環境變量path來搜索,而require所使用的路徑與傳統的路徑不同,require采用的路徑是一連串的模式,其中每項都是一種將模塊名轉換為文件名的方式。require會用模塊名來替換每個“?”,然后根據替換的結果來檢查是否存在這樣一個文件,如果不存在,就會嘗試下一項。路徑中的每一項都是以分號隔開,比如路徑為以下字符串:

復制代碼 代碼如下:

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

 

那么,當我們require “mod”時,就會嘗試著打開以下文件:

 

復制代碼 代碼如下:

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

 

可以看到,require函數只處理了分號和問好,其它的都是由路徑自己定義的。在實際編程中,require用于搜索的Lua文件的路徑存放在變量package.path中,在我的電腦上,print(package.path)會輸出以下內容:

 

復制代碼 代碼如下:

;.\?.lua;D:\Lua\5.1\lua\?.lua;D:\Lua\5.1\lua\?\init.lua;D:\Lua\5.1\?.lua;D:\Lua\5.1\?\init.lua;D:\Lua\5.1\lua\?.luac

 

如果require無法找到與模塊名相符的Lua文件,那Lua就會開始找C程序庫;這個的搜索地址為package.cpath對應的地址,在我的電腦上,print(package.cpath)會輸出以下值:

復制代碼 代碼如下:

.\?.dll;.\?51.dll;D:\Lua\5.1\?.dll;D:\Lua\5.1\?51.dll;D:\Lua\5.1\clibs\?.dll;D:\Lua\5.1\clibs\?51.dll;D:\Lua\5.1\loadall.dll;D:\Lua\5.1\clibs\loadall.dll

 

當找到了這個文件以后,如果這個文件是一個Lua文件,它就通過loadfile來加載該文件;如果找到的是一個C程序庫,就通過loadlib來加載。loadfile和loadlib都只是加載了代碼,并沒有運行它們,為了運行代碼,require會以模塊名作為參數來調用這些代碼。如果lua文件和C程序庫都找不到,怎么辦?我們試一下,隨便require一個東西,比如:

復制代碼 代碼如下:

require "jellythink"
lua: test.lua:1: module 'jellythink' not found:
     no field package.preload['jellythink']
     no file '.\jellythink.lua'
     no file 'D:\Lua\5.1\lua\jellythink.lua'
     no file 'D:\Lua\5.1\lua\jellythink\init.lua'
     no file 'D:\Lua\5.1\jellythink.lua'
     no file 'D:\Lua\5.1\jellythink\init.lua'
     no file 'D:\Lua\5.1\lua\jellythink.luac'
     no file '.\jellythink.dll'
     no file '.\jellythink51.dll'
     no file 'D:\Lua\5.1\jellythink.dll'
     no file 'D:\Lua\5.1\jellythink51.dll'
     no file 'D:\Lua\5.1\clibs\jellythink.dll'
     no file 'D:\Lua\5.1\clibs\jellythink51.dll'
     no file 'D:\Lua\5.1\loadall.dll'
     no file 'D:\Lua\5.1\clibs\loadall.dll'

 

是的,會報錯的。以上就是require的一般工作流程。

奇淫技巧

可以看到,上面總結的都是通過模塊的名稱來使用它們。但有的時候需要將一個模塊改名,以避免名稱沖突。比如有這樣的場景,在測試中需要加載同一模塊的不同版本,而獲得版本之間的性能區別。那么我們如何加載同一模塊的不同版本呢?對于一個Lua文件來說,我們可以很輕易的改掉它的名稱,但是對于一個C程序庫來說,我們是沒有辦法編輯其中的luaopen_*函數的名稱的。為了這種重命名的需求,require用到了一個小的技巧:如果一個模塊名中包含了連字符,require就會用連字符后的內容來創建luaopen_*函數名。比如:如果一個模塊的名稱為a-b,require就會認為它的open函數名為luaopen_b,并不是luaopen_a-b。現在好了,對于上面提出的不同版本進行測試的需求,就可以迎刃而解了。

寫一個我們自己的模塊

在Lua中創建一個模塊最簡單的方法是:創建一個table,并將所有需要導出的函數放入其中,最后返回這個table就可以了。相當于將導出的函數作為table的一個字段,在Lua中函數是第一類值,提供了天然的優勢。來寫一個我們自己的模塊,代碼如下:

 

復制代碼 代碼如下:

complex = {}    -- 全局的變量,模塊名稱
 
function complex.new(r, i) return {r = r, i = i} end
 
-- 定義一個常量i
complex.i = complex.new(0, 1)
 
function complex.add(c1, c2)
    return complex.new(c1.r + c2.r, c1.i + c2.i)
end
 
function complex.sub(c1, c2)
    return complex.new(c1.r - c2.r, c1.i - c2.i)
end
 
return complex  -- 返回模塊的table

 

上面就是一個最簡單的模塊。在編寫代碼的過程中,會發現必須顯式地將模塊名放到每個函數定義中;而且,一個函數在調用同一個模塊中的另一個函數時,必須限定被調用函數的名稱,然而我們可以稍作變通,在模塊中定義一個局部的table類型的變量,通過這個局部的變量來定義和調用模塊內的函數,然后將這個局部名稱賦予模塊的最終的名稱,代碼如下:

 

復制代碼 代碼如下:

local M = {}    -- 局部的變量
complex = M     -- 將這個局部變量最終賦值給模塊名
 
function M.new(r, i) return {r = r, i = i} end
 
-- 定義一個常量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
 
return complex  -- 返回模塊的table

 

這樣,我們在模塊內部其實使用的是一個局部的變量。這樣看起來比較簡單粗暴,但是每個函數仍需要一個前綴。實際上,我們可以完全避免寫模塊名,因為require會將模塊名作為參數傳給模塊。讓我們來做個試驗:

 

復制代碼 代碼如下:

local moduleName = ...
 
-- 打印參數
for i = 1, select('#', ...) do
     print(select(i, ...))
end
 
local M = {}    -- 局部的變量
_G[moduleName] = M     -- 將這個局部變量最終賦值給模塊名
complex = M
 
function M.new(r, i) return {r = r, i = i} end
 
-- 定義一個常量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
 
return complex  -- 返回模塊的table

 

將上述代碼保存為test1.lua。再寫一個文件,代碼如下:

 

復制代碼 代碼如下:

require "test"
 
c1 = test.new(0, 1)
c2 = test.new(1, 2)
 
ret = test.add(c1, c2)
print(ret.r, ret.i)

 

將上述代碼保存為test2.lua

將上述代碼放在同一個文件夾下,運行test2.lua文件,打印結果如下:

復制代碼 代碼如下:

test1
1     3

 

(PS:如果對代碼中的三個點(…)不熟悉的同學,請參考:《Lua中的函數》一文)經過這樣的修改,我們就可以完全不用在模塊中定義模塊名稱,如果需要重命名一個模塊,只需要重命名定義它的文件就可以了。

細心的同學可能注意到了模塊結尾處的return語句,這樣的一個return語句,在定義模塊時,是非常容易漏寫的,怎么辦?如果將所有與模塊相關的設置任務都集中在模塊開頭,就會更好了。消除return語句的一種方法是,將模塊table直接賦值給package.loaded,代碼如下:

復制代碼 代碼如下:

local moduleName = ...
 
local M = {}    -- 局部的變量
_G[moduleName] = M     -- 將這個局部變量最終賦值給模塊名
 
package.loaded[moduleName] = M
-- 后續代碼省略

 

示例代碼下載:點擊這里下載

package.loaded是什么?

require會將返回值存儲到table package.loaded中;如果加載器沒有返回值,require就會返回table package.loaded中的值。可以看到,我們上面的代碼中,模塊沒有返回值,而是直接將模塊名賦值給table package.loaded了。這說明什么,package.loaded這個table中保存了已經加載的所有模塊。現在我們就可以看看require到底是如何加載的呢?

1.先判斷package.loaded這個table中有沒有對應模塊的信息;
2.如果有,就直接返回對應的模塊,不再進行第二次加載;
3.如果沒有,就加載,返回加載后的模塊。

再說“環境”

大家可能注意到了,當我訪問同一個模塊中的其它函數時,都需要限定名稱,就比如上面代碼中的M。當我把模塊內部的一個local函數由私有改變成公有以后,相應的調用local函數的地方都需要修改,加上限定名稱。怎么辦?總不能每次都修改代碼吧。如何一次搞定?是否還記得《Lua中的環境概念》這篇博文,里面講到的環境概念在這里就能派上用場。

我們可以讓模塊的主程序塊有一個獨占的環境,這樣不僅它的所有函數都可共享這個table,而且它的所有全局變量也都記錄在這個table中,還可以將所有公有函數聲明為全局變量,這樣它們就都自動地記錄在一個獨立的table中。而模塊所要做的就是將這個table賦予模塊名和package.loaded。比如以下代碼就可以完成:

復制代碼 代碼如下:

local moduleName = ...
 
local M = {}    -- 局部的變量
_G[moduleName] = M     -- 將這個局部變量最終賦值給模塊名
 
package.loaded[moduleName] = M
setfenv(1, M)

 

這之后,當我們寫下下面的代碼:

 

復制代碼 代碼如下:

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


它其實是和下面的代碼是等價的:

復制代碼 代碼如下:

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

 

當我調用同一個模塊中的函數new時,也不用指定M了。這樣就可以讓我們在寫自己的模塊時,省去了前綴;還有其它好處,你可以自己想想。但是,當我們調用setfenv之后,將一個空table M作為環境后,就無法訪問前一個環境中全局變量了。這該如何是好?現在提供幾種方法。

方法一:

最簡單的方法就是在《Lua中的環境概念》一文中說的那樣,使用元表,設置__index,模擬繼承來實現。代碼如下:

 

復制代碼 代碼如下:

local moduleName = ...
 
local M = {}    -- 局部的變量
_G[moduleName] = M     -- 將這個局部變量最終賦值給模塊名
 
package.loaded[moduleName] = M
 
setmetatable(M, {__index = _G})
setfenv(1, M)

 

上述代碼很簡單,原理在之前的博文中都詳細的講過了,這里不再啰嗦了。由于需要設置元表,所有會有一定的開銷,但是可以忽略的。

方法二:

 

復制代碼 代碼如下:

local moduleName = ...
 
local M = {}    -- 局部的變量
_G[moduleName] = M     -- 將這個局部變量最終賦值給模塊名
 
package.loaded[moduleName] = M
 
local _G = _G -- 保存了全局的環境變量
setfenv(1, M)

 

這樣在自己的模塊中保存一個全局的環境變量,當我們訪問前一個環境中的變量時,就需要添加前綴_G,貌似有點小麻煩。但是,由于沒有涉及到元方法,這種方法會比方法一略快。

方法三:

這種方法是最正規的方法,就是將那些需要用到的函數或模塊聲明為局部變量,看以下代碼:

 

復制代碼 代碼如下:

local moduleName = ...
 
local M = {}    -- 局部的變量
_G[moduleName] = M     -- 將這個局部變量最終賦值給模塊名
 
package.loaded[moduleName] = M
 
local sqrt = math.sqrt -- 在我們自己的模塊中需要用到math.sqrt這個函數,所以就先保存下來
local io = io -- 需要用到io庫,也保存下來
setfenv(1, M) -- 設置完成以后,就不能再使用_G table中的內容了

 

方法三需要做的工作是最多的,而且也是最麻煩的,但是性能是最好的。怎么用,你自己看著辦吧。

module函數

大家可能也注意到了,在定義一個模塊時,前面的幾句代碼都是一樣的,就分為以下幾步:

1.從require傳入的參數中獲取模塊名;
2.建立一個空table;
3.在全局環境_G中添加模塊名對應的字段,將空table賦值給這個字段;
4.在已經加載table中設置該模塊;
5.設置環境變量。

就是這幾步,在每一個模塊的定義之前都需要加上,是不是有點麻煩,在Lua5.1中提供了一個新函數module,它包括了以上這些步驟完成的功能。在編寫一個模塊時,可以直接用以下代碼來取代前面的設置代碼:

復制代碼 代碼如下:

module(...)


就上面這一小句代碼,它會創建一個新的table,并將其賦予給模塊名對應的全局字段和loaded table,最后還會將這個table設為主程序塊的環境。默認的情況下,module不提供外部的訪問的,也就是說,你無法訪問前一個環境了,在再說“環境”一節,我專門說了三種解決方案。在使用module時是這樣解決的:

復制代碼 代碼如下:

module(..., package.seeall)


這句話的功能就好比之前的功能再加上了setmetatable(M, {__index = _G})。有了這一句代碼,基本上就可以說萬事不愁了。

 

子模塊與包

Lua支持具有層級性的模塊名,可以用一個點來分隔名稱中的層級。假設一個模塊名為mod.sub,那么它就是mod的一個子模塊。因此,可以認為模塊mod.sub會將其所有值都定義在table mod.sub中,也就是一個存儲在table mod中,且key為sub的table。就好比下述的定義:

復制代碼 代碼如下:

local mod = {sub = {}}


當require一個模塊mod.sub時,require會用原始的模塊名“mod.sub”作為key來查詢table package.loaded和package.preload,其中,模塊名中的點在搜索時沒有任何意義。但是,當搜索一個定義子模塊的文件時,require會將點轉換成另一個字符,通常就是系統的目錄分隔符,轉換之后require就像搜索其他名稱一樣來搜索這個名稱。比如路徑為以下字符串:

復制代碼 代碼如下:

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

 

那么,當我們require “mod.sub”時,就會嘗試著打開以下文件:

復制代碼 代碼如下:

mod\sub
mod\sub.lua
c:\windows\mod\sub
/usr/local/lua/mod/mod/sub.lua


通過這樣的加載策略,就可以將一個包中的所有模塊組織到一個目錄中。像這些小的功能,都會組合成很多的奇淫技巧,雖然在實際項目中用的不會很多,但是玩起來還是很有意思的。

 

總結

這一篇文章主要總結了Lua中的兩個非常重要的函數require和module。希望對大家有用。對于今天的開發來說,什么都講究模塊開發,而這篇文章總結的就是進行模塊開發時需要使用的兩個重要函數。大家在日后構建自己的模塊時,如果有哪里不懂,哪里不清楚,可以再回過頭來閱讀這篇文章,或者可以直接留言和我交流。我相信,分享與交流使我們更進步。

延伸 · 閱讀

精彩推薦
  • LuaLua中table庫函數方法介紹

    Lua中table庫函數方法介紹

    這篇文章主要介紹了Lua中table庫函數方法介紹,本文講解了concat、insert、maxn、remove、sort、foreachi等方法,需要的朋友可以參考下 ...

    腳本之家2502020-04-17
  • LuaLua中計算、執行字符串中Lua代碼的方法

    Lua中計算、執行字符串中Lua代碼的方法

    這篇文章主要介紹了Lua中計算、執行字符串中Lua代碼的方法,類似JavaScript中eval函數的功能,在Lua中也可以實現,需要的朋友可以參考下 ...

    腳本之家6322020-04-30
  • LuaLua簡介、編譯安裝教程及變量等語法介紹

    Lua簡介、編譯安裝教程及變量等語法介紹

    這篇文章主要介紹了Lua簡介、編譯安裝教程及變量等語法介紹,本文同時講解了lua注釋語法、Lua命令行方式等內容,需要的朋友可以參考下 ...

    junjie3632020-04-14
  • LuaLua教程(二):基礎知識、類型與值介紹

    Lua教程(二):基礎知識、類型與值介紹

    這篇文章主要介紹了Lua教程(二):基礎知識、類型與值介紹,本文講解了Hello World程序、代碼規范、全局變量、類型與值等內容,需要的朋友可以參考下 ...

    腳本之家5922020-04-28
  • LuaLua實現__add方法重載示例

    Lua實現__add方法重載示例

    這篇文章主要介紹了Lua實現__add方法重載示例,本文直接給出實現代碼,需要的朋友可以參考下 ...

    腳本之家7452020-04-24
  • LuaLua中的元方法__newindex詳解

    Lua中的元方法__newindex詳解

    這篇文章主要介紹了Lua中的元方法__newindex詳解,本文講解了查詢與更新、監控賦值、通過table給另一個table賦值等內容,需要的朋友可以參考下 ...

    笨木頭8872020-04-09
  • LuaLua和C語言的交互詳解

    Lua和C語言的交互詳解

    這篇文章主要介紹了Lua和C語言的交互詳解,Lua和C語言通過棧完成交互,本文結合代碼實例詳細講解了交互的方法,需要的朋友可以參考下 ...

    果凍想3702020-04-14
  • Lua深入探究Lua中的解析表達式

    深入探究Lua中的解析表達式

    這篇文章主要介紹了深入探究Lua中的解析表達式,對于其語法部分的說明和示例都超詳細,極力推薦此文!需要的朋友可以參考下 ...

    腳本之家3542020-05-05
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 日本中文字幕在线视频 | 久久国产综合 | 国产97在线 | 免费 | 亚洲精品高潮呻吟久久av | 亚洲国产精品久久久久婷婷老年 | 中文字幕精品一区久久久久 | 国产一区二区在线免费观看 | 欧美日韩国产精品一区二区 | av网站免费看 | 日韩欧美一级片 | 国产精品久久久久永久免费观看 | 欧美 日韩 中文 | 亚洲男人的天堂视频 | 日韩欧美视频 | 欧美亚洲高清 | bxbx成人精品一区二区三区 | 日韩理论在线 | 日韩激情一区 | 欧美中文在线 | 一区亚洲 | 一区二区在线视频 | 欧美视频在线观看 | 国产福利在线视频 | 天堂成人av| 日韩精品| 亚洲一区视频在线 | 精品国产免费人成在线观看 | 91粉色视频 | 激情欧美一区二区三区中文字幕 | 欧美一级精品 | 看亚洲a级一级毛片 | 午夜精品一区二区三区在线观看 | 国产精品久久久久久婷婷天堂 | 亚洲好看站| 在线激情网 | 国产成人一区二区三区在线观看 | 欧美午夜在线观看 | 精品国产仑片一区二区三区 | 人人人人澡人人爽人人澡 | 色综合中文 | 黄色网页在线观看 |