Zoeken…
Syntaxis
- [[local] mt =] getmetatable ( t ) -> ophalen bijbehorende metatable voor ' t '
- [[local] t =] setmetatable ( t , mt ) -> stel de metatabel voor ' t ' in op ' mt ' en retourneert ' t '
parameters
Parameter | Details |
---|---|
t | Variabele die verwijst naar een lua-tabel; kan ook een letterlijke tafel zijn. |
mt | Tabel om te gebruiken als metatabel; kan nul of meer metamethodvelden hebben ingesteld. |
Opmerkingen
Er zijn enkele metamethoden die hier niet worden genoemd. Voor de volledige lijst en hun gebruik, zie de overeenkomstige vermelding in de lua-handleiding .
Creatie en gebruik van metatabellen
Een metatabel definieert een reeks bewerkingen die het gedrag van een lua-object wijzigen. Een metatabel is gewoon een gewone tabel, die op een speciale manier wordt gebruikt.
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 gebruiken als metamethoden
Sommige metamethoden hoeven geen functies te zijn. Het belangrijkste voorbeeld hiervan is de __index
metamethod. Het kan ook een tabel zijn, die vervolgens wordt gebruikt als opzoekactie. Dit wordt vrij vaak gebruikt bij het maken van klassen in Lua. Hier wordt een tabel (vaak de metatabel zelf) gebruikt om alle bewerkingen (methoden) van de klasse te bevatten:
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()
Vuilnisman - de __gc-metamethod
Voorwerpen in lua worden afval verzameld. Soms moet u een bron vrijmaken, een bericht afdrukken of iets anders doen wanneer een object wordt vernietigd (verzameld). Hiervoor kunt u de __gc
metamethod gebruiken, die met het object als argument wordt aangeroepen wanneer het object wordt vernietigd. Je zou deze metamethode kunnen zien als een soort destructor.
Dit voorbeeld toont de __gc
metamethode in actie. Wanneer de binnentafel die aan t
toegewezen, afval wordt verzameld, wordt er een bericht afgedrukt voordat het wordt opgehaald. Hetzelfde geldt voor de buitenste tabel wanneer het einde van het script wordt bereikt:
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
Meer metamethoden
Er zijn nog veel meer metamethoden, sommige zijn rekenkundig (bijv. Optellen, aftrekken, vermenigvuldigen), er zijn bitgewijze bewerkingen (en, of, xor, shift), vergelijking (<,>) en ook basistype-bewerkingen zoals == en # (gelijkheid en lengte). Laten we een klasse bouwen die veel van deze bewerkingen ondersteunt: een oproep voor rationeel rekenen. Hoewel dit erg basic is, laat het het idee zien.
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
Maak tabellen opvraagbaar
Er is een metamethode genaamd __call
, die de bevahiour van het object definieert bij gebruik als een functie, bijvoorbeeld object()
. Dit kan worden gebruikt om functieobjecten te maken:
-- 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
De metamethod wordt met het bijbehorende object aangeroepen, alle resterende argumenten worden daarna doorgegeven aan de functie:
local meta = {
__call = function(self, ...)
print(self.prepend, ...)
end
}
local self = { prepend = "printer:" }
setmetatable(self, meta)
self("foo", "bar", "baz")
Indexering van tabellen
Misschien is het belangrijkste gebruik van metatabellen de mogelijkheid om de indexering van tabellen te wijzigen. Hiervoor zijn er twee acties te overwegen: het lezen van de inhoud en het schrijven van de inhoud van de tabel. Merk op dat beide acties alleen worden geactiveerd als de bijbehorende sleutel niet in de tabel aanwezig is.
Lezing
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
Dit kan worden gebruikt om een fout te genereren tijdens het lezen van een niet-bestaande sleutel:
-- 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
Schrijven
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
U kunt zich nu afvragen hoe de werkelijke waarde in de tabel wordt geschreven. In dit geval niet. Het probleem is hier dat metamethoden metamethoden kunnen activeren, wat zou resulteren in een infinitieve lus, of beter gezegd, een stapeloverloop. Dus hoe kunnen we dit oplossen? De oplossing hiervoor wordt onbewerkte toegang tot de tabel genoemd .
Toegang tot onbewerkte tabellen
Soms wil je geen metamethoden activeren, maar schrijf of lees je precies de gegeven sleutel, zonder een paar slimme functies rond de toegang. Hiervoor biedt lua onbewerkte toegangsmethoden voor tabellen:
-- 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])
Hiermee kunnen we de vorige __newindex
methode herschrijven om de waarde daadwerkelijk naar de tabel te schrijven:
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 simuleren
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'"
Instantiemethoden kunnen worden geschreven door het object als het eerste argument door te geven.
-- 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"
Daar is wat syntactische suiker voor.
function Class:saybye(phrase)
print("I am " .. self .. "\n" .. phrase)
end
object:saybye("c ya") --> will print "I am <table ID>
--> c ya"
We kunnen ook standaardvelden aan een klasse toevoegen.
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