learnxinyminutes-docs/ru/lua.md
2024-12-08 23:20:53 -07:00

424 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
filename: learnlua-ru.lua
contributors:
- ["Tyler Neylon", "http://tylerneylon.com/"]
translators:
- ["Max Solomonov", "https://vk.com/solomonovmaksim"]
- ["Max Truhonin", "https://vk.com/maximmax42"]
- ["Konstantin Gromyko", "https://vk.com/id0x1765d79"]
- ["Stanislav Gromov", "https://vk.com/id156354391"]
---
```lua
-- Два дефиса начинают однострочный комментарий.
--[[
Добавление двух квадратных скобок
делает комментарий многострочным.
--]]
--------------------------------------------------------------------------------
-- 1. Переменные, циклы и условия.
--------------------------------------------------------------------------------
num = 42 -- Все числа имеют тип double.
-- Не волнуйтесь, в 64-битных double 52 бита
-- отведено под хранение целой части числа;
-- точность не является проблемой для
-- целочисленных значений, занимающих меньше 52 бит.
s = 'walternate' -- Неизменные строки, как в Python.
t = "Двойные кавычки также приветствуются"
u = [[ Двойные квадратные скобки
начинают и заканчивают
многострочные значения.]]
t = nil -- Удаляет определение переменной t; в Lua есть сборка мусора.
-- Блоки обозначаются ключевыми словами, такими как do/end:
while num < 50 do
num = num + 1 -- Операторов ++ и += нет.
end
-- Ветвление "если":
if num > 40 then
print('больше 40')
elseif s ~= 'walternate' then -- ~= обозначает "не равно".
-- Проверка равенства это ==, как в Python; работает для строк.
io.write('не больше 40\n') -- По умолчанию вывод в stdout.
else
-- По умолчанию переменные являются глобальными.
thisIsGlobal = 5 -- Стиль CamelСase является общим.
-- Как сделать переменную локальной:
local line = io.read() -- Считывает введённую строку.
-- Для конкатенации строк используется оператор .. :
print('Зима пришла, ' .. line)
end
-- Неопределённые переменные возвращают nil.
-- Этот пример не является ошибочным:
foo = anUnknownVariable -- Теперь foo = nil.
aBoolValue = false
-- Только значения nil и false являются ложными; 0 и '' являются истинными!
if not aBoolValue then print('это значение ложно') end
-- Для 'or' и 'and' действует принцип "какой оператор дальше,
-- тот и применяется". Это действует аналогично оператору a?b:c в C/js:
ans = aBoolValue and 'yes' or 'no' --> 'no'
karlSum = 0
for i = 1, 100 do -- Здесь указан диапазон, ограниченный с двух сторон.
karlSum = karlSum + i
end
-- Используйте "100, 1, -1" как нисходящий диапазон:
fredSum = 0
for j = 100, 1, -1 do fredSum = fredSum + j end
-- В основном, диапазон устроен так: начало, конец[, шаг].
-- Другая конструкция цикла:
repeat
print('путь будущего')
num = num - 1
until num == 0
--------------------------------------------------------------------------------
-- 2. Функции.
--------------------------------------------------------------------------------
function fib(n)
if n < 2 then return n end
return fib(n - 2) + fib(n - 1)
end
-- Вложенные и анонимные функции являются нормой:
function adder(x)
-- Возвращаемая функция создаётся, когда вызывается функция adder,
-- и запоминает значение переменной x:
return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16)) --> 25
print(a2(64)) --> 100
-- Возвраты, вызовы функций и присвоения работают со списками,
-- которые могут иметь разную длину.
-- Лишние получатели принимают значение nil, а лишние значения игнорируются.
x, y, z = 1, 2, 3, 4
-- Теперь x = 1, y = 2, z = 3, а 4 просто отбрасывается.
function bar(a, b, c)
print(a, b, c)
return 4, 8, 15, 16, 23, 42
end
x, y = bar('zaphod') --> выводит "zaphod nil nil"
-- Теперь x = 4, y = 8, а значения 15..42 отбрасываются.
-- Функции могут быть локальными и глобальными. Эти строки делают одно и то же:
function f(x) return x * x end
f = function (x) return x * x end
-- Эти тоже:
local function g(x) return math.sin(x) end
local g = function(x) return math.sin(x) end
-- Эквивалентно для local function g(x)..., однако ссылки на g
-- в теле функции не будут работать, как ожидалось.
local g; g = function (x) return math.sin(x) end
-- 'local g' будет прототипом функции.
-- Кстати, тригонометрические функции работают с радианами.
-- Вызов функции с одним строковым параметром не требует круглых скобок:
print 'hello' -- Работает без ошибок.
-- Вызов функции с одним табличным параметром также
-- не требует круглых скобок (про таблицы в след. части):
print {} -- Тоже сработает.
--------------------------------------------------------------------------------
-- 3. Таблицы.
--------------------------------------------------------------------------------
-- Таблица = единственная составная структура данных в Lua;
-- представляет собой ассоциативный массив.
-- Подобно массивам в PHP или объектам в JS, они представляют собой
-- хеш-таблицы, которые также можно использовать в качестве списков.
-- Использование словарей:
-- Литералы имеют ключ по умолчанию:
t = {key1 = 'value1', key2 = false}
-- Строковые ключи используются, как в точечной нотации в JS:
print(t.key1) -- Печатает 'value1'.
t.newKey = {} -- Добавляет новую пару ключ/значение.
t.key2 = nil -- Удаляет key2 из таблицы.
-- Литеральная нотация для любого значения ключа (кроме nil):
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'}
print(u[6.28]) -- пишет "tau"
-- Ключ соответствует значению для чисел и строк, но при
-- использовании таблицы в качестве ключа берётся её экземпляр.
a = u['@!#'] -- Теперь a = 'qbert'.
b = u[{}] -- Вы могли ожидать 1729, но получится nil:
-- b = nil, т.к. ключ не будет найден.
-- Это произойдёт потому, что за ключ мы использовали не тот же самый объект,
-- который был использован для сохранения оригинального значения.
-- Поэтому строки и числа удобнее использовать в качестве ключей.
-- Вызов функции с одной таблицей в качестве аргумента
-- не требует круглых скобок:
function h(x) print(x.key1) end
h{key1 = 'Sonmi~451'} -- Печатает 'Sonmi~451'.
for key, val in pairs(u) do -- Цикл по таблице.
print(key, val)
end
-- _G - это таблица со всеми глобалями.
print(_G['_G'] == _G) -- Печатает 'true'.
-- Использование таблиц, как списков / массивов:
-- Список значений с неявно заданными целочисленными ключами:
v = {'value1', 'value2', 1.21, 'gigawatts'}
for i = 1, #v do -- #v - размер списка v.
print(v[i]) -- Нумерация начинается с 1 !!
end
-- Список не является отдельным типом. v - всего лишь таблица
-- с последовательными целочисленными ключами, воспринимаемая как список.
--------------------------------------------------------------------------------
-- 3.1 Метатаблицы и метаметоды.
--------------------------------------------------------------------------------
-- Таблицу можно связать с метатаблицей, задав ей поведение, как при
-- перегрузке операторов. Позже мы увидим, что метатаблицы поддерживают
-- поведение, как в js-прототипах.
f1 = {a = 1, b = 2} -- Представляет дробь a/b.
f2 = {a = 2, b = 3}
-- Это не сработает:
-- s = f1 + f2
metafraction = {}
function metafraction.__add(f1, f2)
local sum = {}
sum.b = f1.b * f2.b
sum.a = f1.a * f2.b + f2.a * f1.b
return sum
end
setmetatable(f1, metafraction)
setmetatable(f2, metafraction)
s = f1 + f2 -- вызвать __add(f1, f2) на метатаблице от f1
-- f1, f2 не имеют ключа для своих метатаблиц в отличии от прототипов в js,
-- нужно получить его через getmetatable(f1). Метатаблица - обычная таблица
-- поэтому с ключами, известными для Lua (например, __add).
-- Но следущая строка будет ошибочной т.к в s нет метатаблицы:
-- t = s + s
-- Похожий на классы подход, приведенный ниже, поможет это исправить.
-- __index перегружает в метатаблице просмотр через точку:
defaultFavs = {animal = 'gru', food = 'donuts'}
myFavs = {food = 'pizza'}
setmetatable(myFavs, {__index = defaultFavs})
eatenBy = myFavs.animal -- работает! спасибо, мета-таблица.
--------------------------------------------------------------------------------
-- При неудаче прямой табличный поиск попытается использовать
-- значение __index в метатаблице, причём это рекурсивно.
-- Значение __index также может быть функцией
-- function(tbl, key) для настраиваемого поиска.
-- Значения типа __index, __add, ... называются метаметодами.
-- Ниже приведён полный список метаметодов.
-- __add(a, b) для a + b
-- __sub(a, b) для a - b
-- __mul(a, b) для a * b
-- __div(a, b) для a / b
-- __mod(a, b) для a % b
-- __pow(a, b) для a ^ b
-- __unm(a) для -a
-- __concat(a, b) для a .. b
-- __len(a) для #a
-- __eq(a, b) для a == b
-- __lt(a, b) для a < b
-- __le(a, b) для a <= b
-- __index(a, b) <функция или таблица> для a.b
-- __newindex(a, b, c) для a.b = c
-- __call(a, ...) для a(...)
--------------------------------------------------------------------------------
-- 3.2 Классоподобные таблицы и наследование.
--------------------------------------------------------------------------------
-- В Lua нет поддержки классов на уровне языка,
-- однако существуют разные способы их создания с помощью
-- таблиц и метатаблиц.
-- Ниже приведён один из таких способов.
Dog = {} -- 1.
function Dog:new() -- 2.
local newObj = {sound = 'woof'} -- 3.
self.__index = self -- 4.
return setmetatable(newObj, self) -- 5.
end
function Dog:makeSound() -- 6.
print('I say ' .. self.sound)
end
mrDog = Dog:new() -- 7.
mrDog:makeSound() -- 'I say woof' -- 8.
-- 1. Dog похоже на класс, но на самом деле это таблица.
-- 2. "function tablename:fn(...)" - то же самое, что и
-- "function tablename.fn(self, ...)", просто : добавляет первый аргумент
-- перед собой. См. пункты 7 и 8, чтобы понять, как self получает значение.
-- 3. newObj - это экземпляр класса Dog.
-- 4. "self" - экземпляр класса. Зачастую self = Dog, но с помощью наследования
-- это можно изменить. newObj получит свои функции, когда мы установим
-- метатаблицу для newObj и __index для self на саму себя.
-- 5. Напоминание: setmetatable возвращает первый аргумент.
-- 6. : работает, как в пункте 2, но в этот раз мы ожидаем,
-- что self будет экземпляром, а не классом.
-- 7. То же самое, что и Dog.new(Dog), поэтому self = Dog в new().
-- 8. То же самое, что mrDog.makeSound(mrDog); self = mrDog.
--------------------------------------------------------------------------------
-- Пример наследования:
LoudDog = Dog:new() -- 1.
function LoudDog:makeSound()
local s = self.sound .. ' ' -- 2.
print(s .. s .. s)
end
seymour = LoudDog:new() -- 3.
seymour:makeSound() -- 'woof woof woof' -- 4.
--------------------------------------------------------------------------------
-- 1. LoudDog получит методы и переменные класса Dog.
-- 2. В self будет ключ 'sound' из new(), см. пункт 3.
-- 3. То же самое, что и "LoudDog.new(LoudDog)", конвертированное
-- в "Dog.new(LoudDog)", поскольку в LoudDog нет ключа 'new',
-- но в его метатаблице есть "__index = Dog".
-- Результат: Метатаблицей для seymour стала LoudDog,
-- а "LoudDog.__index = Dog". Поэтому seymour.key будет равно
-- seymour.key, LoudDog.key, Dog.key, в зависимости от того,
-- какая таблица будет первой с заданным ключом.
-- 4. Ключ 'makeSound' находится в LoudDog;
-- то же самое, что и "LoudDog.makeSound(seymour)".
-- При необходимости функция new() в подклассе
-- может быть похожа на аналог в базовом классе.
function LoudDog:new()
local newObj = {}
-- установить newObj
self.__index = self
return setmetatable(newObj, self)
end
--------------------------------------------------------------------------------
-- 4. Модули.
--------------------------------------------------------------------------------
--[[ Я закомментировал этот раздел, чтобы остальная часть скрипта осталась
-- работоспособной.
```
```lua
-- Предположим, файл mod.lua будет выглядеть так:
local M = {}
local function sayMyName()
print('Hrunkner')
end
function M.sayHello()
print('Привет, ')
sayMyName()
end
return M
-- Другой файл может использовать функциональность mod.lua:
local mod = require('mod') -- Запустим файл mod.lua.
-- require - стандартный способ подключения модулей.
-- require ведёт себя так: (если не кэшировано, см. ниже)
local mod = (function ()
<содержимое mod.lua>
end)()
-- Файл mod.lua воспринимается, как тело функции, поэтому
-- все локальные переменные и функции внутри него не видны за его пределами.
-- Это работает, так как здесь mod = M в mod.lua:
mod.sayHello() -- Выведет "Привет, Hrunkner".
-- Это будет ошибочным; sayMyName доступна только в mod.lua:
mod.sayMyName() -- ошибка
-- Значения, возвращаемые require, кэшируются,
-- поэтому содержимое файла выполняется только 1 раз,
-- даже если он подключается с помощью require много раз.
-- Предположим, mod2.lua содержит "print('Hi!')".
local a = require('mod2') -- Выведет "Hi!"
local b = require('mod2') -- Ничего не выведет; a=b.
-- dofile, в отличии от require, работает без кэширования:
dofile('mod2') --> Hi!
dofile('mod2') --> Hi! (запустится снова)
-- loadfile загружает файл, но не запускает его.
f = loadfile('mod2') -- Вызов f() запустит содержимое mod2.lua.
-- loadstring - это loadfile для строк.
g = loadstring('print(343)') -- Вернет функцию.
g() -- Напишет 343.
--]]
```
## Примечание (от автора)
Мне было интересно изучить Lua, чтобы делать игры при помощи [игрового движка LÖVE](http://love2d.org/).
Я начинал с [BlackBulletIV's Lua for programmers](http://nova-fusion.com/2012/08/27/lua-for-programmers-part-1/).
Затем я прочитал официальную [Документацию по Lua](http://www.lua.org/pil/contents.html).
Также может быть полезной [Краткая справка по Lua](http://lua-users.org/files/wiki_insecure/users/thomasl/luarefv51.pdf) на lua-users.org.
Ещё из основных тем не охвачены стандартные библиотеки:
* [библиотека `string`](http://lua-users.org/wiki/StringLibraryTutorial)
* [библиотека `table`](http://lua-users.org/wiki/TableLibraryTutorial)
* [библиотека `math`](http://lua-users.org/wiki/MathLibraryTutorial)
* [библиотека `io`](http://lua-users.org/wiki/IoLibraryTutorial)
* [библиотека `os`](http://lua-users.org/wiki/OsLibraryTutorial)
Кстати, весь файл написан на Lua; сохраните его как learn.lua и запустите при помощи `lua learn.lua`
Изначально эта статья была написана для tylerneylon.com.
Также она доступна как [GitHub gist](https://gist.github.com/tylerneylon/5853042). Удачи с Lua!