Учебник по Lua для программистов

Материал из ТХАБ.РФ
Перейти к: навигация, поиск

Исправленная версия статьи: Lua за 60 минут

Исправлены

  • сообщения переведены на русский
  • исправлены ошибки в примерах

Lua? Что это?

Lua — простой встраиваемый язык (его можно интегрировать с вашими программами, написанными на других языках), легкий и понятный, с одним типом данных, с однообразным синтаксисом. Идеальный язык для изучения.

Зачем?

Lua может вам пригодится:

  • если вы геймер (плагины для World of Warcraft и множества других игр)
  • если вы пишете игры (очень часто в играх движок пишут на C/C++, а AI — на Lua)
  • если вы системный программист (на Lua можно писать плагины для nmap, wireshark, nginx и других утилит)
  • если вы embedded-разработчик (Lua очень быстрый, компактный и требует очень мало ресурсов)

Что надо для того, чтобы читать дальше?

1. Научитесь программировать. Хотя бы немного. Не важно на каком языке. 2. Установите Lua. Для этого либо скачайте здесь версию 5.2 (http://www.lua.org/download.html), либо ищите ее в репозиториях для linux. Версия 5.1 тоже пойдет, но знайте, что она очень старая.

Все примеры из статьи запускайте в терминале командой наподобие «lua file.lua».

Первые впечатления

Lua — язык с динамической типизацией (переменные получают типы «на лету» в зависимости от присвоенных значений). Писать на нем можно как в императивном, так и в объектно-ориентированном или функциональном стиле (даже если вы не знаете как это — ничего страшного, продолжайте читать). Вот Hello world на Lua:

-- my first lua app: hello.lua
print "Привет мир!";
print("Пока мир.")

Что уже можно сказать о языке:

  • однострочные комментарии начинаются с двух дефисов "--"
  • скобки и точки-с-запятыми можно не писать

Операторы языка

Набор условных операторов и циклов довольно типичен:

-- условные операторы (ветки else может не быть)
if a == 0 then
  print("a is zero")
else
  print("a is not zero")
end

-- сокращенная форма if/elseif/end (вместо switch/case)
if a == 0 then
  print("zero")
elseif a == 1 then
  print("one")
elseif a == 2 then
  print("two")
else
  print("other")
end

-- цикл со счетчиком
for i = 1, 10 do
  print(i)
end

-- цикл с предусловием
b = 5
while b > 0 do
  b = b - 1
end

-- цикл с постусловием
repeat
  b = b + 1
until b >= 5

ПОДУМАЙТЕ: что может означать цикл "for i = 1, 10, 2 do ... end"?

В выражениях можно использовать такие вот операторы над переменными:

  • присваивание: x = 0
  • арифметические: +, -, *, /, % (остаток от деления), ^ (возведение в степень)
  • логические: and, or, not
  • сравнение: >, <, ==, <=, >=, ~= (не-равно, да-да, вместо привычного «!=»)
  • конкатенация строк (оператор «..»), напр.: s1=»hello»; s2=»world»; s3=s1..s2
  • длина/размер (оператор #): s=»hello»; a = #s (‘a’ будет равно 5).
  • получение элемента по индексу, напр.: s[2]

Битовых операций в языке долгое время не было, но в версии 5.2 появилась библиотека bit32, которая их реализует (как функции, не как операторы).

Типы данных

Я вам соврал, когда сказал что у языка один тип данных. Их у него много (как и у каждого серьезного языка):

  • nil (ровным счетом ничего)
  • булевы числа (true/false)
  • числа (numbers) — без деления на целые/вещественные. Просто числа.
  • строки — кстати, они очень похожи на строки в паскале
  • функции — да, переменная может быть типа «функция»
  • поток (thread)
  • произвольные данные (userdata)
  • таблица (table)

Если с первыми типами все понятно, то что же такое userdata? Вспомним о том, что Lua — язык встраиваемый, и обычно тесно работает с компонентами программ, написанными на других языках. Так вот, эти «чужие» компоненты могут создавать данные под свои нужды и хранить эти данные вместе с lua-объектами. Так вот, userdata — и есть подводная часть айсберга, которая с точки зрения языка lua не нужна, но и просто не обращать внимания на нее мы не можем.

А теперь самое важное в языке — таблицы.

Таблицы

Я вам снова соврал, когда сказал, что у языка 8 типов данных. Можете считать что он один: всё — это таблицы (это, кстати, тоже неправда). Таблица — это очень изящная структура данных, она сочетает в себе свойства массива, хэш-таблицы («ключ»-«значение»), структуры, объекта.


-- Итак, вот пример таблицы как массива:
a = {1, 2, 3} -- массив из 3-х элементов
print(a[2]) -- выведет "2", потому что индесы считаются с единицы
-- А таблица в виде разреженного массива (у которого есть не все элементы)
a = {} -- пустая таблица
a[1] = 1
a[3] = 5

ПОДУМАЙТЕ: чему равно a[2] в случае разреженного массива?

В примере выше таблица ведет себя как массив, но на самом деле — у нас ведь есть ключи (индексы) и значения (элементы массива). И при этом ключами могут быть какие угодно типы, не только числа:

a = {}
a["hello"] = true
a["world"] = false
a[true] = 1
-- или так:
a = {
  hello = 123,
  world = 456
}
print(a["hello"))
print(a.hello) -- то же самое, что и a["hello"], хотя выглядит как структура с полями

Кстати, раз уж у таблицы есть ключи и значения, то можно в цикле перебрать все ключи и соответствующие им значения:

t = {
  a = 3,
  b = 4
}
for key, value in pairs(t) do
  print(key, value) -- выведет "a 3", потом "b 4"
end

А как же объекты? О них мы узнаем чуть позже, вначале — о функциях.

Функции

Вот пример обычной функции.

function add(a, b)
  return a + b
end

print(add(5, 3)) -- напечатает "8"

Функции языка позволяют принимать несколько аргументов, и возвращать несколько аргументов. Так аргументы, значения которых не указаны явно, считаются равными nil.

ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько аргументов?

function swap(a, b)
  return b, a
end

x, y = swap(x, y)
-- кстати, это можно сделать и без функции:
x, y = y, x
-- и если уж функция возвращает несколько аргументов, 
-- а они вам не нужны - игнорируйте их с помощью 
-- специальной переменной-подчеркивания "_"
a, _, _, d = some_function()

Функции могут принимать переменное количество аргументов:

-- в прототипе переменное число аргументов записывается как троеточие
function sum(...) 
  s = 0
  for _, n in pairs(arg) do -- в функции обращаются к ним, как к таблице "arg"
     s = s + n
  end
  return a
end

print (sum(1, 2, 3)) -- вернет 10
print (sum(1, 2, 3, 4)) -- вернет 14

Поскольку функции — это полноценный тип данных, то можно создавать переменные-функции, а можно передавать функции как аргументы других функций

a = function(x) -- функция, умножающая X на 2
        return x * 2
    end 
-- краткая форма записи
b = function(x) return x + 1 end -- функция, увеличивающая X на 1

function apply(table, f)
  result = {} -- создание пустой таблицы
  for k, v in pairs(table) do
    result[k] = f(v) -- заменяем элемент на какую-то функцию от этого элемента
    print (result[k]) -- печать результата применения функции f к элементу массива t
  end
end

-- ПОДУМАЙТЕ: что вернут вызовы
t = {1, 3, 5}
print (apply(t, a))
print (apply(t, b))

Объекты = таблицы + функции

Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное.

Перейдем к примерам. Есть у нас объект, скажем, лампочка. Она умеет гореть и не гореть. Ну а действия с ней можно сделать два — включить и выключить:

-- таблица ключ=значение
lamp = {
  on = false
}

function turn_on(l)
  l.on = true
end
-- или в кратком виде
function turn_off(l) l.on = false  end

-- это просто функции для работы со структурой
turn_on(lamp)
print (lamp.on) -- вывод состояния переменной lamp.on  
turn_off(lamp)
print (lamp.on) -- вывод состояния переменной lamp.on  

А если лампочку сделать объектом, и функции turn_off и turn_on сделать полями объекта, то получится:

lamp = {
  on = false,
  turn_on = function(l)
                  l.on = true
            end,
  turn_off = function(l) l.on = false end 
}
lamp.turn_on(lamp)
print (lamp.on) -- вывод состояния переменной lamp.on  
lamp.turn_off(lamp)
print (lamp.on) -- вывод состояния переменной lamp.on  

Мы вынуждены передавать сам объект лампочки в качестве первого аргумента, потому что иначе наша функция не узнает с какой именно лампочкой надо работать, чтобы сменить состояние on/off. Но чтобы не быть многословными, в Lua есть сокращенная запись, которую обычно и используют — lamp:turn_on(). Итого, мы уже знаем несколько таких упрощений синтаксиса:

lamp:turn_on() -- самая общепринятая запись
lamp.turn_on(lamp) -- то с точки зрения синтаксиса это тоже правильно
lamp["turn_on"](lamp) -- и это

Продолжая говорить о сокращениях, функции можно описывать не только явно, как поля структуры, но и в более удобной форме:

lamp = {
 on = false
}
-- через точку, тогда аргумент надо указывать
function lamp.turn_on(l) l.on = true end
-- через двоеточкие, тогда аргумент неявно задается сам, как переменная "self"
-- "self" - и есть та лампочка, для которой вызвали метод
function lamp:turn_off() self.on = false end

Интересно?

Специальные функции

Некоторые имена функций таблиц (методов) зарезервированы, и они несут особый смысл:

  • __add(a, b), __sub(a, b), __div(a, b), __mul(a, b), __mod(a, b), __pow(a, b) — вызываются, когда выполняются арифметические операции над таблицей
  • __unm(a) — унарная операция «минус» (когда пишут что-то типа «x = -x»)
  • __lt(a, b), __le(a, b), __eq(a, b) — вычисляют результат сравнения (<, <=, ==)
  • __len(a) — вызывается, когда делается "#a"
  • __concat(a, b) — вызывается при "a..b"
  • __call(a, …) — вызывается при "a()". Переменные аргументы — это аргументы при вызове
  • __index(a, i) — обращение к a[i], при условии, что такого элемента не существует
  • __newindex(a, i, v) — создание "a[i] = v"
  • __gc(a) — когда объект удаляется при сборке мусора

Подменяя эти методы, можно перегружать операторы и использовать синтаксис языка для своих целей. Главное не переусердствовать.

см. также

Ссылки