Metatabeli umożliwiają stworzenie tabeli bardziej potężnych niż wcześniej. Zostały one załączone do danych i zawierają wartości zwane metametodami. Metametody są uruchamiane, gdy pewna akcja jest wykonana z danej, do której jest załączony.
Zastanów się nad następującym kodem:
local list = {1, 2}print(list[3])
Możesz się spodziewać, że ten kod szuka w liście dla trzeciego wskaźnika w liście, znajduje nic i wypisuje nil. To nie jest poprawne. Co się jednak dzieje, to kod szuka w liście dla trzeciego wskaźnika, znajduje nic i wypisuje nil, jeśli nie ma metabeli załączonego z tabelą, wypisuje nil.
Manipulowanie metatabelami
Dwa główne funkcje do dodania i znalezienia metabeli tabeli to setmetatable i getmetatable
local x = {}local metaTable = {} -- metaTables to także tabela!setmetatable(x, metaTable) -- Daj x metabeli zwany metaTablem!print(getmetatable(x)) --> table: [hexadecimal memory address]
Funkcja setmetatable również zwraca tabelę, do której ustawiasz metabelę, więc te dwa skrypty robią to samo:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Metody
Metamety są funkcjami, które są przechowywane w metabelce. Mogą one iść od wzywania tabeli, poprzez dodawanie tabeli, a nawet dzielenie tabel, aby uzyskać listę dostępnych metod metametody. Oto lista dostępnych metametod:
Mето | Opis |
---|---|
__index(tabela, indeks) | Występuje, gdy tabela[index] jest indeksowana, jeśli tabela[index] jest nil. Można również ustawić na tabelę, w której tabela[index] będzie indeksowana. |
__nowindex(tabela, indeks, wartość) | Występuje, gdy tabela [index] próbuje być ustawiona (tabela [index] = wartość), jeśli tabela [index] jest nieważna. Można również ustawić tabelę, w której tabela [index] będzie indeksowana. |
__call(tabela, ...) | Występuje, gdy tabela jest nazywana jak funkcja, ... są argumenty, które zostały przekazane. |
__concat(tabela, wartość) | Występuje, gdy operator .. łączenia jest używany na tabeli. |
__unm(tabela) | Wystąpi, gdy unary - operator jest używany na tabeli. |
__add(tabela, wartość) | Operator +. |
__sub(tabela, wartość) | The - operator odejmowania. |
__mul(tabela, wartość) | Operator * multiplikacji. |
__div(tabela, wartość) | Operator / dzielenia. |
__idiv(tabela, wartość) | operator podziału // na poziomie |
__mod(tabela, wartość) | Operator modułu % |
__pow(tabela, wartość) | ^ operator ekspresji. |
__tostring(tabela) | Wystrzelony, gdy w tabeli jest wezwany bystring. |
__metabela | Jeśli obecny, zablokowuje metabelę, aby getmetabela powróciła zamiast metabeli i setmetabeli będzie błędem. Nie jest wartością funkcji. |
__eq(tabela, wartość) | The == równa operatorowi¹ |
__lt(tabela, wartość) | The < mniej niż operator¹ |
__le(tabela, wartość) | Operator gelegentowy |
__tryb | Używany w słabych tabelach, deklarując, czy klucze i/lub wartości tabeli są słabe. Uwaga: Odniesienie się do instancji Roblox nigdy nie jest słabe. Tabela, która zawiera takie odniesienie, nigdy nie będzie zbierała śmieci. |
__gc(tabela) | Wystrzelony, gdy tabela jest gromadzona. Uwaga: Na Roblox metoda metametoda jest wyłączona. |
__len(tabela) | Wystrzelony, gdy operator długości # jest używany na Objekcie. |
__iter(tabela) | Używany do określenia niestandardowego inicjatora podczas używania ogólnego inicjatora. |
Należy zauważyć, że podczas pisania funkcji dla arytmetycznych lub relacyjnych metod metametody dwie zmienne funkcji są wymienialne między tabelą, która wykonała metodę metametody, a inną wartością. Na przykład, gdy wykonujesz operacje wektorowe z dzieleniem skalarów nie jest komunikatywny. Dlatego jeśli piszesz metametody dla własnej klasy metametody2, należy uważa
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a jest skalarem, b jest wektorem
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a jest wektorem, b jest skalarem
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
-- oba a i b są wektorem
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
Używanie metatabel
Istnieje wiele sposobów na użycie metatabeli, na przykład metametody __unm (aby uczynić tabelę ujemną):
local metatable = {
__unm = function(t) -- __unm jest dla unary - operator
local negated = {}
for key, value in t do
negated[key] = -value -- zanuluj wszystkie wartości w tabeli
end
return negated -- zwróć tabelę
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
Oto ciekawy sposób na zadeklarowanie rzeczy używając __index :
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
__index zakończył się po tym, jak x został zaindeksowany w tabeli i nie znaleziony. Lua wtedy przeszukał tabelę __index dla indeksu nazyjącego się x i znaleziono jeden, a następnie zwrócił to.
Teraz możesz łatwo zrobić to za pomocą prostej funkcji, ale jest jeszcze wiele innych, skąd to pochodzi. Weź to na przykład:
local t = {10, 20, 30}print(t(5))
Oczywiście nie możesz nazwać tabeli. To szaleństwo, ale (niespodzianka, niespodzianka!) z metatabelami możesz.
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Dodaj argument (5) do wartości, a następnie umieść go w nowej tabeli (t).
end
return unpack(sum) -- Wróć indywidualne wartości tabeli
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Możesz zrobić o wiele więcej, takich jak dodawanie tabel!
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
To będzie błąd mówiąc, że próbujesz wykonania arytmetyki na tabeli. Spróbuj to z metitablem.
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
Użyj przypadków
Teraz wszystkie te przykłady można zaimplementować jako prostą funkcję, ale możesz zrobić o wiele więcej niż to. Spróbujmy prosty program, który zapamięta numer, gdy możliwy problem matematyczny zostanie w niego wpisany.
Dla tego użyjemy metody __index metametody, aby ją uproszczyć:
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]) -- Będzie powolny, ponieważ jest to pierwszy raz używania tego numeru, więc musi ona wykonać funkcję matematyczną.
print(t[2]) -- będzie powolny, ponieważ jest to pierwszy raz używania tego numeru.
print(t[1]) -- will be fast because it's just grabbing the number from the table.
Rawset, Rawget, Rawequal
Gdy grać z metatables, możesz napotkać kilka problemów. Co się stanie, jeśli musisz użyć metod metatables, aby stworzyć nowe wartości w tabeli, ale że metatabel tabeli ma również metodę __index w niej? Będziesz chciał użyć funkcji metatables wbudowanej w Lua
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10 -- tylko jako przykład
return self[i]
end,
__newindex = function(self, i, v)
--nie robić nic, ponieważ nie chcemy, aby ustawić wartości na tabeli w sposób normalny
end
})
print(t[1]) -- Causes a C-Stack overflow
Teraz dlaczego to powoduje przelicznik stosunki między liniami? Przeliczniki stosunku między liniami zdarzają się, gdy próbujesz wejść na funkcję zbyt wiele razy, ale co powoduje, że to się stanie? W funkcji __index ustawiliśmy self[i] na wartość,
Problem polega na tym, że __newindex nie pozwala nam ustawić wartości. Jego obecność powoduje, że wartości są dodawane do tabeli za pomocą standardowego metody t[i] = v. Aby przezwyciężyć to, używasz funkcji rawset.
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
--nie robić nic, ponieważ nie chcemy, aby ustawić wartości na tabeli w sposób normalny
end
})
print(t[1]) -- prints 10
Używanie ustawienia daty
A zestaw to zbiór przedmiotów bez porządku i bez duplikatów. Przedmiot jest albo jest lub nie jest zawarty w ustawiać. Używając metatabeli, możesz zbudować i manipulować zestawami w Lua skryptach.
Podstawowe metody
Poniższy kod zawiera podstawowe funkcje zestawów, umożliwiając budowanie nowych zestawów, dodawanie i usuwanie przedmiot, sprawdzenie, czy zestaw zawiera przedmioti wyświetlenie zawartości ustawiać.
local Set = {}
Set.__index = Set
-- Funkcja do budowania zestawu z opcjonalnej listy przedmiotów
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Funkcja dodania pozycji do ustawiać
function Set:add(item)
self[item] = true
end
-- Funkcja usuwania pozycji z ustawiać
function Set:remove(item)
self[item] = nil
end
-- Funkcja sprawdzenia, czy zestaw zawiera przedmiot
function Set:contains(item)
return self[item] == true
end
-- Funkcja do wygenerowania listy jako kompletu znaków dla debugingu
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
Utwórz zestaw
Nowy zestaw można zbudować, nazyając Set.new() z opcjonalną listą elementów do dodawać.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
Uwaga, że z definicji, zestaw nie ma pojęcia o kolejności.
Dodaj przedmiot
Dodanie przedmiotu do istniejącego zestawu można zrobić za pośrednictwem metody Set:add() .
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:add("Mango")
Usuń Przedmiot
Aby usunąć pozycję z ustawiać, wezwij Set:remove() z imieniem pozycji.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:remove("Orange")
Sprawdź przedmiot
Aby sprawdzić, czy zestaw zawiera określony przedmiot, użyj Set:contains() .
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})local result1 = fruits:contains("Cherry")print(result1) -- prawdziwylocal result2 = fruits:contains("Watermelon")print(result2) -- false
Dodatkowe metody
Inne użyteczne operacje można zaimplementować dla zestawów, umożliwiając porównanie pozycji między zestawami, łączenie zestawów lub odejmowanie jednego zestawu z innym.
Przyłącz
Podczas rozważania zestawów jako diagrama Venn, można uzyskać intersekcję dwóch zestawów, jak następuje, co oznacza przedmioty, które pojawiają się w obu zestawach.
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
Unia
Możesz uzyskać union dwóch zestawów za pomocą następującej funkcji, co oznacza, że kolekcja przedmiotów w obu zestawach bez duplikatów. Uwaga, że ta funkcja używa metody __add , aby zapewnić dodatkowy skrót 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() -- Śliwka, Lima, Jabłko, Wiśnia, Cytryna, Mango
Odejście
Możesz usunąć wszystkie pozycje w jednym zestawie z pozycji w innym zestawie poprzez następującą funkcję. Podobnie jak funkcja powyżej, używa metody metabeli __sub , aby zapewnić skrót odejścia 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