Metatabele pozwalają tabelom stać się bardziej potężnymi niż kiedykolwiek wcześniej. Są one przypisane do danych i zawierają wartości zwane metodami metaprogramowania. Metody metaprogramowania są wywoływane, gdy określona akcja jest używana z danymi, do których są przypisane.
Manipulacja metatabelami
Dwie podstawowe funkcje do dodawania i znajdowania metatabeli tabeli to setmetatable() i getmetatable().
local x = {}
local metaTable = {} -- Metatabele to również tabele!
setmetatable(x, metaTable) -- Przypisz x metatabelę o nazwie metaTable!
print(getmetatable(x)) --> table: [adres pamięci w formacie szesnastkowym]Funkcja setmetatable() zwraca również tabelę, której ustawiasz metatabelę, więc te dwa skrypty robią to samo:
local x = {}
setmetatable(x, {})local x = setmetatable({}, {})Metody metaprogramowania
Metody metaprogramowania to funkcje, które są przechowywane wewnątrz metatabeli. Mogą one obejmować wywoływanie tabeli, dodawanie tabeli, a nawet dzielenie tabel. Oto lista dostępnych metod metaprogramowania:
| Metoda | Opis |
|---|---|
| __index(table, index) | Wywoływana, gdy table[index] jest indeksowane, jeśli table[index] jest nil. Może być również ustawiona na tabelę, w takim przypadku ta tabela będzie indeksowana. |
| __newindex(table, index, value) | Wywoływana, gdy table[index] próbuje być ustawione (table[index] = value), jeśli table[index] jest nil. Może być również ustawiona na tabelę, w takim przypadku ta tabela będzie indeksowana. |
| __call(table, ...) | Wywoływana, gdy tabela jest wywoływana jak funkcja, ... to argumenty, które zostały przekazane. |
| __concat(table, value) | Wywoływana, gdy operator konkatenacji .. jest używany na tabeli. |
| __unm(table) | Wywoływana, gdy operator unarny – jest używany na tabeli. |
| __add(table, value) | Operator dodawania +. |
| __sub(table, value) | Operator odejmowania –. |
| __mul(table, value) | Operator mnożenia *. |
| __div(table, value) | Operator dzielenia /. |
| __idiv(table, value) | Operator dzielenia całkowitego //. |
| __mod(table, value) | Operator reszty z dzielenia %. |
| __pow(table, value) | Operator potęgowania ^. |
| __tostring(table) | Wywoływana, gdy tostring jest wywoływane na tabeli. |
| __metatable | Jeśli jest obecna, blokuje metatabelę, więc getmetatable() zwróci to zamiast metatabeli, a setmetatable() zgłosi błąd. Wartość niebędąca funkcją. |
| __eq(table, value) | Operator równości ==¹ |
| __lt(table, value) | Operator mniejszości <¹ |
| __le(table, value) | Operator mniejszości lub równości <=¹ |
| __mode | Używane w słabych tabelach, deklarując, czy klucze i/lub wartości tabeli są słabe. Zobacz Użyj słabych tabel. |
| __len(table) | Wywoływana, gdy operator długości # jest używany na obiekcie. |
| __iter(table) | Używane do oznaczenia niestandardowego iteratora podczas używania uogólnionej iteracji. |
Należy zauważyć, że podczas pisania funkcji dla metod metaprogramowania arytmetycznych lub relacyjnych dwa parametry funkcji są zamienne między tabelą, która wywołała metodę metaprogramowania, a inną wartością. Na przykład, podczas wykonywania operacji wektorowych z skalarnymi, dzielenie nie jest przemienne. Dlatego, jeśli pisałeś metody metaprogramowania dla swojej własnej klasy vector2, musisz być ostrożny, aby uwzględnić oba scenariusze.
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
-- zarówno a, jak i b są wektorami
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.5Użycie
Istnieje wiele sposobów używania metatabel.
Zastosuj operator minus unarny do tabel
Metoda metaprogramowania __unm obsługuje operator minus unarny. Użyj jej, aby zanegować każdą wartość w tabeli:
local metatable = {
__unm = function(t) -- __unm jest dla operatora unarnego -
local negated = {}
for key, value in t do
negated[key] = -value -- zaneguj wszystkie wartości w tej tabeli
end
return negated -- zwróć tabelę
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12Indeksuj brakujące wartości z innej tabeli
Oto interesujący sposób deklarowania rzeczy za pomocą __index:
local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1__index został wywołany, gdy x był indeksowany w tabeli i nie został znaleziony. Luau następnie przeszukał tabelę __index w poszukiwaniu indeksu o nazwie x, a po znalezieniu go, zwrócił ten indeks.
Wywołaj tabelę jak funkcję
Metatabele mogą robić znacznie więcej niż dostarczać brakującą wartość. Weź na przykład to:
local t = {10, 20, 30}
print(t(5))Zazwyczaj nie możesz wywołać tabeli, ale 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) -- Zwróć poszczególne wartości tabeli
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35Zastosuj operator dodawania do tabel
Możesz zrobić znacznie więcej, na przykład dodawać tabele:
local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
endTo spowoduje błąd, mówiąc, że próbujesz wykonać arytmetykę na tabeli, ale działa, gdy próbujesz z metatabelą:
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)
endUstaw wartości bez wywoływania metod metaprogramowania
Podczas zabawy z metatabelami możesz napotkać pewne problemy. Jeśli musisz użyć metody metaprogramowania __index, aby utworzyć nowe wartości w tabeli, ale metatabela tej tabeli ma również metodę __newindex, będziesz chciał użyć wbudowanej funkcji Luau rawset(), aby ustawić wartość bez wywoływania jakichkolwiek metod metaprogramowania. Weź poniższy kod jako przykład tego, co się dzieje, jeśli nie użyjesz tych funkcji.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Nie ustawiaj wartości w tabeli w normalny sposób
end
})
print(t[1]) -- Powoduje przepełnienie stosuPrzepełnienia stosu występują, gdy próbujesz wywołać funkcję zbyt wiele razy. W funkcji __index powyżej self[i] jest ustawiane na wartość, więc gdy przechodzi do następnej linii, self[i] powinno istnieć i przypuszczalnie nie wywoła metody __index. Problem polega na tym, że __newindex nie pozwala na ustawienie wartości. Jej obecność uniemożliwia dodawanie wartości do tabeli za pomocą standardowej metody t[i] = v. Aby to obejść, użyj funkcji rawset():
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Nie ustawiaj wartości w tabeli w normalny sposób
end
})
print(t[1]) --> 10Użyj słabych tabel
Zmienne, które przechowują tabele, funkcje, wątki i userdata, w tym Instances, przechowują odniesienia do tych obiektów zamiast ich kopii. Przypisanie obiektu do innej zmiennej tworzy inne odniesienie do tego samego obiektu:
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) --> trueObiekty, które nie są już osiągalne przez silne odniesienia, stają się kwalifikowane do zbierania przez garbage collector. Ustawienie jednej zmiennej na nil nie czyni obiektu kwalifikowanym do zbierania przez garbage collector, jeśli inne silne odniesienie nadal wskazuje na ten obiekt:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [adres pamięci w formacie szesnastkowym]Chociaż child nie odnosi się już do tabeli dziecka, parent.Child nadal to robi. Tak długo, jak tabela rodzica pozostaje osiągalna, jej odniesienie utrzymuje tabelę dziecka osiągalną.
Słaba tabela przechowuje klucze, wartości lub oba bez zapobiegania zbieraniu przez garbage collector w przeciwnym razie nieosiągalnych obiektów. Ustaw pole __mode metatabeli tabeli, aby kontrolować, które odniesienia są słabe:
- "k" czyni klucze słabymi.
- "v" czyni wartości słabymi.
- "kv" lub "vk" czyni zarówno klucze, jak i wartości słabymi.
Poniższy przykład kodu tworzy tabelę z słabymi wartościami:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
endPo zakończeniu bloku do wartość przechowywana w children.example nie ma silnych odniesień i staje się kwalifikowana do zbierania przez garbage collector. Gdy garbage collector zbiera tabelę dziecka, Luau usuwa jej wpis z children. Zbieranie przez garbage collector jest niedeterministyczne, więc nie polegaj na tym, kiedy wpis zniknie.
Każdy tryb, który pominiesz, pozostaje silny. Klucze w tabeli __mode = "v" pozostają silnymi odniesieniami, więc obiekt przechowywany jako klucz pozostaje osiągalny, a jego wpis pozostaje w tabeli.
Dla Instances zbieranie przez garbage collector dotyczy odniesienia Luau, a nie samego obiektu. Silnik przechowuje dane instancji poza VM Luau, więc zbieranie odniesienia Luau nie usuwa instancji, a instancja, która pozostaje w modelu danych, nie utrzymuje swojego odniesienia Luau przy życiu. Aby zwolnić instancję, wywołaj Instance:Destroy() i wyczyść wszystkie pozostałe odniesienia do niej.
Użyj typu zbioru
Zbiór to kolekcja elementów bez porządku i bez duplikatów. Element albo jest, albo nie jest zawarty w zbiorze. Używając metatabel, możesz konstruować i manipulować zbiorami w skryptach Luau.
Podstawowe metody
Poniższy kod zawiera podstawową funkcjonalność zbioru, pozwalającą na konstruowanie nowych zbiorów, dodawanie i usuwanie elementów, sprawdzanie, czy zbiór zawiera element, oraz wyświetlanie zawartości zbioru.
local Set = {}
Set.__index = Set
-- Funkcja do konstruowania zbioru z opcjonalnej listy elementów
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Funkcja do dodawania elementu do zbioru
function Set:add(item)
self[item] = true
end
-- Funkcja do usuwania elementu ze zbioru
function Set:remove(item)
self[item] = nil
end
-- Funkcja do sprawdzania, czy zbiór zawiera element
function Set:contains(item)
return self[item] == true
end
-- Funkcja do wyświetlania zbioru jako listy oddzielonej przecinkami do debugowania
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
endUtwórz zbiór
Nowy zbiór można skonstruować, wywołując Set.new() z opcjonalną tablicą elementów do dodania.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})Należy pamiętać, że z definicji zbiór nie ma pojęcia porządku.
Dodaj element
Dodawanie elementu do istniejącego zbioru można wykonać za pomocą metody Set:add().
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")Usuń element
Aby usunąć element ze zbioru, wywołaj Set:remove() z nazwą elementu.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")Sprawdź obecność elementu
Aby sprawdzić, czy zbiór zawiera konkretny element, użyj 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) -- falseDodatkowe metody
Inne przydatne operacje można zaimplementować dla zbiorów, pozwalając na porównywanie elementów między zbiorami, łączenie zbiorów lub odejmowanie jednego zbioru od drugiego.
Przecięcie
Rozważając zbiory jako diagramy Venna, możesz uzyskać przecięcie dwóch zbiorów w następujący sposób, co oznacza elementy, które pojawiają się w obu zbiorach.
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, PeachSuma
Możesz uzyskać sumę dwóch zbiorów za pomocą następującej funkcji, co oznacza kolekcję elementów w obu zbiorach bez duplikatów. Zauważ, że ta funkcja używa metody metatabeli __add, aby zapewnić skrót dodawania 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, MangoOdejmowanie
Możesz usunąć wszystkie elementy w jednym zbiorze z elementów w innym zbiorze za pomocą następującej funkcji. Podobnie jak w powyższej funkcji, ta używa metody metatabeli __sub, aby zapewnić skrót odejmowania 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