metatable,Lua中的元表,是Lua中重要的內容。
參考自:Lua中的元表與元方法(果凍想)
在Lua代碼中,只能設置table的元表。若要設置其它類型的值的元表,則必須通過C代碼來完成。還存在一個特例,對於字符串,標準的字符串程序庫爲所有的字符串都設置了一個元表,而其它類型在默認情況下都沒有元表。
下面用一個簡單的例子初探元表:
__add元方法:
Set = {}
local mt = {}
function Set.new(table)
local set = {}
setmetatable(set, mt)
for _, v in pairs(table) do
set[v] = true
end
return set
end
function Set.union(tableA, tableB)
if getmetatable(tableA) ~= mt or getmetatable(tableB) ~= mt then
print("Set.union metatable error")
return
end
local set = Set.new{} --類似Set.new({})。很關鍵,新表的生成調用new函數,保證綁定了元表
for k in pairs(tableA) do
set[k] = true
end
for k in pairs(tableB) do
set[k] = true
end
return set
end
function Set.print(table)
for k, v in pairs(table) do
print(k, " ", v)
end
end
mt.__add = Set.union
local setA = Set.new({1, 2, 3})
local setB = Set.new({4, 5})
print(getmetatable(setA))
print(getmetatable(setB))
local setC = setA + setB
local setD = 2 + setC
Set.print(setC)
table: 0x213cad0
table: 0x213cad0
Set.union metatable error
1 true
2 true
3 true
4 true
5 true
Lua是按照以下步驟尋找對元方法:
1、對於二元操作符,如果第一個操作數有元表,並且元表中有所需要的字段定義,比如我們這裏的__add元方法定義,那麼Lua就以這個字段爲元方法,而與第二個值無關;
2、對於二元操作符,如果第一個操作數有元表,但是元表中沒有所需要的字段定義,比如我們這裏的__add元方法定義,那麼Lua就去查找第二個操作數的元表;
3、如果兩個操作數都沒有元表,或者都沒有對應的元方法定義,Lua就引發一個錯誤。
上面的例子中,2 + setC 雖然 setC 具有元方法,但是其與數字 2 相加的並沒有定義相關的方法,所以在Set.union進行了判斷,保證兩個入參對應同樣的元表。
__tostring元方法:
上面我們寫了自己 print 函數,如果我們使用 lua 自帶的 print 函數呢?不能格式化輸出!怎麼辦?我們需要自己重新定義__tostring元方法,讓print可以格式化打印出table類型的數據。
function Set.tostring(set)
local tab = {}
for k in pairs(set) do
tab[#tab + 1] = k
end
return "{" .. table.concat(tab, ", ") .. "}"
end
mt.__tostring = Set.tostring
local setA = Set.new({1, 2, 3})
local setB = Set.new({4, 5})
local setC = setA + setB
print(setC)
這樣打印出:
{1, 2, 3, 4, 5}
元表的設置與查看通過 setmetatable 和 getmetatable 兩個函數可以方便得到,但如果我們不希望這麼“方便”呢?我們希望在元表被設置後不被修改,元表中的一個字段,用於保護元表,該字段是__metatable。當我們想要保護集合的元表,讓用戶既不能看也不能修改元表,那麼就需要使用__metatable字段了,在設置了該字段時,getmetatable就會返回這個字段的值,而setmetatable則會引發一個錯誤。如下:
Set = {}
local mt = {}
function Set.new(table)
local set = {}
setmetatable(set, mt)
for _, v in pairs(table) do
set[v] = true
end
mt.__metatable = "You cannot get metatable"
return set
end
local setA = Set.new({1, 2, 3})
local setB = Set.new({4, 5})
print(getmetatable(setA))
print(setmetatable(setB, {}))
你會得到:
You cannot get metatable
lua: a.lua:48: cannot change a protected metatable
這是最常見的元方法。我們訪問一個table中不存在的字段時,得到的結果是nil,但是這種狀況很容易被改變。Lua按照以下的步驟決定是返回nil還是其它值:
1、當訪問一個table的字段時,如果table有這個字段,則直接返回對應的值;
2、當table沒有這個字段,則會促使解釋器去查找一個叫__index的元方法,接下來就就會調用對應的元方法,返回元方法返回的值;
3、如果沒有這個元方法,那麼就返回nil結果。
下面用例子說明:
Person = {}
Person.default = {sex = "male", year = 0, height = 0}
Person.mt = {}
function Person.new(person)
setmetatable(person, Person.mt)
return person
end
-- 定義__index元方法
Person.mt.__index = function (table, key)
return Person.default[key]
end
local person = Person.new({sex = "female", year = 18})
print(person.sex)
print(person.year)
print(person.height)
輸出爲:
female
18
0
__index元方法不必一定是一個函數,它還可以是一個table。當它是一個函數時,Lua以table和不存在key作爲參數來調用該函數,當它是一個table時,Lua就以相同的方式來重新訪問這個table,所以上面的代碼也可以是這樣的:
Person.mt.__index = Person.default
可以達到同樣的目的。
有時候我們並不想去查詢元表,怎麼辦?
使用
rawget(table, key)
可以完成一次原生的訪問。如:
print(rawget(person, "sex"))
print(rawget(person, "year"))
print(rawget(person, "height"))
的輸出結果爲:
female
18
nil