元表使表格比以前更強大。它們附加到數據上,並包含稱為元方法的值。當對附加的數據執行某種操作時,元方法會被觸發。
操作元表
添加和查找表的元表的兩個主要功能是 setmetatable() 和 getmetatable()。
local x = {}local metaTable = {} -- 元表也是表格!setmetatable(x, metaTable) -- 為 x 設置一個名為 metaTable 的元表!print(getmetatable(x)) --> table: [十六進制內存地址]
setmetatable() 函數還會返回設置元表的表,因此這兩個腳本執行的操作是相同的:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
元方法
元方法是存儲在元表中的函數。它們可以用於從調用表到將表相加甚至除表等等。這裡是可用的元方法列表:
| 方法 | 描述 |
|---|---|
| __index(table, index) | 當 table[index] 被索引時觸發,如果 table[index] 是 nil。也可以設置為一個表,在這種情況下該表將被索引。 |
| __newindex(table, index, value) | 當嘗試設置 table[index] (即 table[index] = value)時觸發,如果 table[index] 是 nil。也可以設置為一個表,在這種情況下該表將被索引。 |
| __call(table, ...) | 當表像函數一樣被調用時觸發,... 是傳遞的參數。 |
| __concat(table, value) | 當對該表使用 .. 串接運算符時觸發。 |
| __unm(table) | 當使用一元 – 運算符在該表上時觸發。 |
| __add(table, value) | + 加法運算符。 |
| __sub(table, value) | – 減法運算符。 |
| __mul(table, value) | * 乘法運算符。 |
| __div(table, value) | / 除法運算符。 |
| __idiv(table, value) | // 整數除法運算符。 |
| __mod(table, value) | % 取模運算符。 |
| __pow(table, value) | ^ 指數運算符。 |
| __tostring(table) | 當對該表調用 tostring 時觸發。 |
| __metatable | 如果存在,鎖定元表,以便 getmetatable() 將返回此值而不是元表,並且 setmetatable() 將引發錯誤。非函數值。 |
| __eq(table, value) | == 等於運算符¹ |
| __lt(table, value) | < 小於運算符¹ |
| __le(table, value) | <= 運算符¹ |
| __mode | 用於弱表,聲明表的鍵和/或值是否為弱。請注意,對 Roblox 實例的引用永遠不會是弱的。持有此類引用的表永遠不會被垃圾回收。 |
| __len(table) | 當對該對象使用 # 長度運算符時觸發。 |
| __iter(table) | 用於表示自定義迭代器,當使用泛型迭代時。 |
需要注意的是,在為算術或關係元方法編寫函數時,兩個函數參數在觸發元方法的表和另一個值之間是可互換的。例如,在進行標量的向量操作時,除法不是交換的。因此,如果您為自己的 vector2 類編寫元方法,您會希望小心考慮這兩種情況。
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a 是標量,b 是向量
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a 是向量,b 是標量
local vector, scalar = a, b
return vector2.new(vector.x / scalar, vector.y / scalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- a 和 b 均為向量
return vector2.new(a.x / b.x, a.y / b.y)
end
end
function mt.__tostring(t)
return t.x .. ", " .. t.y
end
function vector2.new(x, y)
local self = setmetatable({}, mt)
self.x = x or 0
self.y = y or 0
return self
end
local a = vector2.new(10, 5)
local b = vector2.new(-3, 4)
print(a / b) -- -3.3333333333333, 1.25
print(b / a) -- -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5
使用
有很多方法可以使用元表,例如使用 __unm 元方法使一個表為負數:
local metatable = {
__unm = function(t) -- __unm 用於一元 - 運算符
local negated = {}
for key, value in t do
negated[key] = -value -- 將表中的所有值取反
end
return negated -- 返回該表
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
這是一個使用 __index 聲明內容的有趣方法:
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
當 x 在表中被索引且未找到時,__index 被觸發。Luau 隨後在 __index 表中查找名為 x 的索引,並找到後返回它。
現在,您可以使用簡單的函數輕鬆執行此操作,但還有很多其他用途。比如這個:
local t = {10, 20, 30}print(t(5))
通常您不能調用一個表,但使用元表可以:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- 將參數(5)加到值上,然後放入新表(t)中。
end
return unpack(sum) -- 返回個別的表值
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
您還可以做很多其他事情,例如將表相加:
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
這將報錯,說明您嘗試對一個表執行算術運算,但當您嘗試使用元表時,它將能夠正常工作:
local metatable = {
__add = function(t1, t2)
local sum = {}
for key, value in t1 do
sum[key] = value
end
for key, value in t2 do
if sum[key] then
sum[key] += value
else
sum[key] = value
end
end
return sum
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
local table2 = setmetatable({13, 14, 15}, metatable)
for k, v in table1 + table2 do
print(k, v)
end
在玩元表時,您可能會遇到一些問題。如果您需要使用 __index 元方法來在一個表中創建新值,但該表的元表也具有 __newindex 元方法,則您將需要使用 Luau 的內建函數 rawset() 來設置值而不觸發任何元方法。以下代碼示例顯示如果不使用這些函數會發生什麼情況。
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- 不要以常規方式設置表中的值
end
})
print(t[1]) -- 導致棧溢出
棧溢出發生在您嘗試從自身調用函數太多次的時候。在上面的 __index 函數中,self[i] 被設置為一個值,因此當進入下一行時,self[i] 應該存在,並且應該不會調用 __index 元方法。問題在於 __newindex 不讓您以標準的 t[i] = v 方法設置值。為了克服這一點,請使用 rawset() 函數:
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- 不要以常規方式設置表中的值
end
})
print(t[1]) --> 10
使用集合數據類型
集合是一組沒有順序且沒有重複元素的項目。一個項目要麼是要麼不是包含在一個集合中。使用元表,您可以在 Luau 腳本中構建和操作集合。
基本方法
以下代碼包括基本的集合功能,讓您構建新集合、添加和移除項目、檢查集合是否包含項目,以及輸出集合的內容。
local Set = {}
Set.__index = Set
-- 通過可選的項目列表構造集合的函數
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- 將項目添加到集合的函數
function Set:add(item)
self[item] = true
end
-- 從集合中移除項目的函數
function Set:remove(item)
self[item] = nil
end
-- 檢查集合是否包含某項目的函數
function Set:contains(item)
return self[item] == true
end
-- 以逗號分隔的列表輸出集合以進行調試的函數
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
創建集合
可以通過調用 Set.new() 並傳入可選的項目數組來構建新的集合。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
注意,根據定義,集合沒有排序的概念。
添加項目
可以通過 Set:add() 方法將項目添加到現有集合中。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:add("Mango")
移除項目
要從集合中移除項目,請調用 Set:remove() 並傳入項目的名稱。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:remove("Orange")
檢查項目
要檢查某個集合是否包含特定項目,請使用 Set:contains()。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})local result1 = fruits:contains("Cherry")print(result1) -- truelocal result2 = fruits:contains("Watermelon")print(result2) -- false
附加方法
可以為集合實現其他有用的操作,讓您可以比較集合之間的項目、合併集合或從一個集合中減去另一個集合。
交集
將集合視為維恩圖,您可以獲得兩個集合的交集,即出現在兩個集合中的項目。
local function getIntersection(set1, set2)
local result = Set.new()
for key, value in set1 do
if set2:contains(key) then
result:add(key)
end
end
return result
end
local freshFruits = Set.new({"Mango", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local frozenFruits = Set.new({"Mango", "Peach", "Pineapple"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, Peach
聯集
您可以通過以下函數獲得兩個集合的聯集,即包含兩個集合中的項目且沒有重複的集合。注意,這個函數使用了元表的 __add 方法,以提供一個加法快捷方式 set1 + set2。
function Set:__add(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = true
end
return result
end
local sweetFruits = Set.new({"Apple", "Mango", "Cherry", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Peach, Lime, Apple, Cherry, Lemon, Mango
減法
您可以通過以下函數從一個集合中移除另一個集合中的所有項目。類似於上面的函數,這個函數使用元表的 __sub 方法以提供一個減法快捷方式 set1 - set2。
function Set:__sub(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = nil
end
return result
end
local allFruits = Set.new({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach