Metatábulos

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

Metamétodos permitem que as tabelas se tornem mais poderosas do que antes. Eles são anexados a dados e contêm valores chamados metamétodos. Metamétodos são disparados quando uma certa ação é usada com o datum ao qual está anexado.

Considere o seguinte código:


local list = {1, 2}
print(list[3])

Você pode esperar que este código pesquise a lista pelo terceiro índice da lista, não encontre nada e devolva nulo. Isso não é correto, no entanto. O que realmente acontece é que o código pesquisa a lista pelo terceiro índice, não encontra nada e, em seguida, verifica se há um metatable anexado à tabela, retornando nulo se não houver nenhum.

Manipulando Metatables

As duas funções primárias para adicionar e encontrar o metatable de uma tabela são setmetatable e getmetatable


local x = {}
local metaTable = {} -- metaTables também são tabelas!
setmetatable(x, metaTable) -- Dê x a uma metatable chamada metaTable!
print(getmetatable(x)) --> table: [hexadecimal memory address]

A função setmetatable também retorna a tabela da qual você está configurando o metatable, então esses dois scripts fazem a mesma coisa:


local x = {}
setmetatable(x, {})

local x = setmetatable({}, {})

Metamétodos

Metamétodos são as funções que são armazenadas dentro de um metatable. Eles podem ir desde chamar uma tabela, até adicionar uma tabela, até mesmo dividir tabelas também. Aqui está a lista de metamétodos disponíveis:

MétodoDescripción
__index(tabla, índice)Se dispara cuando la tabla [index] está indexada, si la tabla [index] es nula. También se puede establecer como una tabla, en cuyo caso esa tabla se indexará.
__newindex(tabla, índice, valor)Se dispara cuando se intenta establecer la tabla [index] (tabla [index] = valor), si la tabla [index] es nula. También se puede establecer en una tabla, en cuyo caso esa tabla se indexará.
__llamada(mesa, ...)Se dispara cuando se llama la tabla como una función, ... es los argumentos que se pasaron.
__concat(tabla, valor)Dispara cuando se usa el operador .. concatenation en la mesa.
__unm(mesa)Dispara cuando se usa el operador unary en la mesa.
__add(tabla, valor)El operador +addition.
__sub(tabla, valor)El operador de resta de .
__mul(tabla, valor)El operador * de multiplicación.
__div(tabla, valor)El operador / división.
__idiv(tabla, valor)El operador de división de piso //.
__mod(tabla, valor)El operador de módulo %.
__pow(tabla, valor)El operador ^ de exponenciación.
__tostring (mesa)Se dispara cuando se llama a tostrar en la mesa.
__métatableSi está presente, bloquea el metatable para que getmetatable lo devuelva en lugar del error de metatable y setmetatable. Valor no funcional.
__eq(tabla, valor)El == igual al operador 1
__lt(tabla, valor)El < menos que el operador1
__le(tabla, valor)El <= operador1
__modoUsado en tablas débiles, declarando si las llaves y / o valores de una tabla son débiles. Nota: Las referencias a las instancias de Roblox nunca son débiles. Las tablas que contienen tales referencias nunca serán recolectadas como basura.
__gc(mesa)Se dispara cuando la mesa está recolectando basura. Nota: En Roblox, este metamétodo está desactivado.
__len(mesa)Se dispara cuando se usa el operador de longitud # en el objeto.
__iter(tabla)Se usa para denotar un iterador personalizado al usar la iteración generalizada.

Deve-se notar que ao escrever funções para metamétodos aritméticos ou relacionais, os dois parâmetros da função são intercambiáveis entre a tabela que disparou o metamétodo e o outro valor. Por exemplo, ao fazer operações de vetor com escalares, a divisão não é comutativa. Portanto, se você estivesse escrevendo metamétodos para sua própria classe vector2, você gostaria de ter cuidado em contabilizar qualquer cenário.


local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a é um escalar, b é um vetor
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a é um vetor, b é um escalar
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
-- tanto a quanto b são vetores
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,33333333333, 1,25
print(b / a) -- -0,3, 0,8
print(2 / a) -- 0,2, 0,4
print(a / 2) -- 5, 2.5

Usando Metatables

Existem muitas maneiras de usar metatables, por exemplo, o __unm metamétodo (para fazer uma tabela negativa):


