1.Lua中的模塊
Lua學習到這裏的時候我們產生一個疑問,Lua的代碼是怎麼寫的呢?全都寫在一個文件中嗎?顯然不可能。那樣的話耦合度也太高了吧?
Lua中爲我們提供了模塊的功能,類似於C#中的命名空間,我們在Lua代碼中引入模塊,就可以調用模塊中的公有變量或函數了,相當於提供了API接口。
實際上,模塊的本質是一個table,只是table中放的是變量和函數,那麼我們定義模塊的方法就很簡單了:
mymodule={} --模塊的本質是一個table
mymodule.num=1 --聲明瞭一個公有變量
mymodule.str="str" --聲明瞭一個公有變量
local a=2 --聲明瞭私有變量
local str2="str" --聲明瞭私有變量
function mymodule.getstr() --聲明瞭公有函數
return mymodule.str,str2
end
local function getnum() --聲明瞭私有函數,私有函數聲明時直接寫出函數名即可,
--不能像公有函數那樣 模塊名.函數名()來聲明
--否則會運行報錯
return mymodule.num,a
end
return mymodule
我們在另一個lua腳本中使用該模塊:
require "mymodule" --引入模塊,引號中填入的應該是文件名,而不是定義模塊時的模塊名
--爲了減少出錯,我們可以在定義模塊的時候模塊名和文件名同步
print(mymodule.num) --調用模塊中的公有變量
print(mymodule.str) --調用模塊中的公有變量
print(mymodule.a) --調用模塊中的私有變量
print(mymodule.str2) --調用模塊中的私有變量
print(mymodule.getstr()) --調用模塊中的公有函數
print(mymodule.getnum()) --調用模塊中的私有函數
運行結果:
公有變量可以正常獲取,私有變量獲取不到,公有函數可以正常調用,私有方法的調用直接報錯。
2.Lua中的元表
Lua中引入了元表的概念,我的理解是:普通表可以根據其元表中定義的行爲來對錶中的元素進行處理,說白了就是元表爲該普通表提供了自定義的方法擴展。
比如對於表,我們想要挨個輸出表中的元素,現階段只能使用for循環,但是我們能不能使用更簡單的方法呢?而不是每次挨個輸出都要經歷for循環。我們學習了元表,可以自定義一個功能行爲來解決這個問題。
在Lua中可以使用下面的方法來設置元表和獲取元表:
tab1={1,2,3.4,5}
tab2={"a","b","c"}
metatab1={}
setmetatable(tab1,metatab1) --將metatab1表設置爲tab1表的元表,返回值爲tab1
getmetatable(tab1) --獲取tab1表的元表,返回值爲tab1的元表
我們知道普通表裏面放正常的數據,那麼元表裏面放什麼呢?答案是:元方法
__index原方法:當訪問普通表中不存在的鍵時的處理方法:
這個方法是最常用的元方法,__index既可以是一個表,也可以是一個匿名函數,當__index爲表時,我們訪問了普通表中不存在的鍵的時候,會在__index表中查詢該鍵,存在則返回值,不存在則返回nil:
tab1={1,2,3.4,5}
tab2={}
tab2[9]=10
metatab1={
__index=tab2
}
setmetatable(tab1,metatab1) --將metatab1表設置爲tab1表的元表,返回值爲tab1
getmetatable(tab1) --獲取tab1表的元表,返回值爲tab1的元表
print(tab1[9],tab1[10])
輸出結果:
當__index爲一個匿名函數時,則會按照匿名函數中處理的方式來處理問題:
tab1={1,2,3.4,5}
tab2={}
tab2[9]=10
metatab1={
__index=function(tab,key)
return("該鍵不存在對應的值")
end
}
setmetatable(tab1,metatab1) --將metatab1表設置爲tab1表的元表,返回值爲tab1
getmetatable(tab1) --獲取tab1表的元表,返回值爲tab1的元表
print(tab1[9],tab1[10])
輸出結果:
引用菜鳥教程中的一段話:
Lua查找一個表元素時的規則,其實就是如下3個步驟:
- 1.在表中查找,如果找到,返回該元素,找不到則繼續
- 2.判斷該表是否有元表,如果沒有元表,返回nil,有元表則繼續。
- 3.判斷元表有沒有__index方法,如果__index方法爲nil,則返回nil;如果__index方法是一個表,則重複1、2、3;如果__index方法是一個函數,則返回該函數的返回值。
__newindex:當修改普通表中不存在的鍵時的處理方法:
__index是訪問涉及的元方法,__newindex是賦值修改涉及的元方法,使用方面都是一樣的,既可以作爲匿名函數,也可以作爲表。
當__newindex爲匿名函數時,我們爲普通表中不存在的索引元素進行賦值時會調用該匿名函數;
當__newindex爲表時,我們爲普通表中不存在的索引元素賦值時,會將鍵值對放到__newindex表中,而不會放在普通表中。
__ call:當我們把普通表當做函數來使用時的處理方法:
__call = function(tab,arg)
xxxxxxx
end
這樣我們可以直接把表作爲函數來使用:
tab={}
tab(3)
__ tostring:當我們把普通表當做字符串來使用時的處理方法:
tab1={"a","b","c"}
tab2={
__tostring = function(tab)
print("把表當做字符串來使用")
return ""
end
}
setmetatable(tab1,tab2)
print(tab1) --print方法中的標準參數就是字符串,所以說是把表作爲字符串來使用
運行結果:
注意運行結果中有一個空行,是因爲__tostring元方法返回值是"",所以也會輸出。該元方法必須要有字符串返回值,否則程序會報錯。
Lua中內置的元方法有很多,我們在使用的時候查詢即可。
3.Lua中的協同程序
協同程序的功能與Unity中的協程很相似,都是可以在程序運行的過程中控制暫停運行和啓動運行。
說起協同程序,很多時候都會與線程來比較。它們的主要區別在於:一個具有多個線程的程序可以同時運行這幾個線程,而協同程序則不同,任意時刻只有一個協同程序在運行,這個協同程序只有被明確要求掛起的時候纔會掛起暫停運行。
協同程序的運行和掛起是由開發者來控制的,而線程的資源分配是由系統來完成的。
a.協同程序的定義:
co = coroutine.create( --定義一個名爲co的協同程序
function (a,b) --定義一個匿名函數(必須是匿名函數)
print(a*b)
end
)
b.協同程序的啓動和繼續運行:
coroutine.resume(co,2,3) --啓動該協同程序並一定要傳入參數才能氣功成功
resume默認會返回boolean類型的返回值,true表示啓動成功,false表示啓動失敗。
Lua中還可以通過coroutine.wrap()方式來定義一個協同程序,兩者的區別是後者可以通過 co(2,3) 的形式來啓動協同程序,更加簡單。
c.協同程序的暫停:
coroutine.yield()
嗯,看到了一個很親切的詞語。
d.協同程序的繼續運行:
coroutine.resume()方法可以繼續運行某個協同程序,只要傳入協同程序的名稱即可,不用再次傳入其他參數。
e.協同程序的返回值:
可以通過匿名函數中的return來返回,也可以通過coroutine.yield()
中填入值來在暫停的時候返回結果。
f.協同程序的狀態:
當協同程序被創建出來還沒有開啓的時候,協同程序的狀態爲:掛起,
啓動之後到coroutine.yield之前,協同程序的狀態爲:正在運行
暫停了之後,協同程序的狀態爲:掛起
繼續運行之後,協同程序的狀態爲:正在運行
運行完成之後,協同程序的狀態爲:死亡。
至此,該協同程序運行結束。
死亡後的協同程序不能再次啓動了。
4.Lua中文件的操作
讀取:
file=io.open("data.txt","r")
io.input(file)
print(io.read()) --io.read()獲取文檔中的一行
io.close(file)
對文件進行操作時,先獲取到文件,然後打開文件,進行讀取操作後,最後關閉文件。
Lua中提供了幾種模式:
5.Lua中的垃圾回收
Lua中的垃圾回收機制是由系統自動完成的,系統將會間隔一定的時間來執行垃圾回收操作,沒有引用了的對象將會被回收。
但是開發者也可以通過主動調用相關方法來進行一次垃圾回收操作。
tab1={ "a","b","c","d" }
print(collectgarbage("count"))
tab1=ni;
print(collectgarbage("count"))
collectgarbage("collect") --主動進行一次垃圾回收
print(collectgarbage("count"))
輸出結果:
我們看到,當我們把一個表置空時,Lua使用的內存並不會馬上減少,而在我們執行了一次垃圾回收後,所用內存減少了。
所以我們說,要釋放某一個對象內存,直接將其置空即可,其實是不準確的,只有當Lua執行了垃圾回收之後,那一塊內存才真真正正被釋放。
我們不需要自己動手釋放每一塊內存區域,因爲那樣不方便,反而可能會拖累性能。
當我們需要釋放某個對象時,直接將其置空,然後等待下一個垃圾回收的時間節點到來,Lua將其釋放就可以了。
另外我認爲我們最多只需要在必要的時候控制設置垃圾收集器的相關屬性即可。
6.Lua中的面向對象編程
經過一段時間的學習,終於來到重中之重的階段了。
Lua中沒有類的概念,所以我們的封裝和繼承等面向對象的特性無法直接實現,但是我們可以通過表來模擬類的功能。
簡單的面向對象:
表中可以放鍵值對,可以放函數。那麼我們可以將鍵看做屬性,將值看做屬性值,函數的鍵看做方法名,函數本身看做方法體。
所以我們就有了簡單的面向對象定義方法:
birds={ name = "鴿子", voice="咕咕叫"}
function birds.shout()
print(birds.name..birds.voice)
end
birds.shout()
我們定義了一個鳥的對象,對象包含鳥的名字,鳥的叫聲和鳥叫的行爲,這些都是放在一張表中的,我們直接調用它叫的函數,可以輸出相應的結果:
這就可以看做是簡單的封裝。
但是這樣聲明函數的時候是存在一些問題的,比如:
birds={ name = "鴿子", voice="咕咕叫"}
function birds.shout()
print(birds.name..birds.voice)
end
a=birds
birds=nil
a.shout()
這樣寫是會報錯的,因爲雖然a現在持有了對原先對象的引用,但是在函數裏,birds.name已經不存在了。所以這樣很不方便。
因此我們使用另一種方法來定義函數:
birds={ name = "鴿子", voice="咕咕叫"}
function birds:shout()
print(self.name..self.voice)
end
a=birds
birds=nil
a:shout()
使用冒號來代替點來定義和調用函數,效果是一樣的,只是函數內會有一個self的參數來代表當前的表,這就比較方便我們操作了。否則的話在調用函數的時候需要將表名傳入纔可以。
但是這樣也會存在問題的,鳥的種類那麼多,我要聲明另一種鳥的話也要寫這麼一大堆嗎?
想想我們C#中,定義好了一個類,我們就可以new很多個對象了,多麼方便!
那麼我們在Lua中怎樣才能達到相同的效果呢?
birds={ name = "", voice=""}
function birds:shout()
print(self.name..self.voice)
end
function birds:new()
local tab={}
setmetatable(tab,{__index = self})
return tab
end
sparrow = birds:new()
dove=birds:new()
sparrow.name="麻雀"
sparrow.voice="啾啾叫"
dove.name="鴿子"
dove.voice="咕咕叫"
sparrow:shout()
dove:shout()
我們新添加了一個new方法來模擬實例化的功能。在new函數中,我們把birds賦給一個空表,然後返回這張表,這樣一來我們就有了一個新的對象。所以我們聲明瞭麻雀和鴿子兩張表,它們彼此間是沒有影響的:
這個new函數我們在C#中有一個洋氣的名字叫做:構造函數!
其實說到這裏,Lua中的繼承已經呼之欲出了。鳥是一個基礎模型,我們通過這個模型構造了鴿子和麻雀。
但是鴿子難道不是一個基礎模型嗎?鴿子還分信鴿、肉鴿、觀賞鴿呢,它們都是咕咕叫的啊。
所以我們看信鴿,繼承了鴿子,擁有咕咕叫的功能,我們再給鴿子添加一個送信的函數,那它就是信鴿了有木有?
然後我們看肉鴿,繼承了鴿子,擁有咕咕叫的功能,我們再給鴿子添加一個被殺掉吃肉的函數,那它就是肉鴿了有木有?
啊,以上就是Lua的大部分知識點,學完之後開闊眼界,神清氣爽!就等應用了。
文章中如有錯誤之處,煩請批評指正,謝謝!