Suche…
Syntax
- [[local] mt =] getmetatable ( t ) -> Verbundenes Metatable für ' t ' abrufen
- [[local] t =] setmetatable ( t , mt ) -> setze die Metatable für ' t ' auf ' mt ' und gibt ' t ' zurück
Parameter
Parameter | Einzelheiten |
---|---|
t | Variable, die sich auf eine Lua-Tabelle bezieht; kann auch ein Tabellenliteral sein. |
mt | Tabelle zur Verwendung als metatable; kann null oder mehr Metamethod-Felder haben. |
Bemerkungen
Es gibt einige Metamethoden, die hier nicht erwähnt werden. Die vollständige Liste und ihre Verwendung finden Sie im entsprechenden Eintrag im Lua-Handbuch .
Erstellung und Verwendung von Metatabellen
Eine Metable definiert eine Menge von Operationen, die das Verhalten eines Lua-Objekts verändern. Ein Metable ist nur ein gewöhnlicher Tisch, der auf besondere Weise verwendet wird.
local meta = { } -- create a table for use as metatable
-- a metatable can change the behaviour of many things
-- here we modify the 'tostring' operation:
-- this fields should be a function with one argument.
-- it gets called with the respective object and should return a string
meta.__tostring = function (object)
return string.format("{ %d, %d }", object.x, object.y)
end
-- create an object
local point = { x = 13, y = -2 }
-- set the metatable
setmetatable(point, meta)
-- since 'print' calls 'tostring', we can use it directly:
print(point) -- prints '{ 13, -2 }'
Tabellen als Metamethoden verwenden
Einige Metamethoden müssen keine Funktionen sein. Das wichtigste Beispiel hierfür ist die __index
__index. Es kann auch eine Tabelle sein, die dann als Nachschlagewerk verwendet wird. Dies wird häufig bei der Erstellung von Klassen in Lua verwendet. Hier wird eine Tabelle (oft die Metable selbst) verwendet, um alle Operationen (Methoden) der Klasse aufzunehmen:
local meta = {}
-- set the __index method to the metatable.
-- Note that this can't be done in the constructor!
meta.__index = meta
function create_new(name)
local self = { name = name }
setmetatable(self, meta)
return self
end
-- define a print function, which is stored in the metatable
function meta.print(self)
print(self.name)
end
local obj = create_new("Hello from object")
obj:print()
Garbage Collector - die __gc-Metamethode
Objekte in Lua sind Müll gesammelt. Manchmal müssen Sie eine Ressource freigeben, eine Nachricht drucken oder etwas anderes tun, wenn ein Objekt zerstört (gesammelt) wird. Dazu können Sie die __gc
__gc verwenden, die mit dem Objekt als Argument aufgerufen wird, wenn das Objekt zerstört wird. Sie könnten diese Metamethode als eine Art Destruktor betrachten.
Dieses Beispiel zeigt die __gc
__gc in Aktion. Wenn die innere Tabelle, die t
zugewiesen ist, Müll gesammelt wird, druckt sie eine Nachricht, bevor sie gesammelt wird. Ebenso für die äußere Tabelle, wenn das Skriptende erreicht ist:
local meta =
{
__gc = function(self)
print("destroying self: " .. self.name)
end
}
local t = setmetatable({ name = "outer" }, meta)
do
local t = { name = "inner" }
setmetatable(t, meta)
end
Weitere Metamethoden
Es gibt viele weitere Metamethoden, einige davon sind Arithmetik (z. B. Addition, Subtraktion, Multiplikation), es gibt bitweise Operationen (und / oder xor, shift), Vergleiche (<,>) sowie grundlegende Typoperationen wie == und # (Gleichheit und Länge). Lassen Sie uns eine Klasse erstellen, die viele dieser Operationen unterstützt: einen Aufruf zur rationalen Arithmetik. Dies ist zwar sehr einfach, zeigt aber die Idee.
local meta = {
-- string representation
__tostring = function(self)
return string.format("%s/%s", self.num, self.den)
end,
-- addition of two rationals
__add = function(self, rhs)
local num = self.num * rhs.den + rhs.num * self.den
local den = self.den * rhs.den
return new_rational(num, den)
end,
-- equality
__eq = function(self, rhs)
return self.num == rhs.num and self.den == rhs.den
end
}
-- a function for the creation of new rationals
function new_rational(num, den)
local self = { num = num, den = den }
setmetatable(self, meta)
return self
end
local r1 = new_rational(1, 2)
print(r1) -- 1/2
local r2 = new_rational(1, 3)
print(r1 + r2) -- 5/6
local r3 = new_rational(1, 2)
print(r1 == r3) -- true
-- this would be the behaviour if we hadn't implemented the __eq metamethod.
-- this compares the actual tables, which are different
print(rawequal(r1, r3)) -- false
Tabellen aufrufbar machen
Es gibt eine metamethod genannt __call
, die die bevahiour des Objekts definiert , bei der als eine Funktion benutzt wird, zum Beispiel object()
. Damit können Funktionsobjekte angelegt werden:
-- create the metatable with a __call metamethod
local meta = {
__call = function(self)
self.i = self.i + 1
end,
-- to view the results
__tostring = function(self)
return tostring(self.i)
end
}
function new_counter(start)
local self = { i = start }
setmetatable(self, meta)
return self
end
-- create a counter
local c = new_counter(1)
print(c) --> 1
-- call -> count up
c()
print(c) --> 2
Die Metamethod wird mit dem entsprechenden Objekt aufgerufen, danach werden alle verbleibenden Argumente an die Funktion übergeben:
local meta = {
__call = function(self, ...)
print(self.prepend, ...)
end
}
local self = { prepend = "printer:" }
setmetatable(self, meta)
self("foo", "bar", "baz")
Indizierung von Tabellen
Die wichtigste Verwendung von Metatables ist möglicherweise die Möglichkeit, die Indizierung von Tabellen zu ändern. Hierzu sind zwei Aktionen zu berücksichtigen: Lesen des Inhalts und Schreiben des Tabelleninhalts. Beachten Sie, dass beide Aktionen nur ausgelöst werden, wenn der entsprechende Schlüssel nicht in der Tabelle vorhanden ist.
lesen
local meta = {}
-- to change the reading action, we need to set the '__index' method
-- it gets called with the corresponding table and the used key
-- this means that table[key] translates into meta.__index(table, key)
meta.__index = function(object, index)
-- print a warning and return a dummy object
print(string.format("the key '%s' is not present in object '%s'", index, object))
return -1
end
-- create a testobject
local t = {}
-- set the metatable
setmetatable(t, meta)
print(t["foo"]) -- read a non-existent key, prints the message and returns -1
Dies kann verwendet werden, um einen Fehler beim Lesen eines nicht vorhandenen Schlüssels auszulösen:
-- raise an error upon reading a non-existent key
meta.__index = function(object, index)
error(string.format("the key '%s' is not present in object '%s'", index, object))
end
Schreiben
local meta = {}
-- to change the writing action, we need to set the '__newindex' method
-- it gets called with the corresponding table, the used key and the value
-- this means that table[key] = value translates into meta.__newindex(table, key, value)
meta.__newindex = function(object, index, value)
print(string.format("writing the value '%s' to the object '%s' at the key '%s'",
value, object, index))
--object[index] = value -- we can't do this, see below
end
-- create a testobject
local t = { }
-- set the metatable
setmetatable(t, meta)
-- write a key (this triggers the method)
t.foo = 42
Sie können sich jetzt fragen, wie der tatsächliche Wert in die Tabelle geschrieben wird. In diesem Fall ist es nicht der Fall. Das Problem hierbei ist, dass Metamethoden Metamethoden auslösen können, die zu einer infinitiven Schleife oder genauer zu einem Stapelüberlauf führen würden. Wie können wir das lösen? Die Lösung hierfür heißt Raw Table Access .
Rohtabellenzugriff
Manchmal möchten Sie keine Metamethods auslösen, sondern wirklich genau den angegebenen Schlüssel schreiben oder lesen, ohne um den Zugriff geschickte Funktionen zu verwenden. Zu diesem Zweck stellt Ihnen Lua unformatierte Tabellenzugriffsmethoden zur Verfügung:
-- first, set up a metatable that allows no read/write access
local meta = {
__index = function(object, index)
-- raise an error
error(string.format("the key '%s' is not present in object '%s'", index, object))
end,
__newindex = function(object, index, value)
-- raise an error, this prevents any write access to the table
error(string.format("you are not allowed to write the object '%s'", object))
end
}
local t = { foo = "bar" }
setmetatable(t, meta)
-- both lines raise an error:
--print(t[1])
--t[1] = 42
-- we can now circumvent this problem by using raw access:
print(rawget(t, 1)) -- prints nil
rawset(t, 1, 42) -- ok
-- since the key 1 is now valid, we can use it in a normal manner:
print(t[1])
Damit können wir jetzt die frühere __newindex
Methode neu schreiben, um den Wert tatsächlich in die Tabelle zu schreiben:
meta.__newindex = function(object, index, value)
print(string.format("writing the value '%s' to the object '%s' at the key '%s'",
value, object, index))
rawset(object, index, value)
end
OOP simulieren
local Class = {} -- objects and classes will be tables
local __meta = {__index = Class}
-- ^ if an instance doesn't have a field, try indexing the class
function Class.new()
-- return setmetatable({}, __meta) -- this is shorter and equivalent to:
local new_instance = {}
setmetatable(new_instance, __meta)
return new_instance
end
function Class.print()
print "I am an instance of 'class'"
end
local object = Class.new()
object.print() --> will print "I am an instance of 'class'"
Instanzmethoden können geschrieben werden, indem das Objekt als erstes Argument übergeben wird.
-- append to the above example
function Class.sayhello(self)
print("hello, I am ", self)
end
object.sayhello(object) --> will print "hello, I am <table ID>"
object.sayhello() --> will print "hello, I am nil"
Dafür gibt es etwas syntaktischen Zucker.
function Class:saybye(phrase)
print("I am " .. self .. "\n" .. phrase)
end
object:saybye("c ya") --> will print "I am <table ID>
--> c ya"
Wir können auch Standardfelder zu einer Klasse hinzufügen.
local Class = {health = 100}
local __meta = {__index = Class}
function Class.new() return setmetatable({}, __meta) end
local object = Class.new()
print(object.health) --> prints 100
Class.health = 50; print(object.health) --> prints 50
-- this should not be done, but it illustrates lua indexes "Class"
-- when "object" doesn't have a certain field
object.health = 200 -- This does NOT index Class
print(object.health) --> prints 200