local metatable = {
__unm = function(t) -- __unm é para o operador unário
local negated = {}
for key, value in t do
negated[key] = -value -- anular todos os valores nesta tabela
end
return negated -- retornar a mesa
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12

Aqui está uma maneira interessante de declarar coisas usando __index:


local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1

__index foi disparado quando x foi indexado na tabela e não foi encontrado. Lua então pesquisou na tabela __index por um índice chamado x e, encontrando um, retornou isso.

Agora você pode facilmente fazer isso com uma função simples, mas há muito mais de onde isso veio. Pegue isso como exemplo:


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

Agora, obviamente você não pode chamar uma mesa. Isso é louco, mas (surpresa, surpresa!) com metatables você pode.


local metatable = {
__call = function(t, param)
local sum = {}
for i, value in t do
sum[i] = value + param -- Adicione o argumento (5) ao valor e coloque-o na nova tabela (t).
end
return unpack(sum) -- Retorna os valores das tabelas individuais
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35

Você pode fazer muito mais também, como adicionar mesas!


local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
end

Isso falhará ao dizer que você está tentando realizar aritmética em uma mesa. Vamos tentar isso com um metatável.


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

Caixas de Uso

Agora, todos esses exemplos podem ser implementados como uma função simples, mas você pode fazer muito mais do que isso. Vamos tentar um programa simples que memorizará um número quando um problema de matemática possivelmente retardado for colocado nele.

Para isso, usaremos o __index metamétodo apenas para simplificar:


local function mathProblem(num)
for i = 1, 20 do
num = math.floor(num * 10 + 65)
end
for i = 1, 10 do
num += i - 1
end
return num
end
local metatable = {
__index = function(object, key)
local num = mathProblem(key)
object[key] = num
return num
end
}
local t = setmetatable({}, metatable)
print(t[1]) -- Será lento porque é a primeira vez que usa este número, então tem que correr a função matemática.
print(t[2]) -- será lento porque é a primeira vez que este número é usado.
print(t[1]) -- will be fast because it's just grabbing the number from the table.

Rawset, Rawget, Rawequal

Ao jogar com metatables, você pode encontrar alguns problemas. O que acontece se você precisar usar o __index metamétodo para criar novos valores em uma tabela, mas o metatable dessa tabela também tem um __newindex metamétodo nela? Você vai querer usar o conjunto de funções built-in Lua para definir o valor sem invocar nenhum metamétodo. Tome o código a seguir como um exemplo do que acontece se você não usar essas funções.


local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10 -- apenas como um exemplo
return self[i]
end,
__newindex = function(self, i, v)
--não faça nada porque não queremos que você defina valores na tabela da maneira normal
end
})
print(t[1]) -- Causes a C-Stack overflow

Agora, por que isso causaria um excesso de pilha? O excesso de pilha acontece quando você tenta chamar uma função de si mesma muitas vezes, mas o que causaria isso? Na função __index, definimos self[i] como um valor, então quando chegar à próxima linha, self[i] deve existir, então não chamará o __index metamétodo, certo?

O problema é que __newindex não nos permite definir o valor. Sua presença impede que os valores sejam adicionados à tabela com o método padrão t[i] = v. Para superar isso, você usa a função rawset.


local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
--não faça nada porque não queremos que você defina valores na tabela da maneira normal
end
})
print(t[1]) -- prints 10

Usando o tipo de dados definido

Um conjunto **** é uma coleção de itens sem ordem e sem elementos duplicados. Um item é ou não é contido em um configurar. Usando metatables, você pode construir e manipular conjuntos dentro de scripts Lua.

Métodos Básicos

O código a seguir inclui a funcionalidade básica do conjunto, permitindo que você construa novos conjuntos, adicione e remova um item, verifique se um conjunto contém um item e exiba o conteúdo de um configurar.


local Set = {}
Set.__index = Set
-- Função para construir um conjunto a partir de uma lista opcional de itens
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Função para adicionar um item a um configurar
function Set:add(item)
self[item] = true
end
-- Função para remover um item de um configurar
function Set:remove(item)
self[item] = nil
end
-- Função para verificar se um conjunto contém um item
function Set:contains(item)
return self[item] == true
end
-- Função para saída definida como uma lista delimitada por vírgulas para depuração
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end

Criar Conjunto

Um novo conjunto pode ser construído chamando Set.new() com um conjunto opcional de itens para adicionar.


local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})

Note que, por definição, um conjunto não tem conceito de ordenação.

Adicionar Item

Adicionar um item a um conjunto existente pode ser feito através do Set:add() método.


local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")

Remover Item

Para remover um item de um configurar, chame Set:remove() com o nome do item.


local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")

Verifique se há algum item

Para verificar se um conjunto contém um item específico, use Set:contains() .


local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local result1 = fruits:contains("Cherry")
print(result1) -- verdadeiro
local result2 = fruits:contains("Watermelon")
print(result2) -- false

Métodos Adicionais

Outras operações úteis podem ser implementadas para conjuntos, permitindo que você compare itens entre conjuntos, combine conjuntos ou subtraia um conjunto de outro.

Intersecção

Ao considerar conjuntos como diagramas de Venn, você pode obter a interseção **** de dois conjuntos da seguinte forma, ou seja, os itens que aparecem em ambos os conjuntos .


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

União

Você pode obter a união de dois conjuntos com a seguinte função, ou seja, uma coleção dos itens em ambos os conjuntos sem duplicatas. Observe que esta função usa o método metabilizável __add para fornecer um atalho adicional de 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() -- Pêssego, Limão, Maçã, Cereja, Limão, Manga

Subtração

Você pode remover todos os itens de um conjunto dos itens de outro conjunto através da seguinte função. Semelhante à função acima, isso usa o método metável __sub para fornecer um atalho de subtração de 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