Metatabellen ermöglichen es Tabellen, leistungsfähiger als zuvor zu werden. Sie sind an Daten angehängt und enthalten Werte, die als Metamethoden bezeichnet werden. Metamethoden werden ausgelöst, wenn eine bestimmte Aktion mit dem Datum verwendet wird, an das sie angehängt sind.
Metatabellen manipulieren
Die beiden Hauptfunktionen zum Hinzufügen und Finden einer Metatabelle einer Tabelle sind setmetatable() und getmetatable().
local x = {}
local metaTable = {} -- Metatabellen sind auch Tabellen!
setmetatable(x, metaTable) -- Gib x eine Metatabelle namens metaTable!
print(getmetatable(x)) --> table: [hexadezimale Speicheradresse]Die Funktion setmetatable() gibt auch die Tabelle zurück, deren Metatabelle du festlegst, sodass diese beiden Skripte dasselbe tun:
local x = {}
setmetatable(x, {})local x = setmetatable({}, {})Metamethoden
Metamethoden sind die Funktionen, die in einer Metatabelle gespeichert sind. Sie können von der Aufrufung einer Tabelle über die Addition einer Tabelle bis hin zur Division von Tabellen reichen. Hier ist die Liste der verfügbaren Metamethoden:
| Methode | Beschreibung |
|---|---|
| __index(table, index) | Wird ausgelöst, wenn table[index] indiziert wird, wenn table[index] nil ist. Kann auch auf eine Tabelle gesetzt werden, in diesem Fall wird diese Tabelle indiziert. |
| __newindex(table, index, value) | Wird ausgelöst, wenn table[index] versucht wird, gesetzt zu werden (table[index] = value), wenn table[index] nil ist. Kann auch auf eine Tabelle gesetzt werden, in diesem Fall wird diese Tabelle indiziert. |
| __call(table, ...) | Wird ausgelöst, wenn die Tabelle wie eine Funktion aufgerufen wird, ... sind die übergebenen Argumente. |
| __concat(table, value) | Wird ausgelöst, wenn der Verkettungsoperator .. auf die Tabelle angewendet wird. |
| __unm(table) | Wird ausgelöst, wenn der unäre – Operator auf die Tabelle angewendet wird. |
| __add(table, value) | Der + Additionsoperator. |
| __sub(table, value) | Der – Subtraktionsoperator. |
| __mul(table, value) | Der * Multiplikationsoperator. |
| __div(table, value) | Der / Divisionsoperator. |
| __idiv(table, value) | Der // Ganzzahldivisionsoperator. |
| __mod(table, value) | Der % Modulooperator. |
| __pow(table, value) | Der ^ Exponentialoperator. |
| __tostring(table) | Wird ausgelöst, wenn tostring auf die Tabelle aufgerufen wird. |
| __metatable | Wenn vorhanden, sperrt die Metatabelle, sodass getmetatable() diese zurückgibt, anstatt die Metatabelle, und setmetatable() einen Fehler ausgibt. Kein Funktionswert. |
| __eq(table, value) | Der == Gleichheitsoperator¹ |
| __lt(table, value) | Der < Kleiner-als-Operator¹ |
| __le(table, value) | Der <= Operator¹ |
| __mode | Wird in schwachen Tabellen verwendet, um zu erklären, ob die Schlüssel und/oder Werte einer Tabelle schwach sind. Siehe Schwache Tabellen verwenden. |
| __len(table) | Wird ausgelöst, wenn der # Längenoperator auf das Objekt angewendet wird. |
| __iter(table) | Wird verwendet, um einen benutzerdefinierten Iterator bei der Verwendung von verallgemeinerter Iteration zu kennzeichnen. |
Es sollte beachtet werden, dass beim Schreiben von Funktionen für entweder arithmetische oder relationale Metamethoden die beiden Funktionsparameter austauschbar sind zwischen der Tabelle, die die Metamethode ausgelöst hat, und dem anderen Wert. Zum Beispiel ist die Division bei Vektoroperationen mit Skalaren nicht kommutativ. Daher solltest du beim Schreiben von Metamethoden für deine eigene vector2-Klasse darauf achten, beide Szenarien zu berücksichtigen.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a ist ein Skalar, b ist ein Vektor
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a ist ein Vektor, b ist ein 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
-- sowohl a als auch b sind Vektoren
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.5Verwendung
Es gibt viele Möglichkeiten, Metatabellen zu verwenden.
Wende den unären Minusoperator auf Tabellen an
Die Metamethode __unm behandelt den unären Minusoperator. Verwende ihn, um jeden Wert in einer Tabelle zu negieren:
local metatable = {
__unm = function(t) -- __unm ist für den unären - Operator
local negated = {}
for key, value in t do
negated[key] = -value -- negiere alle Werte in dieser Tabelle
end
return negated -- gebe die Tabelle zurück
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12Indiziere fehlende Werte aus einer anderen Tabelle
Hier ist eine interessante Möglichkeit, Dinge mit __index zu deklarieren:
local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1__index wurde ausgelöst, als x in der Tabelle indiziert wurde und nicht gefunden wurde. Luau suchte dann in der __index-Tabelle nach einem Index namens x und gab, nachdem er einen gefunden hatte, diesen zurück.
Rufe eine Tabelle wie eine Funktion auf
Metatabellen können viel mehr tun, als einen fehlenden Wert bereitzustellen. Nimm zum Beispiel dies:
local t = {10, 20, 30}
print(t(5))Typischerweise kannst du eine Tabelle nicht aufrufen, aber mit Metatabellen kannst du:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Füge das Argument (5) zum Wert hinzu und platziere es dann in der neuen Tabelle (t).
end
return unpack(sum) -- Gebe die einzelnen Tabellenwerte zurück
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35Wende den Additionsoperator auf Tabellen an
Du kannst auch viel mehr tun, wie Tabellen addieren:
local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
endDies wird einen Fehler ausgeben, dass du versuchst, arithmetische Operationen an einer Tabelle durchzuführen, aber es funktioniert, wenn du es mit einer Metatabelle versuchst:
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)
endSetze Werte, ohne Metamethoden aufzurufen
Wenn du mit Metatabellen spielst, kannst du auf einige Probleme stoßen. Wenn du die Metamethode __index verwenden musst, um neue Werte in einer Tabelle zu erstellen, aber die Metatabelle dieser Tabelle auch eine Metamethode __newindex hat, möchtest du die eingebaute Luau-Funktion rawset() verwenden, um den Wert zu setzen, ohne Metamethoden aufzurufen. Nimm den folgenden Code als Beispiel dafür, was passiert, wenn du diese Funktionen nicht verwendest.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Setze keine Werte auf die normale Weise in die Tabelle
end
})
print(t[1]) -- Verursacht einen StacküberlaufStacküberläufe treten auf, wenn du versuchst, eine Funktion zu oft von sich selbst aufzurufen. In der obigen __index-Funktion wird self[i] auf einen Wert gesetzt, sodass, wenn es zur nächsten Zeile kommt, self[i] existieren sollte und vermutlich die Metamethode __index nicht aufruft. Das Problem ist, dass __newindex es dir nicht erlaubt, den Wert zu setzen. Ihre Anwesenheit verhindert, dass Werte mit der Standardmethode t[i] = v zur Tabelle hinzugefügt werden. Um dies zu umgehen, verwende die Funktion rawset():
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Setze keine Werte auf die normale Weise in die Tabelle
end
})
print(t[1]) --> 10Verwende schwache Tabellen
Variablen, die Tabellen, Funktionen, Threads und Benutzerdaten halten, einschließlich Instances, speichern Referenzen auf diese Objekte anstelle von Kopien davon. Das Zuweisen eines Objekts an eine andere Variable erstellt eine weitere Referenz auf dasselbe Objekt:
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) --> trueObjekte, die nicht mehr über starke Referenzen erreichbar sind, werden für die Garbage Collection geeignet. Das Setzen einer Variablen auf nil macht ein Objekt nicht für die Garbage Collection geeignet, wenn eine andere starke Referenz weiterhin auf dieses Objekt zeigt:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [hexadezimale Speicheradresse]Obwohl child nicht mehr auf die Kindtabelle verweist, tut es parent.Child weiterhin. Solange die Elterntabelle erreichbar bleibt, bleibt ihre Referenz auch die Kindtabelle erreichbar.
Eine schwache Tabelle hält Schlüssel, Werte oder beides, ohne den Garbage Collector daran zu hindern, ansonsten unerreichbare Objekte zurückzugewinnen. Setze das Feld __mode der Metatabelle der Tabelle, um zu steuern, welche Referenzen schwach sind:
- "k" macht die Schlüssel schwach.
- "v" macht die Werte schwach.
- "kv" oder "vk" macht sowohl die Schlüssel als auch die Werte schwach.
Der folgende Code erstellt eine Tabelle mit schwachen Werten:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
endNachdem der do-Block endet, hat der Wert, der in children.example gespeichert ist, keine starken Referenzen mehr und wird für die Garbage Collection geeignet. Wenn der Garbage Collector die Kindtabelle zurückgewinnt, entfernt Luau ihren Eintrag aus children. Die Garbage Collection ist nicht deterministisch, also verlasse dich nicht darauf, wann der Eintrag verschwindet.
Jeder Modus, den du weglässt, bleibt stark. Schlüssel in einer __mode = "v" Tabelle bleiben starke Referenzen, sodass ein Objekt, das als Schlüssel gespeichert ist, erreichbar bleibt und sein Eintrag in der Tabelle bleibt.
Für Instances gilt, dass die Garbage Collection auf die Luau-Referenz und nicht auf das Objekt selbst angewendet wird. Die Engine speichert Instanzdaten außerhalb der Luau-VM, sodass das Sammeln einer Luau-Referenz die Instanz nicht löscht, und eine Instanz, die im Datenmodell bleibt, hält ihre Luau-Referenz nicht am Leben. Um eine Instanz freizugeben, rufe Instance:Destroy() auf und lösche jede verbleibende Referenz darauf.
Verwende den Datentyp Set
Ein Set ist eine Sammlung von Elementen ohne Ordnung und ohne doppelte Elemente. Ein Element ist oder ist nicht in einem Set enthalten. Mit Metatabellen kannst du Sets innerhalb von Luau-Skripten erstellen und manipulieren.
Grundlegende Methoden
Der folgende Code enthält grundlegende Set-Funktionalität, die es dir ermöglicht, neue Sets zu erstellen, ein Element hinzuzufügen und zu entfernen, zu überprüfen, ob ein Set ein Element enthält, und den Inhalt eines Sets auszugeben.
local Set = {}
Set.__index = Set
-- Funktion zum Erstellen eines Sets aus einer optionalen Liste von Elementen
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Funktion zum Hinzufügen eines Elements zu einem Set
function Set:add(item)
self[item] = true
end
-- Funktion zum Entfernen eines Elements aus einem Set
function Set:remove(item)
self[item] = nil
end
-- Funktion zum Überprüfen, ob ein Set ein Element enthält
function Set:contains(item)
return self[item] == true
end
-- Funktion zum Ausgeben des Sets als durch Kommas getrennte Liste zur Fehlersuche
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
endSet erstellen
Ein neues Set kann erstellt werden, indem Set.new() mit einem optionalen Array von hinzuzufügenden Elementen aufgerufen wird.
local fruits = Set.new({"Apfel", "Zitrone", "Orange", "Kirsche", "Limette", "Pfirsich"})Beachte, dass ein Set definitionsgemäß kein Konzept von Ordnung hat.
Element hinzufügen
Ein Element zu einem bestehenden Set kann über die Methode Set:add() hinzugefügt werden.
local fruits = Set.new({"Apfel", "Zitrone", "Orange", "Kirsche", "Limette", "Pfirsich"})
fruits:add("Mango")Element entfernen
Um ein Element aus einem Set zu entfernen, rufe Set:remove() mit dem Elementnamen auf.
local fruits = Set.new({"Apfel", "Zitrone", "Orange", "Kirsche", "Limette", "Pfirsich"})
fruits:remove("Orange")Nach Element überprüfen
Um zu überprüfen, ob ein Set ein bestimmtes Element enthält, verwende Set:contains().
local fruits = Set.new({"Apfel", "Zitrone", "Orange", "Kirsche", "Limette", "Pfirsich"})
local result1 = fruits:contains("Kirsche")
print(result1) -- true
local result2 = fruits:contains("Wassermelone")
print(result2) -- falseZusätzliche Methoden
Weitere nützliche Operationen können für Sets implementiert werden, die es dir ermöglichen, Elemente zwischen Sets zu vergleichen, Sets zu kombinieren oder ein Set von einem anderen abzuziehen.
Schnittmenge
Wenn man Sets als Venn-Diagramme betrachtet, kannst du die Schnittmenge von zwei Sets wie folgt erhalten, was die Elemente bedeutet, die in beiden Sets erscheinen.
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", "Zitrone", "Orange", "Kirsche", "Limette", "Pfirsich"})
local frozenFruits = Set.new({"Mango", "Pfirsich", "Ananas"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, PfirsichVereinigung
Du kannst die Vereinigung von zwei Sets mit der folgenden Funktion erhalten, was eine Sammlung der Elemente in beiden Sets ohne Duplikate bedeutet. Beachte, dass diese Funktion die Metatabelle __add-Methode verwendet, um eine Addition-Abkürzung von set1 + set2 bereitzustellen.
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({"Apfel", "Mango", "Kirsche", "Pfirsich"})
local sourFruits = Set.new({"Zitrone", "Limette"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Pfirsich, Limette, Apfel, Kirsche, Zitrone, MangoSubtraktion
Du kannst alle Elemente in einem Set von den Elementen in einem anderen Set über die folgende Funktion entfernen. Ähnlich wie die oben genannte Funktion verwendet diese die Metatabelle __sub-Methode, um eine Subtraktions-Abkürzung von set1 - set2 bereitzustellen.
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({"Apfel", "Zitrone", "Mango", "Kirsche", "Limette", "Pfirsich"})
local sourFruits = Set.new({"Zitrone", "Limette"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apfel, Kirsche, Pfirsich