Metatabellen erlauben es Tabellen, mächtiger als je zuvor zu werden. Sie sind an Daten angehängt und enthalten Werte, die als Metamethoden bezeichnet werden. Metamethoden werden aktiviert, wenn eine bestimmte Aktion mit dem Datum ausgeführt wird, an das sie angehängt sind.
Metatabellen manipulieren
Die beiden Hauptfunktionen zum Hinzufügen und Finden der 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, von der du die Metatabelle setzt, 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 Indizierung einer Tabelle über das Hinzufügen einer Tabelle bis hin zur Division von Tabellen reichen. Hier ist die Liste der verfügbaren Metamethoden:
| Methode | Beschreibung |
|---|---|
| __index(table, index) | Wird aktiviert, 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 aktiviert, 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 aktiviert, wenn die Tabelle wie eine Funktion aufgerufen wird, ... sind die übergebenen Argumente. |
| __concat(table, value) | Wird aktiviert, wenn der Verkettungsoperator .. auf der Tabelle verwendet wird. |
| __unm(table) | Wird aktiviert, wenn der unäre – Operator auf der Tabelle verwendet 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 // ganzzahlige Divisionsoperator. |
| __mod(table, value) | Der % Modulo-Operator. |
| __pow(table, value) | Der ^ Exponentialoperator. |
| __tostring(table) | Aktiviert, wenn tostring auf der Tabelle aufgerufen wird. |
| __metatable | Wenn vorhanden, sperrt die Metatabelle, sodass getmetatable() diese zurückgibt, anstatt die Metatabelle zu verwenden, und setmetatable() einen Fehler verursacht. Kein Funktionswert. |
| __eq(table, value) | Der == Gleichheitsoperator¹ |
| __lt(table, value) | Der < Weniger-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. Beachte, dass Referenzen auf Roblox-Instanzen niemals schwach sind. Tabellen, die solche Referenzen halten, werden niemals vom Garbage Collector gesammelt. |
| __len(table) | Wird aktiviert, wenn der # Längenoperator auf dem Objekt verwendet wird. |
| __iter(table) | Verwendet, um einen benutzerdefinierten Iterator bei der Verwendung verallgemeinerter Iteration zu kennzeichnen. |
Es sollte beachtet werden, dass bei der Erstellung von Funktionen für arithmetische oder relationale Metamethoden die beiden Funktionsparameter austauschbar sind zwischen der Tabelle, die die Metamethode aktiviert hat, und dem anderen Wert. Zum Beispiel ist die Division bei Vektoroperationen mit Skalaren nicht kommutativ. Daher solltest du, wenn du Metamethoden für deine eigene vector2-Klasse schreibst, 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.5
Verwendung
Es gibt viele Möglichkeiten, Metatabellen zu verwenden, zum Beispiel die Metamethode __unm, um eine Tabelle negativ zu machen:
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 -- gib die Tabelle zurück
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
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 aktiviert, als x in der Tabelle indiziert und nicht gefunden wurde. Luau suchte dann in der __index-Tabelle nach einem Index mit dem Namen x und, nachdem er einen gefunden hatte, wurde dieser zurückgegeben.
Jetzt kannst du das ganz einfach mit einer einfachen Funktion machen, aber es gibt noch viel mehr, was daraus entsteht. Nimm zum Beispiel dies:
local t = {10, 20, 30}print(t(5))
Typischerweise kannst du eine Tabelle nicht aufrufen, aber mit Metatabellen kannst du es:
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 lege es dann in der neuen Tabelle (t) ab.
end
return unpack(sum) -- Gib die einzelnen Tabellenwerte zurück
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Du kannst noch viel mehr tun, wie z.B. Tabellen addieren:
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Dies wird einen Fehler verursachen und sagen, dass du versuchst, Arithmetik mit 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)
end
Beim Spielen mit Metatabellen könntest du auf einige Probleme stoßen. Wenn du die Metamethode __index verwenden musst, um neue Werte in einer Tabelle zu erstellen, diese Tabelle jedoch auch eine Metatabelle mit der Metamethode __newindex hat, möchtest du die eingebaute Luau-Funktion rawset() verwenden, um den Wert zu setzen, ohne irgendwelche 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 zur Tabelle auf die normale Weise
end
})
print(t[1]) -- verursacht einen Stack Overflow
Stack Overflows treten auf, wenn du versuchst, eine Funktion zu oft von sich selbst aufzurufen. In der oben genannten __index-Funktion wird self[i] auf einen Wert gesetzt, sodass es, wenn es zur nächsten Zeile kommt, existieren sollte und vermutlich die __index-Metamethode nicht aufruft. Das Problem ist, dass __newindex dir nicht erlaubt, den Wert zu setzen. Ihre Anwesenheit verhindert, dass Werte mit der Standardmethode t[i] = v zur Tabelle hinzugefügt werden. Um darüber hinwegzukommen, 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 zur Tabelle auf die normale Weise
end
})
print(t[1]) --> 10
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 in Luau-Skripten erstellen und manipulieren.
Grundlegende Methoden
Der folgende Code umfasst grundlegende Set-Funktionalitäten, die es dir ermöglichen, 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, um ein Set aus einer optionalen Liste von Elementen zu erstellen
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Funktion, um ein Element zu einem Set hinzuzufügen
function Set:add(item)
self[item] = true
end
-- Funktion, um ein Element aus einem Set zu entfernen
function Set:remove(item)
self[item] = nil
end
-- Funktion, um zu überprüfen, ob ein Set ein Element enthält
function Set:contains(item)
return self[item] == true
end
-- Funktion, um das Set als durch Kommas getrennte Liste zur Fehlersuche auszugeben
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
Set erstellen
Ein neues Set kann erstellt werden, indem Set.new() mit einem optionalen Array von 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 hinzuzufügen, kann über die Methode Set:add() erfolgen.
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")
Auf 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) -- truelocal result2 = fruits:contains("Wassermelone")print(result2) -- false
Zusä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 Set zu subtrahieren.
Schnittmenge
Wenn man Sets als Venn-Diagramme betrachtet, kann man die Schnittmenge von zwei Sets wie folgt erhalten, was bedeutet, dass die Elemente, 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, Pfirsich
Vereinigung
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 verwendet, um eine Addition-Kurzschrift 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, Mango
Subtraktion
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 dies die Metatabelle __sub, um eine Subtraktions-Kurzschrift 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