Lua元表示例:__index和__newindex

元表裏面有元方法
設置元表的方法:

setmetatable(tab, metatable

__index元方法

prototype = {x = 0, y = 0, width = 100, height = 100}

local metatable = {}  -- 元表
metatable.__index = function(_, key)	--元表裏面有__index元方法
	return prototype[key]
end

-- 構造函數
function new(o)
	setmetatable(o, metatable)
	return o
end

w = new{x=10, y=20}
print(w.width)

w沒有width這個字段,但是有一個帶有__index方法的元表,於是Lua會將wwidth一起傳入__index方法

當元方法是函數,Lua會以表和不存在的鍵作爲參數調用該函數。

當元方法是一個表,Lua會訪問這個表。也就是說,將4到6行替換成:

metatable.__index = prototype

可以得到相同的結果。

當元方法是一個函數,開銷會比表更昂貴,但是更靈活,例如可以通過函數實現多繼承、緩存等特性。


__newindex元方法

__index用於表的查詢
__newindex用於表的更新

對錶中不存在的鍵賦值,Lua會查找__newindex元方法,如果該原方法是一個函數,則調用它,不賦值。如果該方法是一個表,則在此表賦值,而不是在原始的表中賦值。

組合使用這兩種元方法,可以實現只讀的表、具有默認值的表、繼承。


應用1:具有默認值的表

function SetDefaultValue(t, d)
	local metatable = {__index = function() return d end}
	setmetatable(t, metatable)
end

tab = {x=10, y=20}
print(tab.x, tab.z) -- 10 nil

SetDefaultValue(tab, 0)
print(tab.x, tab.z) -- 10 0

調用了SetDefaultValue之後,訪問tab中不存在的z字段,會先調用其__index元方法,該方法返回0,所以有默認值。

上述寫法開銷較大,因爲這會爲每一個表都創建一個新的閉包和一個新的元表。

以下寫法能夠使得所有的表都使用一個元表:

local metatable = {__index = function (t) return t.xxx end}
function SetDefaultValue(t, d)
	t.xxx = d
	setmetatable(t, metatable)
end

tab = {x=10, y=20}
print(tab.x, tab.z) -- 10 nil

SetDefaultValue(tab, 0)
print(tab.x, tab.z) -- 10 0

如果擔心命名衝突,可以使用唯一的鍵:

local key = {}

local metatable = {__index = function (t) return t[key] end}
function SetDefaultValue(t, d)
	t[key] = d
	setmetatable(t, metatable)
end

tab = {x=10, y=20}
print(tab.x, tab.z) -- 10 nil

SetDefaultValue(tab, 0)
print(tab.x, tab.z) -- 10 0

參考資料:《Lua程序設計 第四版 Programming in Lua, Fourth Edition》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章