Учебник по LuaRu — различия между версиями
Админ (обсуждение | вклад) (Новая страница: «Исправленная версия статьи: [https://zserge.wordpress.com/2012/02/23/lua-за-60-минут/ Lua за 60 минут] Исправленн…») |
(→Исправлены) |
||
(не показано 18 промежуточных версий 3 участников) | |||
Строка 3: | Строка 3: | ||
=== Исправлены === | === Исправлены === | ||
− | * | + | * Сообщения переведены на русский язык |
− | * | + | * Исправлены ошибки в примерах |
==== Для LuaRu ==== | ==== Для LuaRu ==== | ||
− | * Переводятся (В | + | * Переводятся (В РАБОТЕ!!!) на LuaRu указания |
+ | * И насчёт битовых действий в учебнике LuaRu устаревшие сведения, в версии 5.3 добавлена полноценная их поддержка. | ||
== Lua? Что это? == | == Lua? Что это? == | ||
− | Lua — простой встраиваемый язык (его можно | + | Lua — простой встраиваемый язык (его можно встраивать в ваши программы, написанные на иных языках), лёгкий и понятный, с одним видом данных, с однообразным синтаксисом. Совершенный язык для изучения. |
== Зачем? == | == Зачем? == | ||
Строка 16: | Строка 17: | ||
Lua может вам пригодится: | Lua может вам пригодится: | ||
− | * если вы | + | * если вы игрок (дополнения для World of Warcraft и множества других игр) |
− | * если вы пишете игры (очень часто в играх движок пишут на C/C++, а | + | * если вы пишете игры (очень часто в играх движок пишут на C/C++, а ИИ — на Lua) |
− | * если вы системный программист (на Lua можно писать | + | * если вы системный программист (на Lua можно писать дополнения для nmap, wireshark, nginx и других оснасток) |
− | * если вы | + | * если вы разработчик-встройщик (Lua очень быстрый, маленький и требует очень мало ресурсов) |
== Что надо для того, чтобы читать дальше? == | == Что надо для того, чтобы читать дальше? == | ||
Строка 30: | Строка 31: | ||
== Первые впечатления == | == Первые впечатления == | ||
− | Lua — язык с динамической типизацией (переменные получают | + | Lua — язык с динамической типизацией (переменные получают виды «на лету», в зависимости от присвоенных значений). Писать на нём можно как в императивном, так и в объектно-ориентированном или функциональном стиле (даже если вы не знаете как это — ничего страшного, продолжайте читать). Вот "Привет, мир!" на Lua: |
− | -- | + | -- моя первая программа на luaRu : приветМир.lua |
− | print "Привет мир!"; | + | печать=print |
− | + | print "Привет, мир!"; | |
+ | печать("Пока, мир.") | ||
Что уже можно сказать о языке: | Что уже можно сказать о языке: | ||
− | * однострочные комментарии начинаются с двух | + | * однострочные комментарии начинаются с двух чёрточек "--" |
* скобки и точки-с-запятыми можно не писать | * скобки и точки-с-запятыми можно не писать | ||
− | == | + | == Указания языка == |
− | Набор условных | + | Набор условных указаний и повторов довольно обычен: |
− | -- условные | + | -- условные указания (ветки '''иначе''' может не быть) |
− | + | если ж == 0 то | |
− | + | печать("ж это ноль") | |
− | + | иначе | |
− | + | печать("ж это не ноль") | |
− | + | всё | |
− | -- | + | -- сокращённый вид если/то/иначе/иначе_если/всё (вместо выбрать/из (switch/case)) |
− | + | если a == 0 то | |
− | + | печать("ноль") | |
− | + | иначе_если a == 1 то | |
− | + | печать("один") | |
− | + | иначе_если a == 2 то | |
− | + | печать("два") | |
− | + | иначе | |
− | + | печать("другое") | |
− | + | всё | |
− | -- | + | -- повтор со счётчиком |
− | + | для ж = 1, 10 делать | |
− | + | печать(ж) | |
− | + | всё | |
− | -- | + | -- повтор с предусловием |
− | + | ж = 5 | |
− | + | пока ж > 0 делать | |
− | + | ж = ж - 1 | |
− | + | всё | |
− | -- | + | -- повтор с послеусловием |
− | + | повторять | |
− | + | ж = ж + 1 | |
− | + | пока_не ж >= 5 | |
− | ПОДУМАЙТЕ: что может означать | + | ПОДУМАЙТЕ: что может означать повтор "для ж = 1, 10, 2 делать ... конец"? |
− | В выражениях можно использовать такие вот | + | В выражениях можно использовать такие вот действия над переменными: |
* присваивание: x = 0 | * присваивание: x = 0 | ||
− | * | + | * счётные: +, -, *, /, % (остаток от деления), ^ (возведение в степень) |
* логические: and, or, not | * логические: and, or, not | ||
− | * сравнение: >, <, ==, <=, >=, ~= ( | + | * сравнение: >, <, ==, <=, >=, ~= (неравно, да-да, вместо привычного «!=») |
− | * | + | * слияние строк (оператор «..»), напр.: s1=»hello»; s2=»world»; s3=s1..s2 |
* длина/размер (оператор #): s=»hello»; a = #s (‘a’ будет равно 5). | * длина/размер (оператор #): s=»hello»; a = #s (‘a’ будет равно 5). | ||
− | * получение | + | * получение значения по номеру, напр.: s[2] |
− | Битовых | + | Битовых действий в языке долгое время не было, но в версии 5.2 появилась библиотека bit32, которая их воплощает (как функции, а не как действия). |
− | == | + | == Виды данных == |
− | Я вам соврал, когда сказал что у языка один | + | Я вам соврал, когда сказал, что у языка один вид данных. Их у него много (как и у каждого серьёзного языка): |
− | * nil (ровным | + | * пусто (nil) (ровным счётом ''ничто'') |
− | * | + | * логические значения (да/нет, правда/ложь) (true/false) |
* числа (numbers) — без деления на целые/вещественные. Просто числа. | * числа (numbers) — без деления на целые/вещественные. Просто числа. | ||
* строки — кстати, они очень похожи на строки в паскале | * строки — кстати, они очень похожи на строки в паскале | ||
− | * функции — да, переменная может быть | + | * функции — да, переменная может быть вида «функция» |
* поток (thread) | * поток (thread) | ||
* произвольные данные (userdata) | * произвольные данные (userdata) | ||
* таблица (table) | * таблица (table) | ||
− | Если с первыми | + | Если с первыми видами все понятно, то что же такое userdata? Вспомним о том, что Lua — язык встраиваемый, и обычно тесно работает с частями программ, написанными на других языках. Так вот, эти «чужие» части могут создавать данные под свои нужды и хранить эти данные вместе с lua-объектами. Отсюда userdata — и есть "подводная часть айсберга", которая с точки зрения языка lua не нужна, хотя не обращать внимания на неё мы не можем. |
А теперь самое важное в языке — таблицы. | А теперь самое важное в языке — таблицы. | ||
Строка 112: | Строка 114: | ||
== Таблицы == | == Таблицы == | ||
− | Я вам снова соврал, когда сказал, что у языка 8 | + | Я вам снова соврал, когда сказал, что у языка 8 видов данных. Можете считать, что он один: всё — это таблицы (это, кстати, тоже неправда). Таблица — это очень изящный строй данных, он сочетает в себе свойства ряда, хэш-таблицы («ключ»-«значение»), структуры, объекта. |
− | -- Итак, вот пример таблицы как | + | -- Итак, вот пример таблицы как ряда: |
− | + | ж = {1, 2, 3} -- ряд из 3-х значений | |
− | + | печать(ж[2]) -- выведет "2", потому что номера считаются с единицы | |
− | -- А таблица в виде разреженного | + | -- А таблица в виде разреженного ряда(у которого есть не все элементы) |
− | + | ж = {} -- пустая таблица | |
− | + | ж[1] = 1 | |
− | + | ж[3] = 5 | |
− | ПОДУМАЙТЕ: чему равно | + | ПОДУМАЙТЕ: чему равно ж[2] в случае разреженного ряда? |
− | В примере выше таблица | + | В примере выше таблица ведёт себя как ряд, но на самом деле — у нас ведь есть ключи (номера) и значения (элементы ряда). И при этом ключами могут быть какие угодно виды, не только числа: |
− | + | ж = {} | |
− | + | ж["привет"] = правда | |
− | + | ж["мир"] = ложь | |
− | + | ж[правда] = 1 | |
-- или так: | -- или так: | ||
− | + | ж = { | |
− | + | привет = 123, | |
− | + | мир = 456 | |
} | } | ||
− | + | печать(ж["привет")) | |
− | + | печать(ж.привет) -- то же самое, что и ж["привет"], хотя выглядит как структура с полями | |
− | Кстати, раз уж у таблицы есть ключи и значения, то можно в | + | Кстати, раз уж у таблицы есть ключи и значения, то можно в повторе перебрать все ключи и соответствующие им значения: |
− | + | таблица = { | |
− | + | а = 3, | |
− | + | ж = 4 | |
} | } | ||
− | + | для ключ, значение в pairs(таблица) делать | |
− | + | печать(ключ, значение) -- выведет "а 3", потом "ж 4" | |
− | + | конец | |
А как же объекты? О них мы узнаем чуть позже, вначале — о функциях. | А как же объекты? О них мы узнаем чуть позже, вначале — о функциях. | ||
Строка 155: | Строка 157: | ||
Вот пример обычной функции. | Вот пример обычной функции. | ||
− | + | функция сложить(ф, ж) | |
− | + | вернуть ф + ж -- '''вариант''' результат ф + ж | |
− | + | конец | |
− | + | печать(сложить(5, 3)) -- напечатает "8" | |
− | Функции языка позволяют принимать несколько | + | Функции языка позволяют принимать несколько доводов, и возвращать несколько ответов. Так доводы, значения которых не указаны явно, считаются равными '''пусто''' (nil). |
− | ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько | + | ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько ответов? |
− | + | функция поменять_местами(ф, ж) | |
− | + | результат ж, ф | |
− | + | конец | |
− | x, y = | + | x, y = поменять_местами(x, y) |
-- кстати, это можно сделать и без функции: | -- кстати, это можно сделать и без функции: | ||
x, y = y, x | x, y = y, x | ||
Строка 175: | Строка 177: | ||
-- а они вам не нужны - игнорируйте их с помощью | -- а они вам не нужны - игнорируйте их с помощью | ||
-- специальной переменной-подчеркивания "_" | -- специальной переменной-подчеркивания "_" | ||
− | a, _, _, d = | + | a, _, _, d = какаято_функция() |
− | Функции могут принимать переменное количество | + | Функции могут принимать переменное количество доводов: |
− | -- в прототипе переменное число | + | -- в прототипе переменное число доводов записывается как троеточие |
− | + | функция сумма(...) | |
− | + | ж = 0 | |
− | + | для _, ф в pairs(arg) делать -- в функции обращаются к ним, как к таблице "arg" | |
− | + | ж = ж + ф | |
− | + | конец | |
− | + | результат ж | |
− | + | конец | |
− | + | печать (сумма(1, 2, 3)) -- вернет 10 | |
− | + | печать (сумма(1, 2, 3, 4)) -- вернет 14 | |
− | Поскольку функции — это полноценный | + | Поскольку функции — это полноценный вид данных, то можно создавать переменные-функции, а можно передавать функции как доводы других функций |
− | + | ф = функция (ж) -- функция, умножающая Ж на 2 | |
− | + | результат ж * 2 | |
− | + | конец | |
-- краткая форма записи | -- краткая форма записи | ||
− | + | ф = функция(ж) результат ж + 1 конец -- функция, увеличивающая Ж на 1 | |
− | + | функция таб_функ(таблица, функ) | |
− | + | табл = {} -- создание пустой таблицы | |
− | + | для ключ, значение в pairs(таблица) делать | |
− | + | табл[ключ] = функ(значение) -- заменяем элемент на какую-то функцию от этого элемента | |
− | + | печать (табл[ключ]) -- печать результата применения функции '''функ''' к элементу массива t | |
− | + | конец | |
− | + | конец | |
-- ПОДУМАЙТЕ: что вернут вызовы | -- ПОДУМАЙТЕ: что вернут вызовы | ||
− | + | таб = {1, 3, 5} | |
− | + | печать(таб_функ(таб, а)) | |
− | + | печать(таб_функ(таб, ж)) | |
== Объекты = таблицы + функции == | == Объекты = таблицы + функции == | ||
Строка 216: | Строка 218: | ||
Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное. | Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное. | ||
− | Перейдем к примерам. Есть у нас объект, скажем, | + | Перейдем к примерам. Есть у нас объект, скажем, светильник. Он умеет гореть и не гореть. Ну а действия с ним можно проделать два — включить и выключить: |
-- таблица ключ=значение | -- таблица ключ=значение | ||
− | + | светильник = { | |
− | + | вкл = нет | |
} | } | ||
− | + | функция включить(с) | |
− | + | с.вкл = да | |
− | + | конец | |
-- или в кратком виде | -- или в кратком виде | ||
− | + | функция выключить(с) с.вкл = нет конец | |
-- это просто функции для работы со структурой | -- это просто функции для работы со структурой | ||
− | + | включить(светильник) | |
− | + | печать(светильник.вкл) -- вывод состояния переменной светильник.вкл | |
− | + | выключить(светильник) | |
− | + | печать(светильник.вкл) -- вывод состояния переменной светильник.вкл | |
− | А если | + | А если светильник сделать объектом, и функции '''выключить''' и '''включить''' сделать полями объекта, то получится: |
− | + | светильник = { | |
− | + | вкл = нет, | |
− | + | включить = функция(с) | |
− | + | с.вкл = да | |
− | + | конец, | |
− | + | выключить = функция(с) с.вкл = нет конец | |
} | } | ||
− | + | светильник.включить(светильник) | |
− | + | печать (светильник.вкл) -- вывод состояния переменной светильник.вкл | |
− | + | светильник.выключить(светильник) | |
− | + | печать (светильник.вкл) -- вывод состояния переменной светильник.вкл | |
− | Мы вынуждены передавать сам объект | + | Мы вынуждены передавать сам объект светильника в качестве первого довода, потому что иначе наша функция не узнает с каким именно светильником надо работать, чтобы сменить состояние вкл/выкл. Но чтобы не быть многословными, в Lua есть сокращенная запись, которую обычно и используют — светильник:включить(). Итого, мы уже знаем несколько таких упрощений синтаксиса: |
− | + | светильник:включить() -- самая общепринятая запись | |
− | + | светильник.включить(светильник) -- то с точки зрения синтаксиса это тоже правильно | |
− | + | светильник["включить"](светильник) -- и это | |
Продолжая говорить о сокращениях, функции можно описывать не только явно, как поля структуры, но и в более удобной форме: | Продолжая говорить о сокращениях, функции можно описывать не только явно, как поля структуры, но и в более удобной форме: | ||
− | + | светильник = { | |
− | + | вкл = нет | |
} | } | ||
− | -- через точку, тогда | + | -- через точку, тогда довод надо указывать |
− | + | функция светильник.включить(с) с.вкл = да конец | |
− | -- через | + | -- через двоеточие, тогда аргумент неявно задается сам, как переменная "self" |
-- "self" - и есть та лампочка, для которой вызвали метод | -- "self" - и есть та лампочка, для которой вызвали метод | ||
− | + | функция светильник:отключить() self.вкл = нет конец | |
− | + | Любопытно? | |
− | === | + | === Особые функции === |
Раскрыть тему [[Lua.Специальные функции]] | Раскрыть тему [[Lua.Специальные функции]] | ||
− | Некоторые | + | Некоторые названия функций таблиц (методов) заняты, и они несут особый смысл: |
− | * __add(a, b), __sub(a, b), __div(a, b), __mul(a, b), __mod(a, b), __pow(a, b) — вызываются, когда выполняются | + | * __add(a, b), __sub(a, b), __div(a, b), __mul(a, b), __mod(a, b), __pow(a, b) — вызываются, когда выполняются счётные действия над таблицей |
− | * __unm(a) — | + | * __unm(a) — единичное действие «минус» (когда пишут что-то типа «x = -x») |
* __lt(a, b), __le(a, b), __eq(a, b) — вычисляют результат сравнения (<, <=, ==) | * __lt(a, b), __le(a, b), __eq(a, b) — вычисляют результат сравнения (<, <=, ==) | ||
* __len(a) — вызывается, когда делается "#a" | * __len(a) — вызывается, когда делается "#a" | ||
* __concat(a, b) — вызывается при "a..b" | * __concat(a, b) — вызывается при "a..b" | ||
− | * __call(a, …) — вызывается при "a()". Переменные | + | * __call(a, …) — вызывается при "a()". Переменные доводы — это доводы при вызове |
− | * __index(a, i) — обращение к a[i], при условии, что такого | + | * __index(a, i) — обращение к a[i], при условии, что такого значения не существует |
* __newindex(a, i, v) — создание "a[i] = v" | * __newindex(a, i, v) — создание "a[i] = v" | ||
* __gc(a) — когда объект удаляется при сборке мусора | * __gc(a) — когда объект удаляется при сборке мусора | ||
Строка 299: | Строка 301: | ||
* __lt( self, arg ) оператор "меньше" | * __lt( self, arg ) оператор "меньше" | ||
* __le( self, arg ) оператор "меньше или равно" | * __le( self, arg ) оператор "меньше или равно" | ||
− | * __tostring( self ) Вызывается при попытке перевести объект в строковое представление (например с помощью функции tostring) | + | * __tostring( self ) Вызывается при попытке перевести объект в строковое представление (например, с помощью функции tostring) |
* __gc Вызывается для userdata-объектов при сборке мусора | * __gc Вызывается для userdata-объектов при сборке мусора | ||
− | * __metatable Если задать это поле в метатаблице, то getmetatable будет просто возвращать его значение, а setmetatable | + | * __metatable Если задать это поле в метатаблице, то getmetatable будет просто возвращать его значение, а setmetatable вернёт ошибку. |
− | Подменяя эти методы, можно перегружать | + | Подменяя эти методы, можно перегружать действители и использовать синтаксис языка для своих целей. Главное не переусердствовать. |
== Наследование == | == Наследование == | ||
− | Для тех, кто не знает ООП, наследование позволяет расширить функциональность уже существующего | + | Для тех, кто не знает ООП, наследование позволяет расширить функциональность уже существующего объекта. Например, просто светильник умеет включаться-выключаться, а усовершенствованный светильник будет ещё и яркость менять. Зачем нам переписывать методы включить/выключить, если можно их повторно использовать? |
В Lua для этого есть понятие мета-таблицы, т.е. таблицы-предка. У каждой таблицы есть одна таблица-предок, и дочерняя таблица умеет делать все, что умеет предок. | В Lua для этого есть понятие мета-таблицы, т.е. таблицы-предка. У каждой таблицы есть одна таблица-предок, и дочерняя таблица умеет делать все, что умеет предок. | ||
− | * Метатаблица - это обычная таблица в которой описываются определённые события (например сложение, вычитание, сравнивание и т.д.) | + | * Метатаблица - это обычная таблица, в которой описываются определённые события (например, сложение, вычитание, сравнивание и т.д.) |
* В качестве названия события выступает индекс таблицы, а её значение - обработчик события (метод) | * В качестве названия события выступает индекс таблицы, а её значение - обработчик события (метод) | ||
Строка 337: | Строка 339: | ||
== Расширение функциональности == | == Расширение функциональности == | ||
− | Работает но непонятно | + | Работает, но непонятно. |
− | Родительские таблицы есть у многих | + | Родительские таблицы есть у многих видов (ну у строк и таблиц точно, у чисел и булевых чисел, и у пустоты их нет). Допустим, мы хотим складывать все строки с помощью оператора "+", а не "..". Для этого нужно подменить функцию «+» (__add) для родительской таблицы всех строк: |
s = getmetatable("") -- получили родительскую таблицу строки | s = getmetatable("") -- получили родительскую таблицу строки | ||
Строка 349: | Строка 351: | ||
print(a + b) -- напишет "ПриветМир!" | print(a + b) -- напишет "ПриветМир!" | ||
− | Собственно, мы | + | Собственно, мы ещё можем заменить функцию print с помощью «print = myfunction», да и много других приёмчиков можно сделать. |
== Области видимости == | == Области видимости == | ||
− | Не понятно. | + | Не понятно. Нужны примеры |
− | Переменные бывают глобальные и | + | Переменные бывают глобальные и местные. При создании все переменные в Lua являются глобальными. |
ПОДУМАЙТЕ: почему? | ПОДУМАЙТЕ: почему? | ||
− | Для указания | + | Для указания местной области видимости пишут ключевое слово local: |
local x | local x | ||
Строка 368: | Строка 370: | ||
== Обработка ошибок == | == Обработка ошибок == | ||
− | Часто, если возникают ошибки, надо прекратить выполнение | + | Часто, если возникают ошибки, надо прекратить выполнение определённой функции. Можно, конечно, сделать множество проверок и вызывать «return», если что-то пошло не так. Но это увеличит объём кода. В Lua используется что-то наподобие исключений. |
− | Ошибки порождаются с помощью функции error(x). В качестве | + | Ошибки порождаются с помощью функции error(x). В качестве довода можно передать всё, что угодно (то, что имеет отношение к ошибке — строковое описание, числовой код, объект, с которым произошла ошибка и т.д.) |
− | Обычно после этой функции вся программа | + | Обычно после этой функции вся программа сбойно завершается. А это нужно далеко не всегда. Если вы вызываете функцию, которая может создать ошибку (или её дочерние функции могут создать ошибку), то вызывайте её безопасно, с помощью pcall(): |
function f(x, y) | function f(x, y) | ||
Строка 387: | Строка 389: | ||
end | end | ||
− | == | + | == Уставные библиотеки == |
− | + | Уставных библиотек мало, зато это позволяет запускать Lua где угодно. Подробнее можно получить их список здесь — http://www.lua.org/manual/5.2/manual.html | |
− | + | Неуставных библиотек много, их можно найти на LuaForge, LuaRocks и в других хранилищах. | |
== Между Lua и не-Lua == | == Между Lua и не-Lua == | ||
− | ВНИМАНИЕ: эту часть | + | ВНИМАНИЕ: эту часть советуем читать людям со знанием языка C. |
− | А если нам недостаточно функциональности стандартных библиотек? Если у нас есть наша программа на C, а мы хотим вызывать | + | А если нам недостаточно функциональности стандартных библиотек? Если у нас есть наша программа на C, а мы хотим вызывать её функции из Lua? Для этого есть очень простой механизм. |
Допустим, мы хотим создать свою функцию, которая возвращает случайное число (в Lua есть math.random(), но мы хотим поучиться). Нам придется написать вот такой код на C: | Допустим, мы хотим создать свою функцию, которая возвращает случайное число (в Lua есть math.random(), но мы хотим поучиться). Нам придется написать вот такой код на C: | ||
Строка 432: | Строка 434: | ||
} | } | ||
− | Т.е. Lua предоставляет нам функции для работы с | + | Т.е. Lua предоставляет нам функции для работы с видами данных, для получения доводов функций и возврата ответов. Функций очень мало, и они довольно простые. Теперь мы собираем нашу библиотеку как динамическую, и можем использовать функцию rand(): |
random = require("librand") -- загружаем библиотеку | random = require("librand") -- загружаем библиотеку | ||
Строка 454: | Строка 456: | ||
Все. | Все. | ||
− | Вы теперь можете писать на Lua. Если вы узнаете | + | Вы теперь можете писать на Lua. Если вы узнаете любопытные вещи про Lua, которые можно было бы отразить в статье — пишите! |
== см. также == | == см. также == |
Текущая версия на 20:47, 9 июня 2018
Исправленная версия статьи: Lua за 60 минут Исправленная версия (В процессе перевода на LuaRu) Учебник по Lua для программистов
Содержание
- 1 Исправлены
- 2 Lua? Что это?
- 3 Зачем?
- 4 Что надо для того, чтобы читать дальше?
- 5 Первые впечатления
- 6 Указания языка
- 7 Виды данных
- 8 Таблицы
- 9 Функции
- 10 Объекты = таблицы + функции
- 11 Наследование
- 12 Расширение функциональности
- 13 Области видимости
- 14 Обработка ошибок
- 15 Уставные библиотеки
- 16 Между Lua и не-Lua
- 17 см. также
- 18 Ссылки
Исправлены
- Сообщения переведены на русский язык
- Исправлены ошибки в примерах
Для LuaRu
- Переводятся (В РАБОТЕ!!!) на LuaRu указания
- И насчёт битовых действий в учебнике LuaRu устаревшие сведения, в версии 5.3 добавлена полноценная их поддержка.
Lua? Что это?
Lua — простой встраиваемый язык (его можно встраивать в ваши программы, написанные на иных языках), лёгкий и понятный, с одним видом данных, с однообразным синтаксисом. Совершенный язык для изучения.
Зачем?
Lua может вам пригодится:
- если вы игрок (дополнения для World of Warcraft и множества других игр)
- если вы пишете игры (очень часто в играх движок пишут на C/C++, а ИИ — на Lua)
- если вы системный программист (на Lua можно писать дополнения для nmap, wireshark, nginx и других оснасток)
- если вы разработчик-встройщик (Lua очень быстрый, маленький и требует очень мало ресурсов)
Что надо для того, чтобы читать дальше?
1. Научитесь программировать. Хотя бы немного. Не важно на каком языке. 2. Установите Lua. Для этого либо скачайте здесь версию 5.3 (http://www.lua.org/download.html), либо ищите ее в репозиториях для linux.
Все примеры из статьи запускайте в терминале командой наподобие «lua file.lua».
Первые впечатления
Lua — язык с динамической типизацией (переменные получают виды «на лету», в зависимости от присвоенных значений). Писать на нём можно как в императивном, так и в объектно-ориентированном или функциональном стиле (даже если вы не знаете как это — ничего страшного, продолжайте читать). Вот "Привет, мир!" на Lua:
-- моя первая программа на luaRu : приветМир.lua печать=print print "Привет, мир!"; печать("Пока, мир.")
Что уже можно сказать о языке:
- однострочные комментарии начинаются с двух чёрточек "--"
- скобки и точки-с-запятыми можно не писать
Указания языка
Набор условных указаний и повторов довольно обычен:
-- условные указания (ветки иначе может не быть) если ж == 0 то печать("ж это ноль") иначе печать("ж это не ноль") всё -- сокращённый вид если/то/иначе/иначе_если/всё (вместо выбрать/из (switch/case)) если a == 0 то печать("ноль") иначе_если a == 1 то печать("один") иначе_если a == 2 то печать("два") иначе печать("другое") всё -- повтор со счётчиком для ж = 1, 10 делать печать(ж) всё -- повтор с предусловием ж = 5 пока ж > 0 делать ж = ж - 1 всё -- повтор с послеусловием повторять ж = ж + 1 пока_не ж >= 5
ПОДУМАЙТЕ: что может означать повтор "для ж = 1, 10, 2 делать ... конец"?
В выражениях можно использовать такие вот действия над переменными:
- присваивание: 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 видов данных. Можете считать, что он один: всё — это таблицы (это, кстати, тоже неправда). Таблица — это очень изящный строй данных, он сочетает в себе свойства ряда, хэш-таблицы («ключ»-«значение»), структуры, объекта.
-- Итак, вот пример таблицы как ряда: ж = {1, 2, 3} -- ряд из 3-х значений печать(ж[2]) -- выведет "2", потому что номера считаются с единицы -- А таблица в виде разреженного ряда(у которого есть не все элементы) ж = {} -- пустая таблица ж[1] = 1 ж[3] = 5
ПОДУМАЙТЕ: чему равно ж[2] в случае разреженного ряда?
В примере выше таблица ведёт себя как ряд, но на самом деле — у нас ведь есть ключи (номера) и значения (элементы ряда). И при этом ключами могут быть какие угодно виды, не только числа:
ж = {} ж["привет"] = правда ж["мир"] = ложь ж[правда] = 1 -- или так: ж = { привет = 123, мир = 456 } печать(ж["привет")) печать(ж.привет) -- то же самое, что и ж["привет"], хотя выглядит как структура с полями
Кстати, раз уж у таблицы есть ключи и значения, то можно в повторе перебрать все ключи и соответствующие им значения:
таблица = { а = 3, ж = 4 } для ключ, значение в pairs(таблица) делать печать(ключ, значение) -- выведет "а 3", потом "ж 4" конец
А как же объекты? О них мы узнаем чуть позже, вначале — о функциях.
Функции
Вот пример обычной функции.
функция сложить(ф, ж) вернуть ф + ж -- вариант результат ф + ж конец печать(сложить(5, 3)) -- напечатает "8"
Функции языка позволяют принимать несколько доводов, и возвращать несколько ответов. Так доводы, значения которых не указаны явно, считаются равными пусто (nil).
ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько ответов?
функция поменять_местами(ф, ж) результат ж, ф конец x, y = поменять_местами(x, y) -- кстати, это можно сделать и без функции: x, y = y, x -- и если уж функция возвращает несколько аргументов, -- а они вам не нужны - игнорируйте их с помощью -- специальной переменной-подчеркивания "_" a, _, _, d = какаято_функция()
Функции могут принимать переменное количество доводов:
-- в прототипе переменное число доводов записывается как троеточие функция сумма(...) ж = 0 для _, ф в pairs(arg) делать -- в функции обращаются к ним, как к таблице "arg" ж = ж + ф конец результат ж конец печать (сумма(1, 2, 3)) -- вернет 10 печать (сумма(1, 2, 3, 4)) -- вернет 14
Поскольку функции — это полноценный вид данных, то можно создавать переменные-функции, а можно передавать функции как доводы других функций
ф = функция (ж) -- функция, умножающая Ж на 2 результат ж * 2 конец -- краткая форма записи ф = функция(ж) результат ж + 1 конец -- функция, увеличивающая Ж на 1 функция таб_функ(таблица, функ) табл = {} -- создание пустой таблицы для ключ, значение в pairs(таблица) делать табл[ключ] = функ(значение) -- заменяем элемент на какую-то функцию от этого элемента печать (табл[ключ]) -- печать результата применения функции функ к элементу массива t конец конец -- ПОДУМАЙТЕ: что вернут вызовы таб = {1, 3, 5} печать(таб_функ(таб, а)) печать(таб_функ(таб, ж))
Объекты = таблицы + функции
Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное.
Перейдем к примерам. Есть у нас объект, скажем, светильник. Он умеет гореть и не гореть. Ну а действия с ним можно проделать два — включить и выключить:
-- таблица ключ=значение светильник = { вкл = нет } функция включить(с) с.вкл = да конец -- или в кратком виде функция выключить(с) с.вкл = нет конец -- это просто функции для работы со структурой включить(светильник) печать(светильник.вкл) -- вывод состояния переменной светильник.вкл выключить(светильник) печать(светильник.вкл) -- вывод состояния переменной светильник.вкл
А если светильник сделать объектом, и функции выключить и включить сделать полями объекта, то получится:
светильник = { вкл = нет, включить = функция(с) с.вкл = да конец, выключить = функция(с) с.вкл = нет конец } светильник.включить(светильник) печать (светильник.вкл) -- вывод состояния переменной светильник.вкл светильник.выключить(светильник) печать (светильник.вкл) -- вывод состояния переменной светильник.вкл
Мы вынуждены передавать сам объект светильника в качестве первого довода, потому что иначе наша функция не узнает с каким именно светильником надо работать, чтобы сменить состояние вкл/выкл. Но чтобы не быть многословными, в Lua есть сокращенная запись, которую обычно и используют — светильник:включить(). Итого, мы уже знаем несколько таких упрощений синтаксиса:
светильник:включить() -- самая общепринятая запись светильник.включить(светильник) -- то с точки зрения синтаксиса это тоже правильно светильник["включить"](светильник) -- и это
Продолжая говорить о сокращениях, функции можно описывать не только явно, как поля структуры, но и в более удобной форме:
светильник = { вкл = нет } -- через точку, тогда довод надо указывать функция светильник.включить(с) с.вкл = да конец -- через двоеточие, тогда аргумент неявно задается сам, как переменная "self" -- "self" - и есть та лампочка, для которой вызвали метод функция светильник:отключить() self.вкл = нет конец
Любопытно?
Особые функции
Раскрыть тему Lua.Специальные функции
Некоторые названия функций таблиц (методов) заняты, и они несут особый смысл:
- __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) — когда объект удаляется при сборке мусора
Ниже список всех доступных событий и их описание:
- __index( self, key ) чтение по ключу
- __newindex( self, key, value ) запись по ключу
- __call( self, ... ) вызов как функции
- __add( self, arg ) сложение
- __sub( self, arg ) вычитание
- __mul( self, arg ) умножение
- __div( self, arg ) деление
- __mod( self, arg ) остаток от деления
- __pow( self, arg ) возведение в степень
- __unm( self ) унарный минус
- __concat( self, arg ) конкатенация строк
- __len( self ) длина
- __eq( self, arg ) оператор "равно"
- __lt( self, arg ) оператор "меньше"
- __le( self, arg ) оператор "меньше или равно"
- __tostring( self ) Вызывается при попытке перевести объект в строковое представление (например, с помощью функции tostring)
- __gc Вызывается для userdata-объектов при сборке мусора
- __metatable Если задать это поле в метатаблице, то getmetatable будет просто возвращать его значение, а setmetatable вернёт ошибку.
Подменяя эти методы, можно перегружать действители и использовать синтаксис языка для своих целей. Главное не переусердствовать.
Наследование
Для тех, кто не знает ООП, наследование позволяет расширить функциональность уже существующего объекта. Например, просто светильник умеет включаться-выключаться, а усовершенствованный светильник будет ещё и яркость менять. Зачем нам переписывать методы включить/выключить, если можно их повторно использовать?
В Lua для этого есть понятие мета-таблицы, т.е. таблицы-предка. У каждой таблицы есть одна таблица-предок, и дочерняя таблица умеет делать все, что умеет предок.
- Метатаблица - это обычная таблица, в которой описываются определённые события (например, сложение, вычитание, сравнивание и т.д.)
- В качестве названия события выступает индекс таблицы, а её значение - обработчик события (метод)
setmetatable связывает таблицы новую и таблицу-предок, с помощью метода __index. Этот метод вызывается всякий раз, когда запрашиваемый атрибут таблицы не найдет (в нашем случае — superlamp:turn_on). И поскольку superlamp.__index == nil и lamp.__index == nil, то вызова метода не происходит.
Допустим, что объект-таблицу lamp мы уже создали. Тогда супер-лампочка будет выглядеть так:
lamp = { on = false; turn_on = function(l) l.on = true end; turn_off = function(l) l.on = false end; } -- говорим искать в таблице lamp все неизвестные атрибуты lamp.__index = lamp superlamp = { brightness = 100 } -- указываем родительскую таблицу setmetatable(superlamp, lamp) -- и ее методы теперь доступны (ищутся в таблице, superlamp, потом в superlamp.__index, потом в ее метатаблице lamp.__index superlamp:turn_on() print (superlamp.on) superlamp:turn_off() print (superlamp.on)
Расширение функциональности
Работает, но непонятно.
Родительские таблицы есть у многих видов (ну у строк и таблиц точно, у чисел и булевых чисел, и у пустоты их нет). Допустим, мы хотим складывать все строки с помощью оператора "+", а не "..". Для этого нужно подменить функцию «+» (__add) для родительской таблицы всех строк:
s = getmetatable("") -- получили родительскую таблицу строки s.__add = function(s1, s2) return s1..s2 end -- подменили метод -- проверяем a = "Привет" b = "Мир!" print(a + b) -- напишет "ПриветМир!"
Собственно, мы ещё можем заменить функцию print с помощью «print = myfunction», да и много других приёмчиков можно сделать.
Области видимости
Не понятно. Нужны примеры
Переменные бывают глобальные и местные. При создании все переменные в Lua являются глобальными.
ПОДУМАЙТЕ: почему?
Для указания местной области видимости пишут ключевое слово local:
local x local var1, var2 = 5, 3
Не забывайте об этом слове.
Обработка ошибок
Часто, если возникают ошибки, надо прекратить выполнение определённой функции. Можно, конечно, сделать множество проверок и вызывать «return», если что-то пошло не так. Но это увеличит объём кода. В Lua используется что-то наподобие исключений.
Ошибки порождаются с помощью функции error(x). В качестве довода можно передать всё, что угодно (то, что имеет отношение к ошибке — строковое описание, числовой код, объект, с которым произошла ошибка и т.д.)
Обычно после этой функции вся программа сбойно завершается. А это нужно далеко не всегда. Если вы вызываете функцию, которая может создать ошибку (или её дочерние функции могут создать ошибку), то вызывайте её безопасно, с помощью pcall():
function f(x, y) ... if ... then error("failed to do somthing") end ... end status, err = pcall(f, x, y) -- f:функция, x-y: ее аргументы if not status then -- обработать ошибку err. В нашем случае в err находится текст ошибки end
Уставные библиотеки
Уставных библиотек мало, зато это позволяет запускать Lua где угодно. Подробнее можно получить их список здесь — http://www.lua.org/manual/5.2/manual.html
Неуставных библиотек много, их можно найти на LuaForge, LuaRocks и в других хранилищах.
Между Lua и не-Lua
ВНИМАНИЕ: эту часть советуем читать людям со знанием языка C.
А если нам недостаточно функциональности стандартных библиотек? Если у нас есть наша программа на C, а мы хотим вызывать её функции из Lua? Для этого есть очень простой механизм.
Допустим, мы хотим создать свою функцию, которая возвращает случайное число (в Lua есть math.random(), но мы хотим поучиться). Нам придется написать вот такой код на C:
#include <lua.h> #include <lauxlib.h> #include <time.h> /* собственно, что делать при вызове `rand(from, to)` */ static int librand_rand(lua_State *L) { int from, to; int x; from = lua_tonumber(L, 1); /* первый параметр функции */ to = lua_tonumber(L, 2); /* второй параметр функции */ x = rand() % (to - from + 1) + from; lua_pushnumber(L, x); /* возвращаемое значение */ return 1; /* возвращаем только один аргумент */ } /* в Lua "rand" соответствует нашей функции librand_rand() */ static const luaL_reg R[] = { {"rand", librand_rand}, {NULL, NULL} /* конец списка экспортируемых функций */ }; /* вызывается при загрузке библиотеку */ LUALIB_API int luaopen_librand(lua_State *L) { luaL_openlib(L, "librand", R, 0); srand(time(NULL)); return 1; /* завершаемся успешно */ }
Т.е. Lua предоставляет нам функции для работы с видами данных, для получения доводов функций и возврата ответов. Функций очень мало, и они довольно простые. Теперь мы собираем нашу библиотеку как динамическую, и можем использовать функцию rand():
random = require("librand") -- загружаем библиотеку print(random.rand(1, 100)) print(random.rand(0, 1))
А если мы хотим вызывать код, написанный на Lua из наших программ? Тогда наши программы должны создавать виртуальную машину Lua, в которой и будут выполняться Lua-скрипты. Это намного проще:
#include "lua.h" #include "lauxlib.h" int main() { lua_State *L = lua_open(); // создаем виртуальную машину Lua luaL_openlibs(L); // загружаем стандартные библиотеку luaL_dofile(L, "rand.lua"); // выполняем скрипт lua_close(L); // закрываем Lua return 0; }
Все.
Вы теперь можете писать на Lua. Если вы узнаете любопытные вещи про Lua, которые можно было бы отразить в статье — пишите!