A:訪問”table”相關的”metamethods”有兩個,__index
和__newindex
。
1、之前說過,當訪問一個”table”中不存在的域時,返回結果是nil
。這是正確的,但並不是完全正確。實際上當這種情況發生時,Lua會試圖尋找對象的”metatable”中名為__index
的”metamethod”。如果沒有這個”metamethod”,那么返回nil
,否則由這個__index
負責返回結果。
之前在“快速掌握Lua 5.3 —— 函數”的“附加 4”中提到過一種自動設定默認值的方法。那種方法是在函數內部幫你補填好默認值,但是從你創建的”table”中無法獲取函數內部提供的默認值。而現在有了__index
,實現方法就更加靈活,我們可以實現創建一張相當於帶有默認值的”table”,
-- create a namespace
Window = {}
-- create the prototype with default values
Window.prototype = {x=0, y=0, width=100, height=100, }
-- create a metatable
Window.mt = {}
-- 為所創建的"table"分配"metatable"。
function Window.new (o)
setmetatable(o, Window.mt)
return o
end
--[[ 定義"metatable"返回"Window.prototype"中存儲的默認值。 當"__index"被調用時, 參數"table"是"w",參數"key"是"width"。]]
Window.mt.__index = function (table, key)
return Window.prototype[key]
end
w = Window.new{x=10, y=20}
print(w.width) --> 100
---------------------------
__index
不像其他”metamethod”一樣需要是個函數,它可以是一張”table”。
當他是個函數時,Lua調用它,以”table”和缺失的”key”作為參數(就像上面例子中那樣)。而當他是一個”table”時,Lua直接以缺失的”key”作為它的”key”再次訪問他(相當於拿着缺失的”key”在它這張”table”中尋找),所以上面的例子中定義__index
的部分可以改為,
Window.mt.__index = Window.prototype
達到的效果是相同的。
2、__newindex
與__index
的功能是互補的關系。當向一個”table”中存入之前不存在的元素時__newindex
被調用(當你向”table”中存儲一個之前不存在的”key-value”時,Lua首先會查找對象的”metatable”中的”__newindex”域,如果找到了則調用它,否則進行正常的存入操作)。
-- 繼續上面的例子。
Window.mt.__newindex = function(t, k, v) Window.prototype[k] = v end
w["z"] = 30
print(Window.prototype.z) --> 30
print(w.z) --> 30
與__index
的特性相同,如果__newindex
是一個函數,Lua以”table”,”key”,”value”作為參數調用它(就像上面例子中那樣)。而如果是一個”table”,Lua在這張”table”上做正常的存入操作,所以__newindex
的部分更改為,
Window.mt.__newindex = Window.prototype
是相同的效果。
A:__index
和__newindex
均是在”table”中沒有指定的”key”時起作用,如果我們想監視對”table”的所有操作,唯一的方法是將”table”一直保持為空。所以我們需要這樣的一個”table”,為其分配”metatable”並設置__index
和__newindex
,在這兩個”metamethod”內部將”key-value”傳遞給真正的”table”,或者從真正的”table”中取出”key-value”。
t = {} -- original table (created somewhere)
local _t = t -- keep a private access to original table
t = {} -- create proxy
-- create metatable
local mt = {
__index = function (t,k)
io.write("*access to element " .. tostring(k) .. ", ")
return _t[k] -- access the original table
end,
__newindex = function (t,k,v)
print("*update of element " .. tostring(k) ..
" to " .. tostring(v))
_t[k] = v -- update original table
end
}
setmetatable(t, mt)
t[2] = 'hello' --> *update of element 2 to hello
print(t[2]) --> *access to element 2, hello
不幸的是這種方法不支持表的遍歷。當使用pairs()
時,遍歷的是那張代理的空表,而不是原表本身。
如果我們想監視許多的”table”,我們不需要為每個代理”table”都分配一個”metatable”。我們可以讓每個代理”table”與他們對應的”table”相關連,而這些代理”table”共享一個”metatable”,關聯的方法是將原”table”保存在代理”table”中。如果擔心域名沖突,還可以使用一個{}
作為索引,
local index = {} -- create private index
-- create metatable
local mt = {
__index = function (t,k)
io.write("*access to element " .. tostring(k) .. ", ")
--[[ 這里傳入的"t"是"proxy",如果還是通過"t[index]"的方式獲取原"table", 那么這個獲取的過程還是會被監視,又會調用"__index",進入無限循環。 所以獲取原"table"的時候需要繞過"__index"。 下面"__newindex"中同理。]]
return rawget(t, index)[k] -- access the original table
end,
__newindex = function (t,k,v)
print("*update of element " .. tostring(k) ..
" to " .. tostring(v))
rawget(t, index)[k] = v -- update original table
end
}
function track (t)
local proxy = {}
proxy[index] = t -- 將原"table"存儲在代理"table"中,以"{}"為"key"。
setmetatable(proxy, mt)
return proxy
end
table = track{}
table[2] = 'hello' --> *update of element 2 to hello
print(table[2]) --> *access to element 2, hello
A:使用代理”table”的概念很容易實現只讀”table”,我們只需要監測到更新”table”的操作時報錯。如果我們不需要監測取數據操作,我們可以將__index
指定為原”table”,這樣將更有效率。
function readOnly (t)
local proxy = {}
local mt = {
--[[ 因為操作的是代理"table",其中沒有任何數據, 所以取數據還是要去原"table"。]]
__index = t,
__newindex = function (t,k,v)
-- 這里第二個參數,指定報錯的位置是更新操作本身。
error("attempt to update a read-only table", 2)
end
}
setmetatable(proxy, mt)
return proxy
end
days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
print(days[1]) --> Sunday
days[2] = "Noday" --> attempt to update a read-only table
1、如果一個”table”的”metatable”設定了__index
和__newindex
,而我們在向”table”中存入”key-value”以及從”table”中取出”key-value”時不想觸發__index
和__newindex
,使用rawset()
和rawget()
可以繞過他們的操作,
table = {}
new_value = {}
setmetatable(table, {__newindex = new_value, __index = new_value})
table["x"] = 90
print(rawget(table, "x")) --> nil
print(new_value.x) --> 90
rawset(table, "y", 10)
print(rawget(table, "y")) --> 10
print(new_value.y) --> nil
2、有了__index
,將一個”table”中未初始化元素的默認值由nil
更改為0
也就非常的簡單了,
function setDefault (t, d)
local mt = {__index = function () return d end}
setmetatable(t, mt)
end
tab = {x=10, y=20}
print(tab.x, tab.z) --> 10 nil
setDefault(tab, 0)
print(tab.x, tab.z) --> 10 0
這段程序為每個需要默認值的”table”創建了一個”metatable”,注意,是在setDefault()
內部創建的,也就是說每個需要默認值的”table”都有一個自己獨有的”metatable”,這樣對於有許多需要默認值的”table”來說開銷會非常大(那得有很多單獨的”metatable”被創建)。
顯然默認值是與”table”相關連的,這樣我們其實可以將默認值存儲在他們對應的”table”中(比如default
域,不過這樣可能造成域名沖突,如果你不想讓這個默認值域與其他的域發生有可能的沖突,你可以使用一個特殊的域名,比如___
。如果這樣你依舊不放心的話,你可以像下面的程序那樣,使用一個”table”作為”key”),然后讓所有”table”共享一個”metatable”,這個”metatable”中有公用的返回默認值的方法。於是程序更改如下,
local key = {} -- unique key
local mt = {__index = function (t) return t[key] end}
function setDefault (t, d)
t[key] = d
setmetatable(t, mt)
end
tab = {x=10, y=20}
print(tab.x, tab.z) --> 10 nil
setDefault(tab, 0)
print(tab.x, tab.z) --> 10 0
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。