【Lua】元表初學

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




__index 元方法

這是最常見的元方法。我們訪問一個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

發佈了129 篇原創文章 · 獲贊 47 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章