Metatabulky

Každá tabulka může mít metatabulku: druhou, obyčejnou tabulku s pokyny, co má Lua s první tabulkou dělat ve zvláštních situacích. Připojíte ji funkcí setmetatable(tabulka, metatabulka).

Pokyny jsou uložené pod zvláštními klíči, které začínají dvěma podtržítky. Nejužitečnější z nich je __index. Říká, kde má Lua hledat, když čtete klíč, který tabulka nemá:

local trzniCeny = {mrkev = 5, brambory = 8, dyne = 20}
local cenyHugo = {dyne = 25}
setmetatable(cenyHugo, {__index = trzniCeny})

print(cenyHugo.dyne, cenyHugo.mrkev, cenyHugo.kukurice)

Vypíše:

25	5	nil

Obchodník Hugo platí běžné tržní ceny, jen za dýně dává víc. Jeho tabulka proto obsahuje jedinou cenu a ostatní hledá Lua jinde:

  • cenyHugo.dyne v Hugově tabulce je, takže výsledek je 25.
  • cenyHugo.mrkev tam není. Lua se podívá do metatabulky, najde v ní __index a přečte mrkev z tabulky, na kterou __index odkazuje, tedy z trzniCeny: 5.
  • kukurice není ani v jedné tabulce, takže výsledek je nil, jako u každého chybějícího klíče.

__index se použije jen při čtení chybějícího klíče. Když klíč zapisujete, uloží se do tabulky samotné a druhá tabulka se nezmění: po cenyHugo.mrkev = 6 platí Hugo za mrkev 6, ale trzniCeny.mrkev je pořád 5.

Tomu, co metatabulka drží pod takovým zvláštním klíčem, se říká metametoda. __index je první, kterou znáte, a v této lekci poznáte další. Funkce getmetatable(t) vrátí metatabulku tabulky t, nebo nil, když žádnou nemá.

Společné metody. Přesně takhle můžou objekty sdílet metody. Dejte metody do jedné tabulky a každému objektu dejte metatabulku, jejíž __index je právě tahle tabulka:

local metodyPlodiny = {}

function metodyPlodiny:dalsiDen()
    self.vek = self.vek + 1
end

function metodyPlodiny:popis()
    print(self.nazev .. ": den " .. self.vek .. "/" .. self.dny)
end

local function novaPlodina(nazev, dny)
    local plodina = {nazev = nazev, dny = dny, vek = 0}
    setmetatable(plodina, {__index = metodyPlodiny})
    return plodina
end

local mrkev = novaPlodina("Mrkev", 3)
local dyne = novaPlodina("Dýně", 7)
mrkev:dalsiDen()
mrkev:popis()
dyne:popis()
print(mrkev.dalsiDen == dyne.dalsiDen)

Vypíše:

Mrkev: den 1/3
Dýně: den 0/7
true

Co se stane při mrkev:dalsiDen():

  1. Je to zkratka pro mrkev.dalsiDen(mrkev), takže Lua nejdřív čte mrkev.dalsiDen. V mrkvi jsou jen nazev, dny a vek, žádné dalsiDen.
  2. Metatabulka mrkve má __index, a tak se Lua podívá do metodyPlodiny a dalsiDen tam najde.
  3. Lua tu funkci zavolá s mrkví jako self, takže self.vek = self.vek + 1 změní mrkev, a ne metodyPlodiny.

Každá plodina teď drží jen svá data a každá metoda existuje jen jednou: mrkev.dalsiDen == dyne.dalsiDen je true. Lua metodu hledá až ve chvíli, kdy ji voláte, takže metoda, kterou do metodyPlodiny přidáte později, funguje i pro plodiny, které už existují.

Tajemství s:upper(). I texty mají metatabulku, jednu společnou pro všechny texty, a její __index je tabulka string:

print(getmetatable("mrkev").__index == string)

Vypíše:

true

s:upper() je tedy s.upper(s). Text žádné vlastní klíče nemá, a tak se Lua podívá do jeho __index, tedy do tabulky string, a najde string.upper. Je to stejný mechanismus jako u plodin.

Pokyny

Ať slepice sdílejí metody. Nad funkcí novaSlepice vytvořte tabulku metodySlepice a přesuňte do ní obě metody (function metodySlepice:snes(pocet), …). V novaSlepice dejte každé nové slepici metatabulku, jejíž __index je metodySlepice.

Spusťte program: poslední řádek teď vypíše true, protože obě slepice používají stejnou funkci. Přidejte ještě metodu metodySlepice:seber(), která vrátí počet vajec slepice a nastaví ho zpátky na 0. Klidně ji napište až pod řádek, který vypisuje true: slepice hledají metody až ve chvíli, kdy je voláte. Na konci programu seberte vejce obou slepic a vypište, kolik jich je dohromady:

Zlatka snesla 2 vejce
Perla snesla 4 vejce
true
Sebráno: 6 vajec

Začněte programovat zdarma

Registrací souhlasíte s provozními podmínkami a zásadami ochrany osobních údajů.

Nebo se registrujte pomocí:

4/9