元表使表變得比以往更強大。它們附加到數據上,並包含稱為元方法的值。當某個操作與附加的數據一起使用時,元方法會被觸發。
操作元表
添加和查找表的元表的兩個主要函數是 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 | 用於弱表,聲明表的鍵和/或值是否為弱。參見 使用弱表。 |
| __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 do
print(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使用弱表
持有表、函數、線程和 用戶數據 的變量,包括 Instances,存儲對這些對象的引用,而不是它們的副本。將一個對象分配給另一個變量會創建對同一對象的另一個引用:
local map = {key = 1}
local mapReference = map
print(map == mapReference) --> true
print(map == {key = 1}) --> false
local function func() end
local funcReference = func
print(func == funcReference) --> true
local part = Instance.new("Part")
local partReference = part
print(part == partReference) --> true不再通過強引用可達的對象變為垃圾回收的候選。將一個變量設置為 nil 不會使對象成為垃圾回收的候選,如果另一個強引用仍然指向該對象:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [十六進制內存地址]雖然 child 不再指向子表,但 parent.Child 仍然指向它。只要父表保持可達,它的引用就會使子表保持可達。
弱表 持有鍵、值或兩者,而不阻止垃圾回收器回收其他不可達對象。設置表的元表的 __mode 字段以控制哪些引用是弱的:
- "k" 使鍵為弱。
- "v" 使值為弱。
- "kv" 或 "vk" 使鍵和值都為弱。
以下代碼示例創建一個具有弱值的表:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
end在 do 塊結束後,存儲在 children.example 中的值沒有強引用,並變為垃圾回收的候選。當垃圾回收器回收子表時,Luau 會從 children 中刪除其條目。垃圾回收是非確定性的,因此不要依賴條目消失的時間。
你留下的任何模式都保持強。__mode = "v" 表中的鍵保持強引用,因此作為鍵存儲的對象保持可達,其條目保持在表中。
對於 Instances,垃圾回收適用於 Luau 引用,而不是對象本身。引擎將實例數據存儲在 Luau VM 之外,因此收集 Luau 引用不會刪除實例,而仍然在數據模型中的實例不會保持其 Luau 引用。要釋放實例,請調用 Instance:Destroy() 並清除對它的所有剩餘引用。
使用集合數據類型
集合 是一組沒有順序且沒有重複元素的項目。項目要麼 存在於 集合中,要麼 不在 集合中。使用元表,你可以在 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) -- true
local 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