1. 程式人生 > >Lua元表理解

Lua元表理解

之前接觸lua時看到了元表的使用,但是一直一知半解,藉此機會對自己的理解做一下總結,方便日後回顧

元表本質上來說是一種用來存放元方法的table。我們可以通過對應的key來得到value值,作用就是修改一個值的行為(更確切的說,這是元方法的能力),需要注意的是,這種修改會覆蓋掉原本該值可能存在的相應的預定義行為。

1. lua中的每個值都可以有一個元表,只是table和userdata可以有各自獨立的元表,而其他型別的值則共享其型別所屬的單一元表。
   lua程式碼中只能設定table的元表,至於其他型別值的元表只能通過C程式碼設定。
   多個table可以共享一個通用的元表,但是每個table只能擁有一個元表。 
我們稱元表中的鍵為事件(event),稱值為元方法(metamethod)。前述例子中的事件是"add",元方法是執行加法的函式。
可通過函式getmetatable查詢任何值的元表。
可通過函式setmetatable替換表的元表

lua查詢表中的元素時規則如下:

 1.在表中查詢,如果找到,返回該元素,找不到則繼續
  2.判斷該表是否有元表,如果沒有元表,返回nil,有元表則繼續
  3.判斷元表有沒有__index方法,如果__index方法為nil,則返回nil;如果__index方法是一個表,則重複1、2、3;如果__index方法是一個函式,則返回該函式的返回值

例如下面程式碼

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

返回為nil,如果改一下

father = {
	house=1
}
son = {
	car=1
}
setmetatable(son, father) --把son的metatable設定為father
father.__index = father
print(son.house)

返回為1,這是因為在son表中沒有house欄位,雖然father是son的元表,但是father沒有元方法,即元方法沒有指向father自己,因此找不到house欄位,這也就解釋通了為什麼會經常在lua中這麼寫了

到這裡,我們對元表有了最基本的認識,它更像是一個備用的查詢表,如果a的元表是b,在a中查詢不到的東西會嘗試從b中繼續找,當然,前提是b設定了元方法

__index元方法:

上面我們說道在b中查詢a中不存在的值時,需要給b的元方法賦值,不然仍然會像上面的例子返回為nil,因此,元方法在這裡像是一個a開啟b表查詢入口的鑰匙,我們順著上面的邏輯再理一下整個查詢思路:

我們在訪問son.house時,son中沒有house這個成員,但Lua接著發現son有元表father,於是此時father被當做元表來查詢,此時,Lua並不是直接在father中找名為house的成員,而是呼叫father的__index方法,如果__index方法為nil,則返回nil,如果是一個表(上例中father的__index方法等於自己,就是這種情況),那麼就到__index方法所指的這個表中查詢名為house的成員,於是,最終找到了house成員。
注:__index方法除了可以是一個表,還可以是一個函式,如果是一個函式,__index方法被呼叫時將返回該函式的返回值

下面引用一個菜鳥教程的例子,很能說明問題

對指定的表設定元表:

mytable = {}                          -- 普通表 
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 設為 mytable 的元表 

以上程式碼也可以直接寫成一行:

mytable = setmetatable({},{})

以下為返回物件元表:

getmetatable(mytable)                 -- 這回返回mymetatable

如果__index包含一個函式的話,Lua就會呼叫那個函式,table和鍵會作為引數傳遞給函式。
__index 元方法查看錶中元素是否存在,如果不存在,返回結果為 nil;如果存在則由 __index 返回結果。

mytable = setmetatable({key1 = "value1"}, {
  __index = function(mytable, key)
    if key == "key2" then
      return "metatablevalue"
    else
      return nil
    end
  end
})

print(mytable.key1,mytable.key2)

例項輸出結果為: value1    metatablevalue

例項解析:

  • mytable 表賦值為 {key1 = "value1"}

  • mytable 設定了元表,元方法為 __index。

  • 在mytable表中查詢 key1,如果找到,返回該元素,找不到則繼續。

  • 在mytable表中查詢 key2,如果找到,返回 metatablevalue,找不到則繼續。

  • 判斷元表有沒有__index方法,如果__index方法是一個函式,則呼叫該函式。

  • 元方法中檢視是否傳入 "key2" 鍵的引數(mytable.key2已設定),如果傳入 "key2" 引數返回 "metatablevalue",否則返回 mytable 對應的鍵值。

我們可以將以上程式碼簡單寫成:

mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)