lua元表與元方法

    lua中每一個值都有一套預約義的操做集合,好比數字是能夠相加的,字符串是能夠鏈接的,可是對於兩個table類型,則不能直接進行「+」操做。這須要咱們進行一些操做。在lua中有一個元表(metatable),咱們能夠經過元表來改變一個值的行爲,使其在面對一個非預約義的操做時執行一個指定的操做。好比,如今有兩個table類型的變量a和b,咱們能夠經過metatable定義如何計算表達式a+b,具體的在Lua中是按照如下步驟進行的:函數

    1.先判斷a和b二者之一是否有元表
lua

    2.檢查該元表中是否有一個叫__add的字段
spa

    3.若是找到該字段,就調用該字段對應的值,這個值對應的是一個metamethod(元方法)
prototype

    4.調用__add對應的元方法計算a和b的值
code


    在table中,咱們能夠從新定義的元方法有如下幾個:
索引

    __add(a, b) --加法ip

    __sub(a, b) --減法字符串

    __mul(a, b) --乘法原型

    __div(a, b) --除法string

    __mod(a, b) --取模

    __pow(a, b) --乘冪

    __unm(a) --相反數

    __concat(a, b) --鏈接

    __len(a) --長度

    __eq(a, b) --相等

    __lt(a, b) --小於

    __le(a, b) --小於等於

    __index(a, b) --索引查詢

    __newindex(a, b, c) --索引更新(PS:不懂的話,後面會有講)

    __call(a, ...) --執行方法調用

    __tostring(a) --字符串輸出

    __metatable --保護元表


    下面來進行一些個別的說明:

    1.__add

