Методы и self
В farm.describe(farm) слово farm написано дважды: один раз, чтобы найти функцию, и второй — чтобы передать ей ферму. С объектами так делают постоянно, поэтому в Lua есть сокращение — двоеточие:
local farm = {name = "ферма Подсолнух", coins = 120}
function farm.describe(f)
print(f.name .. ", монеты: " .. f.coins)
end
farm:describe()Он выводит:
ферма Подсолнух, монеты: 120farm:describe() — сокращение от farm.describe(farm): Lua берёт функцию из таблицы и передаёт ей саму таблицу первым аргументом. Остальные аргументы идут после неё: farm:earn(30) означает farm.earn(farm, 30).
Двоеточие работает и при определении функции. function farm:earn(amount) — сокращение от function farm.earn(self, amount): Lua добавляет скрытый первый параметр с именем self. Когда вы вызываете функцию через двоеточие, self — это та таблица, у которой вы её вызвали:
local carrot = {name = "Морковь", age = 0, days = 3}
function carrot:grow()
self.age = self.age + 1
end
function carrot:isReady()
return self.age >= self.days
end
carrot:grow()
carrot:grow()
print(carrot.age, carrot:isReady())
carrot:grow()
print(carrot.age, carrot:isReady())Он выводит:
2 false
3 trueВ carrot:grow() параметр self — это carrot, поэтому self.age = self.age + 1 делает морковь на один день старше. Функция в объекте, которая получает объект как self, называется методом, а carrot:grow() — вызовом метода. Внутри метода всегда используйте self, а не имя таблицы. Тогда один и тот же метод подходит для любой таблицы, у которой вы его вызываете, и это понадобится уже в следующем упражнении.
Со строками то же самое. В уроке о строках вы узнали, что s:upper() — сокращение от string.upper(s). Теперь понятно, что делает двоеточие: s:upper() — это s.upper(s), вызов метода, где строка передаётся первым аргументом:
local s = "carrot"
print(s:upper(), s.upper(s), string.upper(s))Он выводит:
CARROT CARROT CARROTКак строка находит upper в таблице string, вы увидите в упражнении о метатаблицах.
Классическая ошибка: точка вместо двоеточия. Метод, определённый через двоеточие, нужно и вызывать через двоеточие. Если вызвать его через точку, Lua не передаст таблицу, и self окажется равен nil:
local carrot = {name = "Морковь", age = 0, days = 3}
function carrot:grow()
self.age = self.age + 1
end
carrot.grow()Программа завершается с ошибкой:
main.lua:4: attempt to index a nil value (local 'self')
stack traceback:
main.lua:4: in field 'grow'
main.lua:7: in main chunkПервая строка указывает на строку 4 внутри метода, где используется self. Но ошибка в строке, которая вызвала метод, — последней в трассировке: main.lua:7. Надпись in field 'grow' говорит, что функцию вызвали через точку; вызов через двоеточие показал бы in method 'grow'. Если в ошибке встречается (local 'self'), ищите точку, которая должна быть двоеточием.
Обратная ошибка — функция определена через точку, function carrot.grow(days), а вызвана через двоеточие, carrot:grow(2). Двоеточие передаёт таблицу первым аргументом, поэтому days получает морковь, а 2 игнорируется. Правило простое: определяйте методы через двоеточие и вызывайте их через двоеточие.
Задание
- 1
Превратите обе функции в методы: определите их через двоеточие,
function barn:report()иfunction barn:addEggs(count), используйте внутриselfи вызывайте через двоеточие. Программа выводит то же, что и раньше. - 2
Добавьте метод
barn:sellEggs(count), который забирает из сараяcountяиц и возвращает, сколько монет за них выручено: 2 монеты за яйцо. В конце программы продайте 10 яиц, выведите монеты и снова выведите отчёт:В сарае: яйца 12, молоко 4 л В сарае: яйца 18, молоко 4 л Продано яиц: 10, получено монет: 20 В сарае: яйца 8, молоко 4 л