具有默認(rèn)值的table
我們都知道,table中的任何字段的默認(rèn)值都是nil,但是通過元表,我們可以很容易的修改這一規(guī)定,代碼如下:
function?setDefault(tb,?defaultValue)
?????local?mt?=?{__index?=?function?()?returndefaultValue?end}
?????setmetatable(tb,?mt)
end
local?tb1?=?{x?=?10,?y?=?20}
print(tb1.x,?tb1.z)?????-->?10?nil
setDefault(tb1,?100)?-->設(shè)置默認(rèn)值
print(tb1.x,?tb1.z)?-->?10?100?這里打印的就是默認(rèn)值
可以看到,在代碼中,setDefault函數(shù)為所有需要默認(rèn)值的table創(chuàng)建了一個(gè)新的元表。如果準(zhǔn)備創(chuàng)建很多需要默認(rèn)值得table,這種方法的開銷或許就比較大了。由于在元表中默認(rèn)值defaultValue是與元方法關(guān)聯(lián)在一起的,所以setDefault無法為所有table都使用同一個(gè)元表。如果要讓具有不同默認(rèn)值得table都使用同一個(gè)元表,那么就需要將每個(gè)元表的默認(rèn)值存放在table本身中,可以使用一個(gè)額外的字段來存儲(chǔ)默認(rèn)值。例如以下代碼:
local?mt?=?{__index?=?function?(t)?returnt.___?end}
function?setDefault(tb,?defaultValue)
?????tb.___?=?defaultValue???????--?非常謝謝hellowei犀利的review。具體請(qǐng)參見評(píng)論
?????setmetatable(tb,?mt)
end
上面代碼中的“___”是為了防止名字沖突而起的名字;如果這樣的話,你還擔(dān)心名字沖突,確保key在table中的唯一性,只需要?jiǎng)?chuàng)建一個(gè)新的table,并用它作為key即可,每一個(gè)新創(chuàng)建的table都是一個(gè)唯一的地址,比如以下代碼:
local?key?=?{}?--?唯一的key
local?mt?=?{__index?=?function?(tb)?returntb[key]?end}
function?setDefault(tb,?defaultValue)
?????tb[key]?=?defaultValue
?????setmetatable(tb,?mt)
end
?
記錄table的訪問
有的時(shí)候,一種特定的需求,我們需要記錄對(duì)一個(gè)table的所有訪問,不管是查詢還是更新,我們都需要記錄日志。這如何完成?我們都知道,元表中的__index和__newindex是在table中沒有所需要訪問的index時(shí)才發(fā)揮作用的,因此,只有將一個(gè)table保持為空,然后設(shè)置__index和__newindex元方法,才有可能記錄下來所有對(duì)它的訪問。
為了監(jiān)視一個(gè)table的所有訪問,就應(yīng)該為真正的table創(chuàng)建一個(gè)代理。這個(gè)代理就是一個(gè)空的table,其中__index和__newindex元方法可用于跟蹤所有的訪問,并將訪問重定義到原來的table上。這就是思路,接下來看代碼:
local?t?=?{}?--原來的table
--?保持對(duì)原table的一個(gè)引用
local?_t?=?t
--?創(chuàng)建代理
t?=?{}
--?創(chuàng)建元表
local?mt?=?{
__index?=?function?(t,?k)
print("access?to?element?"..?tostring(k))
return_t[k]
end,
__newindex?=?function?(t,?k,?v)
print("update?of?element?"..?tostring(k))
_t[k]?=?v
end
}
setmetatable(t,?mt)
t.x?=?10?--?update?of?element?x
print(t.x)?--?access?to?element?x
如果想要同時(shí)監(jiān)視幾個(gè)table,無須為每個(gè)table創(chuàng)建不同的元表;相反,只要以某種形式將每個(gè)代理與其原table關(guān)聯(lián)起來,并且所有代理都共享一個(gè)公共的元表。這個(gè)問題與設(shè)置table默認(rèn)值相關(guān)聯(lián)的問題類似,也是將原來的table保存在代理table的一個(gè)特殊的字段中。代碼如下:
--?創(chuàng)建唯一索引
local?index?=?{}
--?創(chuàng)建元表
local?mt?=?{
?????__index?=?function?(t,?k)
??????????print("access?to?element?"..?tostring(k))
??????????returnt[index][k]
?????end,
?????__newindex?=?function?(t,?k,?v)
??????????print("update?of?element?"..?tostring(k))
??????????t[index][k]?=?v
?????end
}
function?track(t)
?????local?proxy?=?{}
?????proxy[index]?=?t
?????setmetatable(proxy,?mt)
?????returnproxy
end
local?t?=?{}
local?proxy?=?track(t)
proxy.x?=?10
print(proxy.x)
只讀的table
通過代理的概念,可以很容易的實(shí)現(xiàn)只讀的table。只需要跟蹤所有對(duì)table的更新操作,并引發(fā)一個(gè)錯(cuò)誤就好了,對(duì)于查詢時(shí),我們不用去館,只需要管對(duì)table的更新操作,廢話不說,來段簡(jiǎn)單的代碼,自然而然的一目了然了。
function?readOnly(t)
?????local?proxy?=?{}
?????--?創(chuàng)建元表
?????local?mt?=?{
??????????__index?=?t,
??????????__newindex?=?function?(t,?k,?v)
???????????????error("Attempt?to?update?a?read-only?table",?2)
??????????end
?????}
?????setmetatable(proxy,?mt)
?????returnproxy
end
local?tbDemo?=?readOnly{1,?2,?3,?4,?5}
print(tbDemo[1])
tbDemo[1]?=?20
元表中__index對(duì)應(yīng)的是原來的table,而更新原來的table時(shí),就會(huì)顯示錯(cuò)誤提示:Attempt to update a read-only table。
總結(jié)
這篇文章對(duì)Lua中的__index和__newindex的使用進(jìn)行了詳細(xì)的講解和分析,并提供了實(shí)際的代碼,主要是為了加深對(duì)Lua中元表和元方法的理解,元表和元方法在Lua中的地位太總要了,很多高級(jí)的編程技巧和特殊需求都是基于元表和元方法來實(shí)現(xiàn)了,所以,也希望大家能好好的閱讀這篇文章,同時(shí)也希望我的文章對(duì)大家有幫助。
來源網(wǎng)址:http://www.jellythink.com/archives/517