Metatables permitem que as tabelas se tornem mais poderosas do que antes. Eles são conectados a dados e contêm valores chamados metamétodos. Metamétodos são executados quando uma certa ação é usada com o datum que está conectado.
Considerar o seguinte código:
local list = {1, 2}print(list[3])
Você pode esperar que este código pesquise através da lista para o terceiro índice na lista, encontre nada e retorne nil. Isso não está correto, no entanto. O que acontece é que o código pesquisa através da lista para o terceiro índice, encontre nada e depois verifica se há uma tabela anexada à tabela, retornando nil se não houver uma.
Manipulando Metatables
As duas funções principais para adicionar e encontrar uma metabela de uma tabela são setmetatable e getmetatable
local x = {}local metaTable = {} -- MetaTables são tabelas também!setmetatable(x, metaTable) -- Dê x uma metabela chamada metaTable!print(getmetatable(x)) --> table: [hexadecimal memory address]
A função setmetatable também retorna a tabela que você está definindo o metabulso, então esses dois scripts fazem a mesma coisa:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Métodos de Metametria
Metamétodos são as funções que são armazenadas dentro de uma metatabela. Eles podem ir de chamar uma tabela, adicionar uma tabela, até mesmo dividir tabelas também. Aqui está a lista de métodos disponíveis:
Método | Descrição |
---|---|
__index(tabela, índice) | É acionado quando a tabela [index] é indexada, se a tabela [index] for nil. Também pode ser definido para uma tabela, neste caso a tabela será indexada. |
__newindex(tabela, índice, valor) | Fires quando a tabela[index] tenta ser definida (tabela[index] = valor), se a tabela[index] for nil. Também pode ser definido para uma tabela, neste caso a tabela[index] será indexada. |
__call(tabela, ...) | Ocorre quando a tabela é chamada como uma função, ... são os argumentos que foram passados. |
__concat(tabela, valor) | Incêndia quando o operador de concatação.. for usado na tabela. |
__unm(tabela) | Dispara quando o operador unário - é usado na tabela. |
__add(tabela, valor) | O operador de adição. |
__sub(tabela, valor) | O operador - subtraição. |
__mul(tabela, valor) | O * operador de multiplicação. |
__div(tabela, valor) | O operador / divisão. |
__idiv(tabela, valor) | O // operador de divisão de piso. |
__mod(tabela, valor) | O operador modulo%. |
__pow(tabela, valor) | O operador ^ de exponencial. |
__tostring(tabela) | Fired quando o tostring é chamado na tabela. |
__metabela | Se presente, bloqueia a tabela de metas para que getmetables retorne este em vez da tabela de metas e setmetables irá ocorrer um erro. Valor não funcional. |
__eq(tabela, valor) | O == igual a operador¹ |
__lt(tabela, valor) | O < menos que operador¹ |
__le(tabela, valor) | O operador << |
__modo | Usado em tabelas fracas, declarando se as chaves e/ou valores de uma tabela são fracas. Nota: referências a instâncias Roblox nunca são fracas. Tabelas que contêm tais referências nunca serão coletadas. |
__gc(tabela) | Fired quando a tabela é recolhida em lixo. Nota: No Roblox, este método metamétrico está desativado. |
__len(tabela) | Iniciado quando o operador # comprimento é usado no Objeto. |
__iter(tabela) | Usado para designar um itador personalizado ao usar iteração geralizada. |
Deve ser notado que ao escrever funções para métodos aritméticos ou relacionais, os dois parâmetros de função são interchangeáveis entre a tabela que executou o método e a outra value. Por exemplo, ao fazer operações de vértice com divisão de escalas não é comutativo. Portanto, se você estiver escrevendo métodos para seu próprio classe veículo2, você deve estar cuidadoso para contar para ambos os cenários.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a é um valor, b é um vetor
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a é um vértice, b é um vetor
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
-- ambos a e 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.333333333333, 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 método __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 -- negue todos os valores nesta tabela
end
return negated -- retornar a tabela
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 executado quando x foi indexado na tabela e não encontrado. Lua então pesquisou através da tabela __index para um índice chamado x, e, encontrando um, retornou que.
Agora você pode facilmente fazer isso com uma função simples, mas há muito mais onde isso veio. Take this for example:
local t = {10, 20, 30}print(t(5))
Agora, obviamente, você não pode chamar uma tabela. Isso é loucura, mas (surpresa, surpresa!) com metatabelas você pode.
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Adicione o argumento (5) ao valor, então coloque-o na nova tabela (t).
end
return unpack(sum) -- Retorne os valores da tabela individual
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Você pode fazer muito mais também, como adicionar tabelas!
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Isso irá ocorrer dizendo que você está tentando executar aritmética em uma tabela. Vamos tentar isso com uma metabela.
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
Use Cases
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 possível problema de matemática for colocado nele.
Para este, usaremos o método __index metamétodo apenas para torná-lo simples:
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]) -- Vai ser lento porque é a primeira vez usando esse número, então ele tem que executar a função de matemática.
print(t[2]) -- será lento porque é a primeira vez usando esse número.
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 que o metatabel da tabela também tem um __newindex metamétodo em ele? Você vai querer usar a função de código
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10 -- somente como um exemplo
return self[i]
end,
__newindex = function(self, i, v)
--não faça nada porque não queremos que você coloque valores na tabela da maneira normal
end
})
print(t[1]) -- Causes a C-Stack overflow
Agora, por que isso causaria um excesso de estacas? Estouros de estacas acontecem quando você tenta chamar uma função de si mesmo muitas vezes, mas o que causaria isso acontecer? Na função __index, definimos self[i] para um valor, então quando chegar à próxima linha, self[i)]</
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ê coloque valores na tabela da maneira normal
end
})
print(t[1]) -- prints 10
Usando o Set Datatype
Um set é uma coleção de itens sem ordem e sem elementos duplicados. Um item é ou não contido dentro de um configurar. Usando metatabelas, você pode construir e manipular setes dentro de scripts Lua.
Métodos Básicos
O seguinte código inclui funcionalidades básicas de set, permitindo que você construa novos sets, adicione e remova um item, verifique se um set contém um item e saia 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 exportar set como uma lista com espaços para debug
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"})
Nota que, por definição, um conjunto não tem conceito de ordem.
Adicionar Item
Adicionar um item a um conjunto existente pode ser feito via o método Set:add() .
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")
Verificar por 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) -- verdadelocal 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, combina conjuntos ou subtrai um conjunto de outro.
Interseção
Ao considerar conjuntos como Venn diagrams, você pode obter a interseção de dois conjuntos da seguinte forma, o que significa que os itens que aparecem em ambos 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, o que significa uma coleção dos itens em ambos os conjuntos sem duplicados. Nota que esta função usa o método __add para fornecer uma aba de adição 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
Subtraição
Você pode remover todos os itens em um conjunto de itens em outro conjunto via a função a seguir. Semelhante à função acima, isso usa o método metitable __sub para fornecer uma subtraição rápida 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