天天看點

【遊戲開發】小白學Lua——從Lua查找表元素的過程看元表、元方法

引言

在上篇部落格中,我們簡單地學習了一下Lua的基本文法。其實在Lua中有一個還有一個叫元表的概念,不得不着重地探讨一下。元表在實際地開發中,也是會被極大程度地所使用到。本篇部落格,就讓我們從Lua查找表元素的過程,來探讨學習一下Lua中的元表。

一、什麼是元表

在Lua table中我們可以通路對應的key來得到value值,但是卻無法對兩個table進行操作。是以Lua 提供了元表(Metatable),允許我們改變table的行為,每個行為關聯了對應的元方法。通俗來說,元表就像是一個“操作指南”,裡面包含了一系列操作的解決方案,例如__index方法就是定義了這個表在索引失敗的情況下該怎麼辦,__add方法就是告訴table在相加的時候應該怎麼做。這裡面的__index,__add就是元方法,下面我們詳細解讀一下元方法。

二、什麼是元方法

通過上面的知識,我們知道了通過使用元表可以定義Lua如何計算兩個table的相加操作。當Lua試圖對兩個表進行相加時,先檢查兩者之一是否有元表,之後檢查是否有一個叫"__add"的字段,若找到,則調用對應的值。"__add"等即時字段,其對應的值(往往是一個函數或是table)就是"元方法"。很多人對Lua中的元表和元方法都會有一個這樣的誤解:“如果A的元表是B,那麼如果通路了一個A中不存在的成員,就會通路查找B中有沒有這個成員”。如果說這樣去了解的話,就大錯特錯了,實際上即使将A的元表設定為B,而且B中也确實有這個成員,傳回結果仍然會是nil,原因就是B的__index元方法沒有指派。别忘了我們之前說過的:“元表是一個操作指南”,定義了元表,隻是有了操作指南,但不應該在操作指南裡面去查找元素,而__index方法則是“操作指南”的“索引失敗時該怎麼辦”。下面我們通過幾段實際的代碼來看一下Lua的表元素的查找過程以便更深入地體會上述這些概念。

下面是一些Lua表中可以重新定義的元方法:

__add(a, b) --加法
__sub(a, b) --減法
__mul(a, b) --乘法
__div(a, b) --除法
__mod(a, b) --取模
__pow(a, b) --乘幂
__unm(a) --相反數
__concat(a, b) --連接配接
__len(a) --長度
__eq(a, b) --相等
__lt(a, b) --小于
__le(a, b) --小于等于
__index(a, b) --索引查詢
__newindex(a, b, c) --索引更新(PS:不懂的話,後面會有講)
__call(a, ...) --執行方法調用
__tostring(a) --字元串輸出
__metatable --保護元表      

三、Lua的表元素查找機制

衆所周知,Lua的Table是個非常重要的資料結構,它既可以當作字典又可以當作數組。(為了适配不同的需求,table的内部結構也分為了數組和哈希表兩個部分,根據不同需求來決定使用哪個部分)當把Table當作字典來使用的時候,可以把它了解為類似C# Dictionary的東西,其元素是很多的Key-Value鍵值對。如果嘗試通路了一個表中并不存在的元素時,就會觸發Lua的一套查找機制,Lua也是憑借這個機制來模拟了類似“類”的行為。下面是一段簡單地通路表中元素的代碼:

myTable = {
    prop1 = 'Property',
}
print (myTable.prop1)
print (myTable.prop2)  --列印不存在的成員prop2      

稍微有些Lua文法的同學,一看就可以看出,上面的輸出結果為:Property  nil 。輸出為nil的原因很簡單,myTable中并沒有prop2這個成員,這符合我們平時操作Dictionary的習慣。但對于Lua的表,如果myTable有元表和元方法,情況就不同了。下面我們再看一下設定了元表和元方法的代碼:

father = {  
    prop1=1  
}  
son = {  
    prop2=1  
}  
setmetatable(son, father) --把son的metatable設定為father  
print (son.prop1)       

執行輸出的結果仍然為:nil,這正印證了上面所說的,隻設定元表是不管用的。再來看看同時設定元表和對應的元方法的代碼:

father = {  
    prop1=1  
}  
father.__index = father -- 把father的__index方法指向它本身
son = {  
    prop2=1  
}  
setmetatable(son, father) --把son的metatable設定為father  
print (son.prop1)      

執行輸出的結果為:1。

結合上述的幾個小例子,我們再來解釋一下__index元方法的含義:在上面的例子中,當通路son.prop1時,son中是沒有prop1這個成員的。接着Lua解釋器發現son設定了元表:father,(需要注意的是:此時Lua并不是直接在fahter中找到名為prop1的成員,而是先調用father的__index方法),如果__index方法為nil,則直接傳回nil。如果__index指向了一張表(上面的例子中father的__index指向了自己本身),那麼就會到__index方法所指向的這個表中去查找名為prop1的成員。最終,我們在father表中找到了prop1成員。這裡的__index方法除了可以是一個表,也可以是一個函數,如果是函數的話,__index方法被調用時會傳回該函數的傳回值。

Lua查找一個表元素的規則可以歸納為如下幾個步驟:

  • Step1:在表自身中查找,如果找到了就傳回該元素,如果沒找到則執行Step2;
  • Step2:判斷該表是否有元表(操作指南),如果沒有元表,則直接傳回nil,如果有元表則繼續執行Step3;
  • Step3:判斷元表是否設定了有關索引失敗的指南(__index元方法),如果沒有(__index為nil),則直接傳回nil;如果有__index方法是一張表,則重複執行Step1->Step2->Step3;如果__index方法是一個函數,則傳回該函數的傳回值

作者:馬三小夥兒

出處:http://www.cnblogs.com/msxh/p/7745553.html

請尊重别人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

繼續閱讀