元表

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

元表允许表变得比以前更强大。它们附属于数据,包含被称为 metamethods 的值。当特定的操作与它附属的数据一起使用时,元方法会被发射。

操纵元表

添加和查找表的可匹配功能的两个主要功能是 setmetatable()getmetatable()


local x = {}
local metaTable = {} -- 元表也是表!
setmetatable(x, metaTable) -- 给 x 一个名为 metaTable 的可转换表!
print(getmetatable(x)) --> table: [hexadecimal memory address]

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 类写了 metamethods,你想要小心地考虑两种情况。


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

__index 在表中索引了 x 并未找到时被发射。然后 Luau 通过 __index 表查找了一个名为 x 的索引,找到一个后返回了它。

现在您可以轻松地使用简单的函数来实现这一点,但那是从哪里来的还有很多。举个例子:


local t = {10, 20, 30}
print(t(5))

通常您不能调用表,但使用 metatables 您可以:


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

当与 metatables 玩耍时,你可能会遇到一些问题。如果您需要使用 __index 虚拟方法来在表中创建新值,但该表的 metatable 也有一个 __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]) -- Causes a stack overflow

当你尝试从自身调用函数太多次时,堆栈溢出会发生。在上面的 __index 函数中,self[i] 设置为值,因此当它到达下一行时,self[i] 应该存在,并且可能不会调用 __index 的 metamethod。问题是,__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

使用设置的数据类型

一个 集合 是一组没有顺序且没有重复元素的物品。一个物品既 不是 包含在设置中。使用 metatables,您可以在 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) -- 真实
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

联盟

您可以使用以下函数获得两个集合的联合,这意味着两个集合中没有重复的项目集合。请注意,这个函数使用了 metatable __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() -- 桃子、柠檬、苹果、樱桃、柠檬、芒果

减法

您可以通过以下函数将另一个集中的所有项目从另一个集中的项目中删除。与上面的函数类似,这使用了 metatable __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