Lua 中 metatable 是一個普通的 table,但其主要有以下幾個功能:
1.定義算術操作符和關系操作符的行為
2.為 Lua 函數(shù)庫提供支持
3.控制對 table 的訪問
Metatables 定義操作符行為
Metatable 能夠被用于定義算術操作符和關系操作符的行為。例如:Lua 嘗試對兩個 table 進行加操作時,它會按順序檢查這兩個 table 中是否有一個存在 metatable 并且這個 metatable 是否存在 __add 域,如果 Lua 檢查到了這個 __add 域,那么會調(diào)用它,這個域被叫做 metamethod。
Lua 中每個 value 都可以有一個 metatable(在 Lua 5.0 只有 table 和 userdata 能夠存在 metatable)。每個 table 和 userdata value 都有一個屬于自己的 metatable,而其他每種類型的所有 value 共享一個屬于本類型的 metatable。在 Lua 代碼中,通過調(diào)用 setmetatable 來設置且只能設置 table 的 metatable,在 C/C++ 中調(diào)用 Lua C API 則可以設置所有 value 的 metatable。默認的情況下,string 類型有自己的 metatable,而其他類型則沒有:
復制代碼 代碼如下:
print(getmetatable('hi')) --> table: 003C86B8
print(getmetatable(10)) --> nil
Metamethod 的參數(shù)為操作數(shù)(operands),例如:
復制代碼 代碼如下:
local mt = {}
function mt.__add(a, b)
return 'table + ' .. b
end
local t = {}
setmetatable(t, mt)
print(t + 1)
每個算術操作符有對應的 metamethod:
+ |
__add |
* |
__mul |
- |
__sub |
/ |
__div |
- |
__unm (for negation) |
% |
__mod |
^ |
__pow |
對于連接操作符有對應的 metamethod:__concat
同樣,對于關系操作符也都有對應的 metamethod:
其他的關系操作符都是用上面三種表示:
a ~= b 表示為 not (a == b)
a > b 表示為 b a
a >= b 表示為 b = a
和算術運算符不同的是,關系運算符用于比較擁有不同的 metamethod(而非 metatable)的兩個 value 時會產(chǎn)生錯誤,例外是比較運算符,擁有不同的 metamethod 的兩個 value 比較的結果是 false。
不過要注意的是,在整數(shù)類型的比較中 a = b 可以被轉換為 not (b a),但是如果某類型的所有元素并未適當排序,此條件則不一定成立。例如:浮點數(shù)中 NaN(Not a Number)表示一個未定義的值,NaN = x 總是為 false 并且 x NaN 也總為 false。
為 Lua 函數(shù)庫提供支持
Lua 庫可以定義和使用的 metamethod 來完成一些特定的操作,一個典型的例子是 Lua Base 庫中 tostring 函數(shù)(print 函數(shù)會調(diào)用此函數(shù)進行輸出)會檢查并調(diào)用 __tostring metamethod:
復制代碼 代碼如下:
local mt = {}
mt.__tostring = function(t)
return '{' .. table.concat(t, ', ') .. '}'
end
local t = {1, 2, 3}
print(t)
setmetatable(t, mt)
print(t)
另外一個例子是 setmetatable 和 getmetatable 函數(shù),它們定義和使用了 __metatable 域。如果你希望設定的 value 的 metatable 不被修改,那么可以在 value 的 metatable 中設置 __metatable 域,getmetatable 將返回此域,而 setmetatable 則會產(chǎn)生一個錯誤:
復制代碼 代碼如下:
mt.__metatable = "not your business"
local t = {}
setmetatable(t, mt)
print(getmetatable(t)) --> not your business
setmetatable(t, {})
stdin:1: cannot change protected metatable
看一個完整的例子:
復制代碼 代碼如下:
Set = {}
local mt = {}
function Set.new(l)
local set = {}
-- 為 Set 設置 metatable
setmetatable(set, mt)
for _, v in ipairs(l) do set[v] = true end
return set
end
function Set.union(a, b)
-- 檢查 a b 是否都是 Set
if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
-- error 的第二個參數(shù)為 level
-- level 指定了如何獲取錯誤的位置
-- level 值為 1 表示錯誤的位置為 error 函數(shù)被調(diào)用的位置
-- level 值為 2 表示錯誤的位置為調(diào)用 error 的函數(shù)被調(diào)用的地方
error("attempt to 'add' a set with a not-set value", 2)
end
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
function Set.intersection(a, b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
mt.__add = Set.union
mt.__mul = Set.intersection
mt.__tostring = function(s)
local l = {}
for e in pairs(s) do
l[#l + 1] = e
end
return '{' .. table.concat(l, ', ') .. '}'
end
mt.__le = function(a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
mt.__lt = function(a, b)
return a = b and not (b = a)
end
mt.__eq = function(a, b)
return a = b and b = a
end
local s1 = Set.new({1, 2, 3})
local s2 = Set.new({4, 5, 6})
print(s1 + s2)
print(s1 ~= s2)
控制 table 的訪問
__index metamethod
在我們訪問 table 的不存在的域時,Lua 會嘗試調(diào)用 __index metamethod。__index metamethod 接受兩個參數(shù) table 和 key:
復制代碼 代碼如下:
local mt = {}
mt.__index = function(table, key)
print('table -- ' .. tostring(table))
print('key -- ' .. key)
end
local t = {}
setmetatable(t, mt)
local v = t.a
__index 域也可以是一個 table,那么 Lua 會嘗試在 __index table 中訪問對應的域:
復制代碼 代碼如下:
local mt = {}
mt.__index = {
a = 'Hello World'
}
local t = {}
setmetatable(t, mt)
print(t.a) --> Hello World
我們通過 __index 可以容易的實現(xiàn)單繼承(類似于 JavaScrpit 通過 prototype 實現(xiàn)單繼承),如果 __index 是一個函數(shù),則可以實現(xiàn)更加復雜的功能:多重繼承、caching 等。我們可以通過 rawget(t, i) 來訪問 table t 的域 i,而不會訪問 __index metamethod,注意的是,不要太指望通過 rawget 來提高對 table 的訪問速度(Lua 中函數(shù)的調(diào)用開銷遠遠大于對表的訪問的開銷)。
__newindex metamethod
如果對 table 的一個不存在的域賦值時,Lua 將檢查 __newindex metamethod:
1.如果 __newindex 為函數(shù),Lua 將調(diào)用函數(shù)而不是進行賦值
2.如果 __newindex 為一個 table,Lua 將對此 table 進行賦值
如果 __newindex 為一個函數(shù),它可以接受三個參數(shù) table key value。如果希望忽略 __newindex 方法對 table 的域進行賦值,可以調(diào)用 rawset(t, k, v)
結合 __index 和 __newindex 可以實現(xiàn)很多功能,例如:
1.OOP
2.Read-only table
3.Tables with default values
Read-only table
復制代碼 代碼如下:
function readOnly(t)
local proxy = {}
local mt = {
__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{'Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'}
print(days[1])
days[2] = 'Noday' --> stdin:1: attempt to update a read-only table
有時候,我們需要為 table 設定一個唯一的 key,那么可以使用這樣的技巧:
復制代碼 代碼如下:
local key = {} -- unique key
local t = {}
t[key] = value
您可能感興趣的文章:- Lua的table庫函數(shù)insert、remove、concat、sort詳細介紹
- Lua中table的幾種構造方式詳解
- Lua中對table排序?qū)嵗?/li>
- Lua中遍歷數(shù)組和table的4種方法
- Lua中的table學習筆記
- Lua中使用table.concat連接大量字符串實例
- Lua中的table淺析
- 獲取Lua表結構(table)數(shù)據(jù)實例
- Lua教程之弱引用table
- Lua Table轉C# Dictionary的方法示例