EMZETT.
Login

Kurz: 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).

Teil des Kurses Lua

Kapitel 6 von 8 im Kurs Lua (Abschnitt „Fortgeschritten“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

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)

Ausgabe:

(4, 6)	(3, 6)	(-1, -2)	true	true	2	(10, 20)
2.2360679774998	5.0	(1, 2)(3, 4)
MetamethodeWann
__indexLesen eines fehlenden Schlüssels (Tabelle oder Funktion)
__newindexSchreiben eines fehlenden Schlüssels
__add __sub __mul __div __mod __pow __unm __idivRechenoperatoren
__eq __lt __leVergleiche
__concat __len __call.., #, Aufruf wie Funktion
__tostringtostring() / print
__close, __gcAufrä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))

Ausgabe:

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)

Ausgabe:

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))

Ausgabe:

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() übergibt self automatisch
  • Für schreibgeschützte Tabellen und Standardwerte sind Metatabellen ideal

Übungsaufgabe

Baue eine Klasse Stapel mit push, pop, groesse und __tostring.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: String-Funktionen und Muster

Weiter: Fehler, Module und Coroutinen

Alle Kapitel: Lua im Überblick