Metatabele

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

Metatabele pozwalają tabelom stać się bardziej potężnymi niż wcześniej. Są one przypinane do danych i zawierają wartości zwane metametodami. Metametody są wywoływane, gdy używana jest określona akcja z danymi, do których są przypisane.

Manipulacja metatabelami

Dwie główne funkcje do dodawania i znajdowania metatabeli tabeli to setmetatable() i getmetatable().


local x = {}
local metaTable = {} -- Metatabele też są tabelami!
setmetatable(x, metaTable) -- Przypisz x metatabelę o nazwie metaTable!
print(getmetatable(x)) --> tabela: [adres pamięci w systemie szesnastkowym]

Funkcja setmetatable() zwraca także tabelę, której ustawiasz metatabelę, więc te dwa skrypty wykonują to samo:


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

local x = setmetatable({}, {})

Metametody

Metametody to funkcje, które są przechowywane w ramach metatabeli. Mogą one wywoływać tabelę, dodawać tabelę, a nawet dzielić tabele. Oto lista dostępnych metametod:

MetodaOpis
__index(table, index)Wywoływana kiedy 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)Operatorek reszty z dzielenia %.
__pow(table, value)Operator potęgowania ^.
__tostring(table)Wywoływana, gdy tostring jest wywoływane na tabeli.
__metatableJeśli obecny, blokuje metatabelę, więc getmetatable() zwróci ją zamiast metatabeli i 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 <=¹
__modeUżywane w słabych tabelach, określając, czy klucze i/lub wartości tabeli są słabe. Zauważ, że odniesienia do instancji Roblox nigdy nie są słabe. Tabele, które przechowują takie odniesienia nigdy nie będą zbierane przez garbage collector.
__len(table)Wywoływana, gdy operator długości # jest używany na obiekcie.
__iter(table)Używane do oznaczenia niestandardowego iteratora przy użyciu uogólnionej iteracji.

Należy zauważyć, że przy pisaniu funkcji dla zarówno metametod arytmetycznych, jak i relacyjnych, dwa parametry funkcji są zamienne między tabelą, która wywołała metametod, a inną wartością. Na przykład, przy operacjach wektorowych ze skalarem dzielenie nie jest przemienne. W związku z tym, jeśli pisałbyś metametody dla swojej własnej klasy vector2, powinieneś 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 to skalar, b to wektor
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a to wektor, b to skalar
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 to wektory
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

Użycie

Jest wiele sposobów na użycie metatabel, na przykład metametoda __unm do uczynienia tabeli ujemną:


local metatable = {
__unm = function(t) -- __unm jest dla operatora unarnego -
local negated = {}
for key, value in t do
negated[key] = -value -- neguj 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; -12

Oto ciekawy sposób zadeklarowania rzeczy przy użyciu __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ło tabelę __index w poszukiwaniu indeksu o nazwie x, i znajdzie jedną, zwróciło ją.

Teraz możesz to łatwo zrobić za pomocą prostej funkcji, ale jest o wiele więcej, co można zrobić. Weź na przykład to:


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

Zazwyczaj nie możesz wywołać tabeli, ale dzięki metatabelom 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 35

Możesz zrobić o wiele 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)
end

To zgłosi błąd, twierdząc, że próbujesz wykonać arytmetykę na tabeli, ale działa, kiedy próbujesz to 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)
end

Gdy bawisz się metatabelami, możesz napotkać pewne problemy. Jeśli musisz użyć metametody __index, aby utworzyć nowe wartości w tabeli, ale metatabela tej tabeli również ma metametodę __newindex, powinieneś użyć wbudowanej funkcji Luau rawset(), aby ustawić wartość bez wywoływania metametod. Oto przykład, 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 stosu

Przepełnienie stosu występuje, gdy próbujesz wywołać funkcję zbyt wiele razy. W funkcji __index powyżej self[i] jest ustawiane na wartość, więc gdy przejdzie do następnej linii, self[i] powinno istnieć i przypuszczalnie nie wywoła metametody __index. Problemem jest to, że __newindex nie pozwala na ustawienie wartości. Jego obecność uniemożliwia dodawanie wartości do tabeli według 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]) --> 10

Użyj typu zbioru

Zbiór to kolekcja przedmiotów bez kolejności i bez duplikatów. Przedmiot jest lub nie jest zawarty w zbiorze. Korzystając z metatabel, możesz tworzyć i manipulować zbiorami w skryptach Luau.

Podstawowe metody

Poniższy kod zawiera podstawową funkcjonalność zbioru, pozwalającą na konstrukcję nowych zbiorów, dodawanie i usuwanie przedmiotu, sprawdzanie, czy zbiór zawiera przedmiot, oraz wyświetlanie zawartości zbioru.


local Set = {}
Set.__index = Set
-- Funkcja do konstruowania zbioru 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 do dodania przedmiotu do zbioru
function Set:add(item)
self[item] = true
end
-- Funkcja do usunięcia przedmiotu ze zbioru
function Set:remove(item)
self[item] = nil
end
-- Funkcja do sprawdzenia, czy zbiór zawiera przedmiot
function Set:contains(item)
return self[item] == true
end
-- Funkcja do wyjścia 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, ", "))
end

Utwórz zbiór

Nowy zbiór można skonstruować, wywołując Set.new() z opcjonalną tablicą przedmiotów do dodania.


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

Zauważ, że z definicji zbiór nie ma pojęcia kolejności.

Dodaj przedmiot

Dodanie przedmiotu do istniejącego zbioru można zrobić za pomocą metody Set:add().


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

Usuń przedmiot

Aby usunąć przedmiot ze zbioru, wywołaj Set:remove() z nazwą przedmiotu.


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

Sprawdź przedmiot

Aby sprawdzić, czy zbiór 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) -- true
local result2 = fruits:contains("Watermelon")
print(result2) -- false

Dodatkowe metody

Inne przydatne operacje można zaimplementować dla zbiorów, pozwalając na porównywanie przedmiotó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 przedmioty, które występują 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, Peach

Zbiór

Możesz uzyskać zbiór dwóch zbiorów za pomocą następującej funkcji, co oznacza kolekcję przedmiotów w obu zbiorach bez duplikatów. Zauważ, że ta funkcja używa metatable __add do zapewnienia skrótu 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, Mango

Odejmowanie

Możesz usunąć wszystkie przedmioty w jednym zbiorze z przedmiotów w drugim zbiorze za pomocą następującej funkcji. Podobnie jak w przypadku powyższej funkcji, używa ona metatable __sub do zapewnienia skrótu 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
©2026 Roblox Corporation. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.