Lua中每個值都可具有元表。 元表是普通的table,定義了原始值在某些特定操作下的行為。你可通過在值的原表中設定特定的欄位來改變作用於該值的操作的某些行為特徵。例如,當數字值作為加法的運算元時,Lua檢查其元表中的”__add”欄位是否有個函式。如果有,Lua呼叫它執行加法。
我們稱元表中的鍵為事件(event),稱值為元方法(metamethod)。前述例子中的事件是”add”,元方法是執行加法的函式。
不能從Lua中改變其他型別的元表(除了使用除錯庫);必須使用C API才能做到。表和完整的使用者資料具有獨立的元表(儘管多個表和使用者資料可共享元表);每種其他型別的所有值共享一個元表。所以,所有數字共享一個元表,字串也是,等等。
一個 metatable 可以控制一個物件做數學運算操作、比較操作、連線操作、取長度操作、取下標操作時的行為, metatable 中還可以定義一個函式,讓 userdata 作垃圾收集時呼叫它。 對於這些操作,Lua 都將其關聯上一個被稱作事件的指定健。 當 Lua 需要對一個值發起這些操作中的一個時, 它會去檢查值中 metatable 中是否有對應事件。 如果有的話,鍵名對應的值(元方法)將控制 Lua 怎樣做這個操作
每種操作都有元表(xx的元表__xx):sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call
其中,__index是取下標操作用於訪問 table[key], __newindex是賦值給指定下標 table[key] = value, __call是當Lua呼叫一個值時呼叫
setmetatable & getmetatable
設定和查詢元表值,setmetatable(只能用於table)和getmetatable(用於任何物件)
下面例子為一個table設定加操作
過載操作符
local mt = {}
function mt.__add(a, b)
return `table + ` .. b
end
local t = {}
setmetatable(t, mt)
print(t + 1) -- table + 1
物件導向模擬
使用metatable可以模擬出物件導向
local Bird = {}
function Bird:new()
local b = {isDead = false}
setmetatable(b, self)
self.__index = self
return b
end
function Bird:fly()
print("fly ~~~")
end
local bird1 = Bird:new()
print(bird1:fly()) -- fly ~~~
print(bird1.isDead) -- false
這裡利用index元表,當我們訪問一個表中的元素不存在時,則會觸發去尋找__index元方法。所以就可以模擬出類的封裝。
Cocos2d-x-lua裡有更完善的類和繼承的class模擬
table保護
table通過對newindex元表的值處理,可以保護table不被修改
例如cocos2d-x裡有個這樣遮蔽全域性變數的函式:
function cc.disable_global()
setmetatable(__g, {
__newindex = function(_, name, value)
error(string.format("USE " cc.exports.%s = value " INSTEAD OF SET GLOBAL VARIABLE", name), 0)
end
})
end
rawset & rawget
rawget 和 rawset 這兩個函式,可以避免Lua使用 __index 和 __newindex。一般用在index,newindex元表中以避免死迴圈。
local Bird = {}
function Bird:new()
local b = {isDead = false}
setmetatable(b, self)
self.__index = function(t, key)return 1000 end
return b
end
w = Bird:new()
print(w["haha"]) -- 1000
print(rawget(w, w["haha"])) -- nil
w["haha"] = 10
print(w["haha"]) -- 10
print(rawget(w, w["haha"])) -- nil
rawset(w, w["haha"], 1)
print(w["haha"]) -- 10
print(rawget(w, w["haha"])) -- 1