Метатаблицы
У любой таблицы может быть метатаблица — вторая, самая обычная таблица с указаниями для Lua, что делать с первой таблицей в особых случаях. Подключить её можно через setmetatable(table, metatable).
Указания хранятся под особыми ключами, которые начинаются с двух подчёркиваний. Самый полезный из них — __index. Он говорит Lua, где искать, когда вы читаете ключ, которого в таблице нет:
local marketPrices = {carrot = 5, potato = 8, pumpkin = 20}
local hugoPrices = {pumpkin = 25}
setmetatable(hugoPrices, {__index = marketPrices})
print(hugoPrices.pumpkin, hugoPrices.carrot, hugoPrices.corn)Он выводит:
25 5 nilТорговец Гуго платит обычные рыночные цены, кроме тыкв: за них он платит больше. В его таблице лежит только эта одна цена, а остальные Lua ищет в другом месте:
hugoPrices.pumpkinесть в таблице Гуго, поэтому это25.hugoPrices.carrotтам нет. Lua смотрит на метатаблицу, находит в ней__indexи читаетcarrotиз таблицы, на которую указывает__index, — изmarketPrices:5.cornнет ни в одной из таблиц, поэтому результат —nil, как для любого отсутствующего ключа.
__index работает только когда вы читаете отсутствующий ключ. Когда вы записываете ключ, он попадает в саму таблицу, а другая таблица не меняется: после hugoPrices.carrot = 6 Гуго платит за морковь 6, а marketPrices.carrot по-прежнему равно 5.
То, что метатаблица хранит под таким особым ключом, называется метаметодом. __index — первый, который вы узнали, и в этом уроке будут другие. getmetatable(t) возвращает метатаблицу таблицы t или nil, если её нет.
Общие методы. Так объекты могут делить методы между собой. Положите методы в одну таблицу и дайте каждому объекту метатаблицу, у которой __index — эта таблица:
local cropMethods = {}
function cropMethods:grow()
self.age = self.age + 1
end
function cropMethods:describe()
print(self.name .. ": день " .. self.age .. " из " .. self.days)
end
local function newCrop(name, days)
local crop = {name = name, days = days, age = 0}
setmetatable(crop, {__index = cropMethods})
return crop
end
local carrot = newCrop("Морковь", 3)
local pumpkin = newCrop("Тыква", 7)
carrot:grow()
carrot:describe()
pumpkin:describe()
print(carrot.grow == pumpkin.grow)Он выводит:
Морковь: день 1 из 3
Тыква: день 0 из 7
trueВот что делает carrot:grow():
- Это сокращение от
carrot.grow(carrot), поэтому Lua сначала читаетcarrot.grow. В моркови лежат толькоname,daysиage, аgrowтам нет. - У метатаблицы моркови есть
__index, поэтому Lua заглядывает вcropMethodsи находитgrowтам. - Lua вызывает эту функцию, передав морковь как
self, поэтомуself.age = self.age + 1меняет морковь, а неcropMethods.
Теперь каждая культура хранит только свои данные, а каждый метод существует в единственном экземпляре: carrot.grow == pumpkin.grow равно true. Lua ищет метод в момент вызова, поэтому метод, который вы позже добавите в cropMethods, заработает и у уже созданных культур.
Секрет s:upper(). У строк тоже есть метатаблица, одна на все строки, и её __index — это таблица string:
print(getmetatable("carrot").__index == string)Он выводит:
trueЗначит, s:upper() — это s.upper(s). Своих ключей у строки нет, поэтому Lua смотрит в её __index, то есть в string, и находит там string.upper. Механизм тот же, что и у культур.
Задание
- 1
Пусть куры делят методы между собой. Над
newHenсоздайте таблицуhenMethodsи перенесите в неё оба метода (function henMethods:lay(count), …). ВnewHenдайте каждой новой курице метатаблицу, у которой__index— этоhenMethods. - 2
Запустите программу: последняя строка теперь выводит
true, потому что обе курицы используют одну и ту же функцию. Добавьте ещё один метод,henMethods:collect(), который возвращает яйца курицы и обнуляет их. Его можно написать под строкой, которая выводитtrue: куры ищут свои методы только в момент вызова. В конце программы соберите яйца обеих кур и выведите, сколько их вместе:Златка снесла яиц: 2 Перла снесла яиц: 4 true Собрано яиц: 6