Metatabellen
Eine Metatabelle definiert, wie sich eine Tabelle bei bestimmten Operationen verhält (Rechnen, Vergleichen, Ausgeben, fehlende Schlüssel). Du setzt sie mit setmetatable(t, mt).
local Vektor = {}
Vektor.__index = Vektor
function Vektor.neu(x, y) return setmetatable({x = x, y = y}, Vektor) end
function Vektor.laenge(v) return math.sqrt(v.x ^ 2 + v.y ^ 2) end
Vektor.__add = function(a, b) return Vektor.neu(a.x + b.x, a.y + b.y) end
Vektor.__mul = function(a, k) return Vektor.neu(a.x * k, a.y * k) end
Vektor.__eq = function(a, b) return a.x == b.x and a.y == b.y end
Vektor.__lt = function(a, b) return a:laenge() < b:laenge() end
Vektor.__tostring = function(v) return "(" .. v.x .. ", " .. v.y .. ")" end
Vektor.__len = function(v) return 2 end
Vektor.__call = function(v, faktor) return v * faktor end
Vektor.__unm = function(v) return Vektor.neu(-v.x, -v.y) end
Vektor.__concat = function(a, b) return tostring(a) .. tostring(b) end
local a, b = Vektor.neu(1, 2), Vektor.neu(3, 4)
print(tostring(a + b), tostring(a * 3), tostring(-a), a == Vektor.neu(1, 2), a < b, #a, tostring(a(10)))
print(a:laenge(), b:laenge(), a .. b)(4, 6) (3, 6) (-1, -2) true true 2 (10, 20) 2.2360679774998 5.0 (1, 2)(3, 4)
| Metamethode | Wann |
|---|---|
__index | Lesen eines fehlenden Schlüssels (Tabelle oder Funktion) |
__newindex | Schreiben eines fehlenden Schlüssels |
__add __sub __mul __div __mod __pow __unm __idiv | Rechenoperatoren |
__eq __lt __le | Vergleiche |
__concat __len __call | .., #, Aufruf wie Funktion |
__tostring | tostring() / print |
__close, __gc | Aufräumen |
Standardwerte und schreibgeschützte Tabellen
local standard = setmetatable({}, {__index = function() return 0 end})
standard.a = 5
print(standard.a, standard.b) -- fehlender Schlüssel liefert 0
local zaehler = setmetatable({}, {__index = function() return 0 end})
for _, w in ipairs({"a", "b", "a"}) do zaehler[w] = zaehler[w] + 1 end
print(zaehler.a, zaehler.b, zaehler.c)
local function nur_lesen(t)
return setmetatable({}, {
__index = t,
__newindex = function(_, k) error("Tabelle ist schreibgeschützt: " .. tostring(k), 2) end,
__len = function() return #t end,
})
end
local konfig = nur_lesen({port = 8080, host = "localhost"})
print(konfig.port, konfig.host)
print(pcall(function() konfig.port = 1 end))5 0 2 1 0 8080 localhost false main.lua:18: Tabelle ist schreibgeschützt: port
Klassen und Vererbung
Lua hat keine Klassen, aber mit Tabellen und __index baut man sie in wenigen Zeilen:
local Tier = {}
Tier.__index = Tier
function Tier.neu(name)
local self = setmetatable({}, Tier)
self.name = name
return self
end
function Tier:laut() return "..." end
function Tier:vorstellen() return self.name .. " sagt " .. self:laut() end
local Hund = setmetatable({}, {__index = Tier}) -- Hund erbt von Tier
Hund.__index = Hund
function Hund.neu(name)
local self = Tier.neu(name)
return setmetatable(self, Hund)
end
function Hund:laut() return "Wuff" end
function Hund:apportiere(was) return self.name .. " holt " .. was end
local Katze = setmetatable({}, {__index = Tier})
Katze.__index = Katze
function Katze.neu(name, leben)
local self = setmetatable(Tier.neu(name), Katze)
self.leben = leben or 9
return self
end
function Katze:laut() return "Miau" end
function Katze:vorstellen() return Tier.vorstellen(self) .. " (" .. self.leben .. " Leben)" end
local tiere = {Hund.neu("Rex"), Katze.neu("Mimi")}
for _, t in ipairs(tiere) do print(t:vorstellen()) end
print(tiere[1]:apportiere("Stock"))
print(getmetatable(tiere[1]) == Hund, tiere[2].apportiere)Rex sagt Wuff Mimi sagt Miau (9 Leben) Rex holt Stock true nil
Der Doppelpunkt obj:methode(a) ist Kurzschreibweise für obj.methode(obj, a); bei der Definition function Klasse:methode() entsteht automatisch der Parameter self.
Eine kleine Klassenhilfe
local function klasse(eltern)
local c = {}
c.__index = c
if eltern then setmetatable(c, {__index = eltern}) end
c.neu = function(...)
local o = setmetatable({}, c)
if o.init then o:init(...) end
return o
end
return c
end
local Konto = klasse()
function Konto:init(inhaber, stand) self.inhaber, self.stand = inhaber, stand or 0 end
function Konto:einzahlen(b)
assert(b > 0, "Betrag muss positiv sein")
self.stand = self.stand + b
return self
end
Konto.__tostring = function(k) return string.format("%s: %.2f", k.inhaber, k.stand) end
local Sparkonto = klasse(Konto)
Sparkonto.__tostring = Konto.__tostring
function Sparkonto:init(inhaber, stand, zins)
Konto.init(self, inhaber, stand)
self.zins = zins
end
function Sparkonto:zinsen() self.stand = self.stand * (1 + self.zins); return self end
local k = Konto.neu("Mia", 100):einzahlen(50)
local s = Sparkonto.neu("Tom", 1000, 0.05):zinsen()
print(tostring(k), tostring(s))
print(pcall(k.einzahlen, k, -5))Mia: 150.00 Tom: 1050.00 false main.lua:16: Betrag muss positiv sein
Merke
- Metatabellen steuern Operatoren, fehlende Schlüssel (
__index,__newindex) und Ausgabe (__tostring) - Klassen = Tabelle +
__index; Vererbung = Metatabelle der Klasse zeigt auf die Oberklasse obj:methode()übergibtselfautomatisch- Für schreibgeschützte Tabellen und Standardwerte sind Metatabellen ideal
Aufgabe
Baue eine Klasse Stapel mit push, pop, groesse und __tostring.