local mytable = setmetatable({1,2,3},{
	__add = function(mytable,newtable)
		for i=1,#newtable do 
			table.insert(mytable,#mytable+1,newtable[i])
		end
		return mytable 
	end
})

local secondtable = {4,5,6}

mytable = mytable + secondtable 

for k,v in ipairs(mytable) do 
	print(k,v)
end

   輸出爲:

1	1
2	2
3	3
4	4
5	5
6	6

   注:因爲mytable和secondtable都是table,因此它們不能簡單的相加。這個時候,lua會去找mytable和secondtable,去看它們有沒有__add方法,這時候,lua發現mytable中有__add,所以調用了__add對應的函數,__add對應的函數傳入的第一個參數是mytable,第二個參數是secondtable。實際上就是至關於把secondtable的值都附加到mytable中。

    如今你們有沒有想過,若是兩個table同時都有__add方法,那lua會執行哪一個呢?咱們看一下下面的代碼:

local mytable = setmetatable({1,2,3},{
	__add = function(mytable,newtable)
		print("這是第一個table")
		for i=1,#newtable do 
			table.insert(mytable,#mytable+1,newtable[i])
		end
		return mytable 
	end
})

local secondtable = setmetatable({4,5,6},{
	__add = function(mytable,newtable)
		print("這是第二個table")
		for i=1,#newtable do 
			table.insert(mytable,#mytable+1,newtable[i])
		end
		return mytable
    end
})

mytable = secondtable + mytable + secondtable + mytable 

for k,v in ipairs(mytable) do 
	print(k,v)
end

    輸出爲:

這是第二個table
這是第二個table
這是第二個table
1	4
2	5
3	6
4	1
5	2
6	3
7	4
8	5
9	6
10	1
11	2
12	3
13	1
14	2
15	3

    注:由輸出咱們能夠知道,哪一個table在「+」操做最前面,則執行該table的__add方法,並且該語句後面也是都是調用第一個table的__add方法。還有朋友可能不太理解爲何會輸出1-15,不該該是1-12嗎?實際上是這樣的咱們在secondtable的__add方法已經把secondtable的值就行改變了,當他執行完secondtable + mytable這個的時候,它已經變成了4,5,6,1,2,3而mytable是在secondtable + mytable + secondtable+ mytable 執行完了以後才被賦值的,因此在右邊的兩個mytable都是1,2,3


    接着咱們在看一下__index元方法,當訪問一個table中不存在的字段時,獲得的結果爲nil。這是對的,但並不是徹底正確。實際上,這些訪問會促使解釋器去查找一個叫__index的元方法。若是沒有這個元方法,那麼訪問結果如前述的胃nil。不然,就由這個元方法來提供最終結果。看下面的例子:

window = {}
window.prototype = {x=0,y=0,width=100,height=100}
window.mt = {}

function window.new(o)
	setmetatable(o,window.mt)
	return o
end

window.mt.__index = function(table,key)
	print("-------------------",key)
	for k,v in pairs(table) do 
		print(k,v)
	end
	return window.prototype[key]
end

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

    輸出:

-------------------	width
y	20
x	10
-------------------	height
y	20
x	10
100	10	20	100

    注:lua檢測到w中沒有某字段,但在其元表中卻有一個__index字段,那麼lua就會以w(table)和"width"(不存在的key)來釣魚這個__index元方法。隨後元方法用這個key來索引原型table,並返回結果。__index元方法不必定是一個函數,它還能夠是一個table,當它是一個函數時,lua以table和不存在的key做爲參數來調用該函數,而當它是一個table時,lua就以相同的方式來從新訪問這個table(會在這個table中繼續查找)。


    咱們如今來看一下__newindex元方法:

    __newindex元方法於__index相似,__newindex用於table的更新,而__index用於table的查詢。當對一個table不存在的索引賦值時,解釋器就會查找__newindex元方法。若是有這個元方法,解釋器就調用它,而不是執行賦值。若是這個元方法是一個table,解釋器就在該table中執行賦值,而不是對原來的table。看下面的例子:

local man = {
	name = "大神",
	money = 30000,
	play = function()
		print("我去打籃球")
	end
}

local t = {}

local mt = {
	__index = man,
	__newindex = function(table,key,value)
		print(key .. "不存在,不要嘗試給它賦值")
	end
}

setmetatable(t,mt)

t.play = function()
	print("我去踢足球")
end

t.play()

    輸出:

play不存在,不要嘗試給它賦值
我去打籃球

    注:由輸出咱們能夠知道t.play = function() print("hi") end並無起到做用,這是由於給t的sayhello字段賦值的時候,lua判斷play字段不存在,因此會去調用元表裏的__newindex元方法。__newindex元方法被調用的時候會傳入3個參數:table自己,字段名,想要賦予的值。

    咱們再看下面的例子:

local man = {
	name = "程序猿"
}

local updatetable = {
	name = "我是美男子"
}

local t = {}

local mt = {
	__index = man,
	__newindex = updatetable
}

setmetatable(t,mt)

print("updatetable賦值前:",updatetable.name)
t.name = "我是大帥哥"
print("updatetable賦值後:",updatetable.name)
print("t.name:",t.name)

    輸出:

updatetable賦值前:	我是美男子
updatetable賦值後:	我是大帥哥
t.name:	程序猿

    這就是上面所說的:若是這個元方法是一個table,解釋器就在該table中執行賦值,而不是對原來的table。看下面的例子。

    若是還不明白,在看下面的例子可能就會更加理解了:

mymetatable = {}
mytable = setmetatable({key = "value"},{__newindex=mymetatable})

print(mytable.key)

mytable.newkey = "newkey value"
print(mytable.newkey,mymetatable.newkey)

mytable.key = "key value"
print(mytable.key,mymetatable.key)

    輸出:

value
nil	newkey value
key value	nil

    注:當lua執行mytable.newkey = "newkey value"這句時,會去mytable中查找newkey,發現沒有newkey的時候就會去找__newindex這個元方法,而後對__newindex對應的table進行賦值,所以mytable.newkey爲nil,mymetatable.newkey爲newkey value。而執行mytable.key = "key value"語句的時候,mytable存在key,所以只修改mytable中的key對應的值。


    咱們再來看一下__call這個元方法,這個元方法會在Lua 調用一個值時調用,看下面的例子:

mytable = setmetatable({10},{
	__call = function(mytable,newtable)
		sum = 0
		for i=1,#mytable do 
			sum = sum + mytable[i]
		end

		for i=1,#newtable do 
			sum = sum + newtable[i]
		end
		return sum
	end
})

newtable = {10,20,30}
print(mytable(newtable))

    輸出:

70

    注:mytable是一個table,當調用其時(mytable(newtable))會執行__call這個元方法,第一個參數爲mytable,第二個參數爲newtable。


    __tostring元方法,它主要是更改打印語句,看下面的例子:

mytable = setmetatable({},{
	__tostring = function(mytable)
		return "我改變你的值"
  	end
})

print(mytable)

    輸出:

我改變你的值
相關文章
相關標籤/搜索