30 посетителей на сайте. Из них:
Пользователи2
Роботы28
Список пользователей
Дмитрий Богословия Сейчас на сайте
Артём Мельников Сейчас на сайте
Oleg Zhidkov Был(a) в сети 4 минуты назад
Крысурсы Был(a) в сети 6 минут назад
MESHKOV STEPAN Был(a) в сети 7 минут назад
андрей старосвеский Был(a) в сети 8 минут назад
Oskaras Geniotis Был(a) в сети 13 минут назад
akihirootmorozok123 Был(a) в сети 15 минут назад
Даня Миронов Был(a) в сети 15 минут назад
Кирилл Сенченко Был(a) в сети 27 минут назад
Сеня Был(a) в сети 30 минут назад
forsizero Был(a) в сети 33 минуты назад
mka Был(a) в сети 35 минут назад
MAYOROV Был(a) в сети 40 минут назад
Саня Кот Был(a) в сети 55 минут назад
dada sdsdsa Был(a) в сети 1 час назад
WiseBear Был(a) в сети 1 час назад
Merkz Live Был(a) в сети 1 час назад
Reactive Fon Был(a) в сети 1 час назад
Никита жосан Был(a) в сети 1 час назад
Петр Иванов Был(a) в сети 1 час назад
Sneg molodoy Был(a) в сети 1 час назад
Саня Был(a) в сети 1 час назад
Такси Максим Был(a) в сети 1 час назад
sda asdasd Был(a) в сети 1 час назад
Hate Был(a) в сети 1 час назад
Ihor Dm Был(a) в сети 1 час назад
Exclsuive Armenia Original Был(a) в сети 2 часа назад
LL X Был(a) в сети 2 часа назад
Obrigan2010 Был(a) в сети 2 часа назад
Olexandr Lubord Был(a) в сети 2 часа назад
Семин Ундер Был(a) в сети 2 часа назад
Sanek Был(a) в сети 2 часа назад
Глеб Был(a) в сети 2 часа назад
denis gold Был(a) в сети 2 часа назад
zhmur123 zhmur Был(a) в сети 2 часа назад
hootan Был(a) в сети 2 часа назад
Ariel Enc. Был(a) в сети 2 часа назад
Fixter Был(a) в сети 2 часа назад
Ok Hh Был(a) в сети 2 часа назад
asdasdasd sdasd Был(a) в сети 2 часа назад
Был(a) в сети 2 часа назад
Енот Плей Был(a) в сети 2 часа назад
Павел Пешкичев Был(a) в сети 2 часа назад
even Был(a) в сети 3 часа назад
Hoshi Был(a) в сети 3 часа назад
Арсений Был(a) в сети 3 часа назад
Кирилл Лагуна Был(a) в сети 3 часа назад
LIMMA 86 Был(a) в сети 3 часа назад
Максим Савенков Был(a) в сети 3 часа назад
Fa1zon YT Был(a) в сети 3 часа назад
Danya.Sapranko Был(a) в сети 3 часа назад
Илья Семкин Был(a) в сети 3 часа назад
влад новиньков Был(a) в сети 3 часа назад
Donny Был(a) в сети 3 часа назад
Dagestani Был(a) в сети 3 часа назад
Alexader Kokin Был(a) в сети 3 часа назад
saba saba Был(a) в сети 3 часа назад
Александр Куртыгин Был(a) в сети 3 часа назад
VoronYTB Был(a) в сети 3 часа назад
Даниил Смирнов Был(a) в сети 4 часа назад
Moat Был(a) в сети 4 часа назад
TONI OWNPONI Был(a) в сети 4 часа назад
Sergey Type Был(a) в сети 4 часа назад
василь дичук Был(a) в сети 4 часа назад
KraKen Hack Был(a) в сети 4 часа назад
Serg Matev Был(a) в сети 4 часа назад
mrvnss Был(a) в сети 4 часа назад
Егор Титаев Был(a) в сети 4 часа назад
sakura. Был(a) в сети 4 часа назад
Miki Kubovych Был(a) в сети 4 часа назад
Danil Dorofeev Был(a) в сети 4 часа назад
vovka danilov Был(a) в сети 4 часа назад
xaineed Был(a) в сети 5 часов назад
Семëн Ханьков Был(a) в сети 5 часов назад
Nikolay Gevorgyan Был(a) в сети 5 часов назад
Никита Вьюгин Был(a) в сети 5 часов назад
mazi kh Был(a) в сети 5 часов назад
Andriuska Sipovic Был(a) в сети 5 часов назад
Данил Дудоров Был(a) в сети 5 часов назад
MTA Был(a) в сети 5 часов назад
Максим Гармаш Был(a) в сети 5 часов назад
Ноунейм Был(a) в сети 6 часов назад
Flame Russia Был(a) в сети 6 часов назад
Был(a) в сети 6 часов назад
Кирилл Моисеев Был(a) в сети 6 часов назад
Nevala kini Был(a) в сети 6 часов назад
♥VELOV♥ Был(a) в сети 6 часов назад
Николас Голубятников Был(a) в сети 6 часов назад
Z Z Был(a) в сети 6 часов назад
bateman Был(a) в сети 6 часов назад
Орвжоаена Был(a) в сети 7 часов назад
Был(a) в сети 7 часов назад
Люблю Аймана Был(a) в сети 7 часов назад
Артем Марсианенский Был(a) в сети 7 часов назад
макс Был(a) в сети 7 часов назад
lkaus Lonselot Был(a) в сети 7 часов назад
Demon Был(a) в сети 7 часов назад
Кирилл Лосев Был(a) в сети 7 часов назад
Новая База Был(a) в сети 7 часов назад
david Ohanyan Был(a) в сети 7 часов назад
Crazy_Style Был(a) в сети 7 часов назад
Спам Пошта Был(a) в сети 8 часов назад
WildWorld Был(a) в сети 8 часов назад
Waxxa Был(a) в сети 8 часов назад
Dmitriy Gromov Был(a) в сети 8 часов назад
Руслан Детров Был(a) в сети 8 часов назад
Дориал Туториал Был(a) в сети 8 часов назад
sabanovandre11 Был(a) в сети 8 часов назад
Кристина Антонова Был(a) в сети 8 часов назад
Helzy Был(a) в сети 8 часов назад
Okoldovany Был(a) в сети 9 часов назад
Danger Player Был(a) в сети 9 часов назад
Mahmut Был(a) в сети 9 часов назад
Иван Чучкалов Был(a) в сети 9 часов назад
GROSSMAN Был(a) в сети 9 часов назад
Clopo Alex Был(a) в сети 9 часов назад
Artem UARP Был(a) в сети 9 часов назад
Сергей Шемет Был(a) в сети 9 часов назад
taras dehtyaruk Был(a) в сети 9 часов назад
Boris Borisovich Был(a) в сети 9 часов назад
Владислав Виноградов Был(a) в сети 9 часов назад
Barxan01 Был(a) в сети 10 часов назад
Александр Сизов Был(a) в сети 10 часов назад
Владислав Ткачев Был(a) в сети 10 часов назад
Руслан Балахнин Был(a) в сети 10 часов назад
Samir Был(a) в сети 10 часов назад
Fade C-OPS Был(a) в сети 10 часов назад
Nice Был(a) в сети 10 часов назад
AGGRESS1VEX Был(a) в сети 10 часов назад
'mrx 'mountian' Был(a) в сети 11 часов назад
Алексей Кожин Был(a) в сети 11 часов назад
Адам Котик Был(a) в сети 11 часов назад
ZEDQ Был(a) в сети 11 часов назад
BRUNO fx Был(a) в сети 11 часов назад
Vladislav Был(a) в сети 12 часов назад
Sergey Makarov Был(a) в сети 12 часов назад
kawan bravo Был(a) в сети 12 часов назад
Олег Хардлайн Был(a) в сети 12 часов назад
イロタナ リトラクタ Был(a) в сети 12 часов назад
Rafael Gc Был(a) в сети 13 часов назад
guiprovador cea2025guiprovador Был(a) в сети 13 часов назад
kokote la aplica Был(a) в сети 15 часов назад
Илья Глинов Был(a) в сети 15 часов назад
Александр Шилякин Был(a) в сети 15 часов назад
Аллалал у Кирилла Был(a) в сети 15 часов назад
Touge Night Был(a) в сети 16 часов назад
Wdstrongmann Был(a) в сети 16 часов назад
David Sardarian Был(a) в сети 16 часов назад
El papi Был(a) в сети 16 часов назад
1 Был(a) в сети 17 часов назад
Makeshov Был(a) в сети 17 часов назад
Роман Римар Был(a) в сети 17 часов назад
Nikita Был(a) в сети 18 часов назад
Gredex ❶ Был(a) в сети 18 часов назад
Max DsUkamak Был(a) в сети 18 часов назад
Əli Aliyev Был(a) в сети 19 часов назад
Montana Specter Был(a) в сети 19 часов назад
Никита Е Был(a) в сети 19 часов назад
Kolya32777 Был(a) в сети 19 часов назад
Dima Assanov Был(a) в сети 19 часов назад
dendyqq Был(a) в сети 19 часов назад
Роман Минаков Был(a) в сети 19 часов назад
Sedgwick Был(a) в сети 19 часов назад
Сева Гринюк Был(a) в сети 19 часов назад
Flesex Был(a) в сети 19 часов назад
Oscar Был(a) в сети 20 часов назад
Семён Сергеев Был(a) в сети 20 часов назад
Был(a) в сети 20 часов назад
Список ботов
rambler (27)

Следите за нами!

Обучение LUA

Описание

Про Lua




Введение

Lua - это скриптовый язык программирования такой. Быстрый, легкий и удобный.

Изначально Lua создавался как язык программирования баз данных. Фактически, все программирование на Lua сводится к различным манипуляциям с таблицами. Таблицы - это краеугольный камень философии Lua. Таблица - это набор данных, где каждому уникальному ключу соответствует значение. Простой синтаксис и легкость встраивания Lua в приложение обеспечили Lua широкое распростанение в среде игроделов (откуда родом я сам). Во многих игровых компаниях при приеме на работу знание Lua уже MUST.

Теперь небольшой вводный курс в Lua.


Типы данных

nil - ничего, означающее отсутствие какого либо значения

boolean - булевская переменная, может принимать значения true или false

number - число

string - строка

function - функция (да-да, это таже тип данных!)

userdata - специальный тип данных, позволяющий хранить в Lua данные из С (фактически это указатель void*)

thread - сопрограмма Lua (позволяет организовать превдо-многопоточность)

table - таблица - ассоциативный массив (набор пар ключ-значение), причем в качестве и ключа и значения может выступать любой тип данных (угу, и функция тоже может быть ключом!)


Lua - язык с динамической типизацией, то есть тип переменной устанавливается не заранее, как, например, в С, а в момент присвоения переменной значения.

Примеры:


var = true -- var - переменная типа boolean

var = 1 -- теперь var число

var = "string" -- теперь var строка

var = function(a, b) return a + b end -- а теперь var функция, которая принимает два параметра и возвращает их сумму

var = coroutine.create(var) -- а теперь var сопрограмма

var = {} -- а тепеь var таблица

var = nil -- а теперь... теперь нет var!


Два символа -- означают начало однострочного комментария.


Область видимости переменных

Переменные могут быть локальными или глобальными. Локальные переменные объявляются с ключевым словом local и определены внутри блока. При выходе за пределы блока значение локальной переменной становится nil. Блоком является либо файл, либо последовательность выражений между ключевыми словами do и end.

Пример:


local x = 10 -- локальная переменная х


Переменная становитя глобальной после первого присвоения ей значения.

Пример:


y = 20 -- глобальня переменная y


Функции

Итак, мы выяснили, что функция является типом данных. То есть, функции можно возвращать из функций.

Пример:


function outer()

    function inner()

        return 1

    end

     return inner

end

local i  = outer() -- значение переменной i равно функции inner

local j = i() -- вызываем функцию i, значение переменной j равно 1


Функции могут принимать несколько значений. Если при вызове функции последние значения опущены, им присваивается nil. Параметры функции являются локальными переменными внутри функции.

Пример:


function f(x, y, z) -- определение функции

end


f() -- вызов функции. x, y, z равны nil

f(1) -- вызов функции. x = 1, y, z равны nil

f(1, 2) -- вызов функции. x = 1, y = 2, z равно nil

f(1, 2, 3) -- вызов функции. x = 1, y = 2, z = 3

f(1, 2, 3, 4) -- вызов функции. x = 1, y = 2, z = 3, 4 лишний параметр


Функции могут возвращать несколько значений.

Пример:


function f()

    return 1, 2, 3 -- функция возвращает три числа

end


local x, y, z = f() -- вызов функции. x = 1, y = 2, z = 3


Переменная _ используется в Lua для замены ненужной переменной.

Например, из функции f() нам нужно получить только третий параметр:


local _, _, z = f() -- z = 3


"Замороженные" переменные (upvalues)

Функция может обращаться к локальным переменным, которые определены вне ее. Если при вызове функции локальная переменная уже вышла из области видимости и была разрушена, то функция пользуется тем значением внешней локальной переменной (external local variable), которое было на момент определения функции.

Пример:


local x = 10 -- установили значение x

function f()

    return x + 1 -- на момент создания функции значение переменной x равно 10

end

x = 20 -- изменили x

local y = f() -- значение у равно 21, локальная переменная х продолжает существовать.


Изменим время жизни локальной переменной x:


do -- начало блока

    local x = 10 -- установили значение x

    function f()

        return x + 1 -- на момент создания функции значение переменной x равно 10

    end

end -- конец блока, переменной x больше нет

local y = f() -- значение у равно 11, хотя переменная х больше не существует


Функции, кстати, тоже могут быть локальными:


local f = function(x, y, z) -- да, функцию можно объявить и так, это ведь всего лишь один из типов данных.

    return z, y, x -- хе-хе, неплохая функция swap получилась, верно?

end


  Некоторые особенности при вызове функций

Например, есть две функции:


function f1()

    return 1, 2

end


function f2(x, y)

    return x + y

end


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


loсal x = f2( f1() ) -- значение x равно 3


Задачка посложнее:


function f1()

    return 1, 2, 3

end


function f2(x, y, z)

    return x + y + z

end


local x = f2(f1()) -- x = 1 + 2 + 3


Теперь так:


local x = f2(4, f1()) -- x = 4 + 1 + 2


Понятно, что f1 дополняет последние параметры f2 нужным числом своих параметров. Однако это работает, только если вызов f1 стоит последним в списке параметров. Что будет если вызвать функции так:


local x = f2(f1(), 4, 5) -- x = 1 + 4 + 5


В этом случае из f1 будет взят только первый параметр. А если так:


local x = f2(f1(), 4) -- x = 1 + 4 + nil - ошибка!


Да, третьим параметром будет nil, что при выполнении математической операции сгенерирует ошибку.


Переменное число параметров

В Lua, как и в С, в функцию можно передавать переменное число параметров. Синтаксис также похож:


function f(x, y, ...)

end


Все параметры, скрытые за ... Lua упаковывает в локальную таблицу arg. У таблицы arg есть поле n, содержащее число переданных аргументов. Для примера, напечатаем все переданные в функцию аргументы:


function f(...)

    for i = 1, arg.n do

        print(arg[i])

    end

end

                      

f(1, 2, 3, "Вася") -- выведет соответственно 1, 2, 3, "Вася"


Таблицы

Таблица - это набор пар ключ-значение. Ключом в таблице может быть любое значение, кроме nil. Значением  поля в таблице так же может быть любое значение, кроме nil. Cоздание таблицы:


local t = {}


Заполним ее различными типами данных:


t[1] = 10

t["Вася"] = "осел"

t[3] = true


local function f()

    return 1

end


t[f] = "функция"

t["функция"] = f

t.me = t -- тоже самое, что t["me"] = t - ссылка на самого себя

print(t["функция"]()) -- > 1


Таблица может быть проинициализирована при создании:


local t = {1, 2, 3, x = 5, ["Вася"] = "осел"}


Массивы

Если при инициализации таблицы ключи не были указаны, то Lua сама присваивает значениям ключи, начиная с 1.

Внимание! Индексация массивов в Lua начинается с 1, не с 0!


local t = {3, 4, 5}

print(t[1], t[2], t[3]) -- > 3, 4, 5


Важный момент: пока возможно, Lua внутри себя таблицу хранит как массив, а не как хэш - таблицу. Соответственно доступ к элементам таблицы происходит почти так же быстро, как в массивах Си. Поэтому без особой нужды не стоит превращать массив в хэш. Для того, чтобы не нарушать структуру при добавлении и удалении элементов массива стоит пользоваться  библиотекой Lua table.


local t = {1, 2, 3, 4, 5}

table.insert(t, 6) -- добавляет элемент в конец массива. Теперь  t = {1, 2, 3, 4, 5, 6}

table.insert(t, 0, 1) -- вставляет элемент по индексу, сдвигая оставшиеся элементы массива. Теперь  t = {0, 1, 2, 3, 4, 5, 6}

table.remove(t, 3) -- удаляет из таблицы элемент по индексу 3 и сдвигает оставшиеся элементы. Теперь t = {0, 1, 3, 4, 5, 6}


Получение размера массива выполняется оператором #:


local count = #t


Оператор # возвращает целое число n, такое, что t[n] не nil, и t[n + 1] равно nil. Другими словами оператор #, возвращает  максимальный индекс непрерывной последовательности ключей от начала массива. Соответственно, для таблицы:


local t = {1, [100] = 2}

print(#t) -- > 1, поскольку t[1] не nil, а t[1 + 1] равно nil.


Для массива, в котором значения хранятся одно за другим, оператор # вернет количество элементов в массиве.


Обход элементов таблицы

В общем случае узнать, сколько элементов хранится в таблице Lua, кроме как обойдя все элементы таблицы, нельзя. Исключение - это массивы и оператор # для определения длины массива. Массив обходится обычным циклом for:


local t = {1, 2, 3, "Вася"}

for i = 1, #t, 1 do

    print(t[i])

end


-- > 1

-- > 2

-- > 3

-- > Вася


Обычные таблицы обходятся циклом for с итератором:


local t = {1, 2, x = 4, y = 5, ["Вася"] = "осел"}

for key, value in pairs(t) do

    print(key, value)

end


-- > 1    1

-- > 2    2

-- > y    5

-- > x    4

-- > Вася    осел


Как видно из печати, порядок элементов в таблице отличается от порядка, в котором значения помещали в таблицу, поскольку внутри хэш-таблицы ключи сортируются по некоему признаку.


Методы

Рассмотрим следующую конструкцию (попытка сымитировать ООП):


local t = {x = 1}


function t:fun()

    print(self.x)

end

                     

t:fun()


-- > 1


 Что мы здесь сделали?

1. Создали таблицу t;

2. В таблице t завели поле x и назначили ему значение 1;

3. В таблице t завели функцию fun;

4. Внутри функции fun обратились к таблице через специальную переменную self (на самом деле self это первый скрытый параметр у функции fun. Вызывая функцию как t:fun() , мы а самом деле сделали то же самое, что и t.fun(t))

5. Вызвали у таблицы (уже почти объекта!) t метод fun;


На что все это похоже? Это похоже на ООП. Есть, объект, есть метод объекта, есть вызов метода объекта. Чего нет? Нет возможности создавать объекты определенного типа (например типа t). То есть и объект, и метод существуют в единственном экземпляре. Для того, чтобы создать объект типа tt, который ведет себя так же, как объект типа t, нам придется повторить всю процедуру с самого начала:


local tt = {x = 2}


function tt:fun()

    print(self.x)

end

                     

function checkMetod(x)

    x:fun()

end


checkMetod(t)

checkMetod(tt)


--> 1

--> 2


Есть ли выход из сложившейся ситуации? Да, есть. Но о нем мы поговорим после того, как рассмотрим метатаблицы.


Метатаблицы.

Метатаблицы позволяют переопределить поведение встроенных типов. Для таблиц это можно сделать прямо в Lua, а для других типов придется это делать через С.

Метатаблицы схожи с конструкцией operator() в C++. Рассмотрим, например, как можно складывать две таблицы (в самом языке такая операция не предусмотрена).


-- первый операнд

local data1 = {x = 1}

-- второй операнд

local data2 = {x = 2}

-- создаем таблицу

local meta = {}

-- в таблице определяем специальное поле __add (оператор +)

function meta.__add(op1, op2)

  return op1.x + op2.x

end

-- устанавливаем метатаблицу первому операнду

setmetatable(data1, meta)

-- проверяем

print(data1 + data2) --> 3


Итак, что мы сделали? Создали два объекта, каждый из которых содержит поле х. Затем создали обычную таблицу meta. В ней определили функцию со специальным именем __add. Затем установили метатаблицу первому операнду.

С тем же успехом мы могли бы установить эту метатаблицу и второму операнду. Когда Lua обнаруживает операцию над значением, то сначала ищется подходящий обработчик в метатаблице первого операнда, и если там нет, то ищется обработчик в метатаблице второго операнда. Если обработчика и там нет, то генерируется ошибка.

Вот список операций, обработчики которых могут быть переопределены в метатаблице:


ОбработчикОператор LuaПримечание
__add(op1, op2)+сложение
__sub(op1, op2)-вычитание
__mul(op1, op2)*умножение
__div(op1, op2)/деление
__mod(op1, op2)%деление по модулю
__pow(op1, op2)^возведение в степень
__unm(op)-унарный минус
__concat(op1, op2)..конкатенация (склейка)
__len(op)#унарный оператор взятия длины
__eq(op1, op2)==оператор "равно"
__lt(op1, op2)<оператор "меньше"
__le(op1, op2)<=оператор "меньше либо равно"
__index(op, key)[]оператор обращения по ключу
вызовется если, например,  вызвать local x = data1[1]
__newindex(op, key, value)[]оператор присвоения нового значения по ключу
если сделать эту функцию пустой, то таблица станет "readonly", то есть в нее нельзя будет добавить новое поле со значением
 вызовется если, например,  вызвать data1[1] = 1
__call(op, ...)()оператор вызова функции
вызовется если, например,  вызвать data1(1, 2, 3)
в обработчик __call первым параметром придет операнд (в нашем случае data1), а следующими параметрами будут те параметры, что стояли в скобках (в нашем случае 1, 2, 3).

Как видно, все очень просто.

ООП

С помощью метатаблиц можно реализовать некое подобие объектно-ориентированного программирования.

Попробуем это сделать. Сначала создадим базовый класс (обычная таблица):


Base = {}


В ней создадим поле field:


Base.field = "text"


и две функции для работы с этим полем:


function Base:setField(value)

    self.field = value

end


function Base:getField()

    return self.field

end


Обратите внимание на двоеточие перед именем метода - первым параметром в метод будет передаваться self, то есть сам объект класса (см. сюда), по аналогии с С++ это this, только в С++ this передается в метод неявно.

Итак, базовый класс создан. Это шаблон, по которому должны создаваться объекты данного типа.

Создадим объект типа Base:


local base = {}


Как видно, пока это обычная таблица. Установим у этой таблицы новую метатаблицу.


setmetatable(base, {__index = Base})

 

Вуаля! Теперь самая обычная таблица стала объектом типа Base. Что это значит? Согласно правилам, описанным в предыдущем пункте Метатаблицы, оператор __index в метатаблице вызывается всякий раз, когда осуществляется поиск в таблице по ключу. А что есть вызовы "методов" и обращение к "членам класса", как не поиск поля в таблице?

Сначала ищутся поля в самой таблице. Если поле не найдено,  в метатаблице ищется метод __index. Если __index является функцией, вызывается эта функция. Если __index является таблицей, то поиск поля осуществляется в этой таблице. Эти вызовы делаются рекурсивно до тех пор, пока метатаблица либо __index не станет равным nil. С учетом вышесказанного становится понятным, почему следующий вызов дает такой результат:


print(base:getField()) --> text


Сначала был произведен поиск поля  getField в таблице base. Ничего не найдено. Далее в таблице base ищется метатаблица. Найдено. В ней ищется поле __index. Найдено. Поле __index является таблицей. В таблице __index (а это на самом деле ссылка на таблицу Base) ищется поле  getField. Найдено. Это функция. Поскольку мы вызывали base:getField(), то в функцию getField первым параметром передается таблица base (см. сюда). Далее внутри функции getField повторяется аналогичный поиск для поля field. В итоге печатается первоначально заданное значение этого поля "text".


Изменим значение этого поля:


base:setField('aaa')

print(base:getField()) --> aaa


Теперь мы хотим наследоваться от класса Base. Наследование выполняется аналогично созданию объекта класса Base:


Child = {}

setmetatable(Child, {__index = Base})


Переопределим в Child метод getField(), фактически, делаем этот метод виртуальным:


function Child:getField()

    return 'zzz'

end


Создадим объект типа Child:


local child = {}

setmetatable(child, {__index = Child})

print(child:getField()) --> zzz


А как же быть, если мы хотим вызвать для Child метод родительского класса? А очень просто:


child:setField('ooo')

print(Base.getField(child)) --> ooo

Модули

Модули - это один из способов грамотно организовать большой проект в Lua. Это что-то типа namespace в С++.

Для начала рассмотрим наивную попытку организовать модули самостоятельно. Сделать это нетрудно. Достаточно создать файл, где будут созданы глобальные таблицы с именами модулей, и далее в программе обращаться к этим глобальным таблицам.

Например:


файл modules.lua:


Module1 = {}

Module2 = {}


файл module1.lua:


Module1.var1 = 1

function Module1.fun1(a, b)

    return a + b + Module1.var1

end


аналогично файл module2.lua:


Module2.var1 = 2

function Module2.fun1(a, b)

    return a + b - Module2.var1

end


файл запуска приложения main.lua:


dofile('modules.lua')

dofile('module1.lua')

dofile('module2.lua')


print(Module1.fun1(1, 2)) --> 4

print(Module2.fun1(1, 2)) --> 1


В общем, все очевидно. Неудобств несколько - необходимость внутри файла модуля (например, в module1.lua) при обращении к переменным модуля все время ссылаться на глобальную таблицу с именем модуля (Module1.var1), вероятность засорения глобального пространства имен именами временных переменных (написав, например, вместо local x = 1 просто x = 1), ну и еще есть какие-нибудь недостатки, включая главный - рукотворность этой

схемы.


Теперь рассмотрим, что нам предлагает Lua. Модули создаются глобальной функцией module(name). Эта функция проверяет, существует ли глобальная таблица с именем name. Если нет, то такая таблица создается. Затем эта таблица помещается в глобальную таблицу package.loaded[name] = table. Функция module(name) также устанавливает в созданной таблице поля t._NAME = name, t._M = t и t._PACKAGE = полное имя модуля минус последний компонент (если имя модуля составное). Имя модуля может состоять из нескольких частей, разделенных точками. В этом случае функция module создает (или использует, если уже существуют) таблицы для каждого компонента. Например вызов  module(a.b.c) создаст глобальную таблицу a, в ней таблицу b, а в таблице b создаст таблицу с. Кроме того, функция module делает еще одну важную вещь - устанавливает созданную таблицу как новый environment (окружение). Это значит, что все последующие обращения к глобальным переменным (до конца блока, обычно до конца текущего файла) будут направляться в новую таблицу.

Загрузка модулей

Чтобы модулем можно было пользоваться, его надо загрузить. Для этого нужно вызвать глобальную функцию require(name). Эта функция начинает с просмотра таблицы package.loaded, чтобы определить, модуль name  уже загружен или еще нет. Если модуль name загружен, то require возвращает значение package.loaded[name]. Если же нет, то require ищет загрузчик модуля.

<Далее идет почти буквальный перевод мануала. Переводил тщательно, чтобы самому все понять :)>

Загрузчики модулей хранятся втаблице package.loaders. Каждое поле этой таблицы является функцией поиска. Когда функция require ищет модуль, то она вызывает эти функции по очереди. Функция поиска может вернуть либо другую функцию (загрузчик модуля), либо строку, которая разъясняет, почему модуль не был найден (либо nil, если ей нечего сказать). Lua инициализирует таблицу package.loaders четырьмя функциями.


Первая функция ищет загрузчик в таблице package.preload.


Вторая функция ищет загрузчик Lua-библиотек, используя пути, прописанные в package.path. Путь - это последовательность шаблонов, разделенных точкой с запятой. В каждый шаблон функция поиска будет подставлять имя модуля, а затем будет пытаться открыть файл с именем, полученным из шаблона. Например, если


package.path = "./?.lua;./?.lc;/usr/local/?/init.lua"


то для загрузки модуля foo Lua будет пытаться открыть следующие файлы (порядок поиска совпадает с порядком задания шаблонов):


./foo.lua, ./foo.lc, and /usr/local/foo/init.lua.


Третья функция ищет загрузчик С-библиотек, используя пути, прописанные в package.cpath. Например, если


package.cpath = "./?.so;./?.dll;/usr/local/?/init.so"


то загрузчик будет пытаться открыть следующие файлы (порядок сохранен):


 ./foo.so, ./foo.dll, and /usr/local/foo/init.so.


Первая найденная библиотека динамически линкуется с программой. Затем загрузчик пытается найти С функцию внутри библиотеки, чтобы использовать ее для загрузки. Имя этой С-функции должно быть "luaopen_" + копия имени модуля, причем в имени модуля все точки должны быть заменены на подчеркивания. Кроме того, если в имени модуля есть дефис, то весь префикс, включая знак дефис, должен быть удален. Например, если имя модуля a.v1-b.c, то имя функции должно быть luaopen_b_c.


Четвертая функция пытается использовать загрузчик "все в одном". Эта функция использует шаблоны для загрузки С-библиотек, чтобы найти библиотеку для корневого модуля. Например, для модуля a.b.c эта функция будет искать С-библиотеку модуля a. Если такая библиотека найдена, то внутри нее ищется функция для загрузки субмодулей. В нашем примере такая функция должна иметь имя luaopen_a_b_c. Такая организация позволяет упаковывать несколько субмодулей внутри одной библиотеки, используя для загрузки каждого субмодуля отдельную функцию.

Уф, насилу перевел!

Теперь быстрее к самому вкусному - ООП на модулях!

ООП на модулях

В С++ это довольно привычно, располагать каждый класс в отдельном файле (обычно в двух, ИмяКласса.h и ИмяКласса.cpp). В Lua, поверьте, это тоже удобно :).

Проект удобно организовать следующим образом:

.

..

<luamodules> - папка

<cmodules> - папка

start.lua

...

Пути к Lua-библиотекам и С-библиотекам (загрузчики) можно прописать двумя различными способами.

Первый способ:

В первых двух строчках файла start.lua написать:


package.path = './luamodules/?.lua' -- пути к Lua библиотекам

package.cpath = './cmodules/?.dll' -- пути к С библиотекам


Второй путь:

Использовать системные переменные.

Создать файл start.bat в котором написать следующее:


set LUA_PATH=.\luamodules\?.lua;

set LUA_CPATH=.\cmodules?.dll

lua.exe start.lua


а в самом файле start.lua уже ничего не писать.


Итак, повторим наш учебный проект, но теперь с использованием модулей.

Первый модуль назовем Factory. Он будет создавать объекты классов и устанавливать метатаблицы.

Создадим файл Factory.lua (мы еще помним, что при поиске модуля Factory в шаблон package.path будет подставляться имя модуля?). Сохраним его в папке ./luamodules. В этом файле напишем следующее:


local base = _G


module('Factory')


function setBaseClass(class, baseClass)

  base.assert(baseClass.mtab) 

  base.setmetatable(class, baseClass.mtab)

end


function create(class, ...)

  local w = {}

  setBaseClass(w, class)

  w:construct(base.unpack(arg))

 

  return w

end


В начале файла мы видим магическую строку


local base = _G


Зачем она? Функция module, если вспомнить вышесказанное о ней, во время своего выполнения переключает локальный контекст. Это означает, что после ее вызова, если ничего не предпринять, доступ к глобальным переменным Lua будет закрыт. Поэтому мы сохраняем специальную глобальную переменную Lua _G (_G._G = _G) в локальной переменной base. Теперь доступ к глобальным переменным осуществляется через base.имя_переменной (в нашем случае это base.assert, base.setmetatable и base.unpack).


Так, прочный фундамент мы заложили, приступим к надстройке. Создадим файл ./luamodules/Base.lua


local base = _G


module('Base')

mtab = { __index = _M }


local Factory = base.require('Factory')


function new()

  return Factory.create(_M)

end


function construct(self)

  base.print('Base created!')

  self.field = 'text'

end


function setField(self, field) -- метод получения значения поля field

  self.field = field

end


function getField(self) -- метод установки значения поля field

  return self.field

end


Проясним некоторые места:


local Factory = base.require('Factory')


Для работы нам будет нужен модуль Factory.


Внимание!

Для избежания ошибок с циклическими зависимостями модулей, все функции require нужно вызывать после выполнения функции module!


mtab = { __index = _M }


Вспоминаем про функцию module - _M - это ссылка на саму таблицу Base.


mtab = { __index = _M }


Таблица mtab будет установлена как метатаблица у нового объекта класса Base при вызове метода create() модуля Factory. Это означает, что при поиске полей внутри объекта класса Base, если поле будет не найдено, то поиск будет осуществляться в таблице, на которую ссылается поле метатаблицы __index (см. метатаблицы).


function new()

  return Factory.create(_M)

end


Для удобства. Конечно, в прикладном коде можно вызвать и


local base = Factory.create(Base)


но мне кажется что вызов


local base = Base.new()


короче и очевидней.


function construct(self)

  base.print('Base created!')

end


Эта функция будет вызвана из метода create() модуля Factory. Фактически, это конструктор класса.

Все методы класса имеют первым параметром ссылку на объект класса self.

Напишем тестовый скрипт ./start.lua:


package.path = './luamodules/?.lua' -- пути к Lua библиотекам

package.cpath = './cmodules/?.dll' -- пути к С библиотекам


require('Base')


local base = Base.new()

print(base:getField())

base:setField(1)

print(base:getField())


Запускаем его на выполнение. 


lua.exe start.lua


Должны получить:


Base created!

text

1


Отлично! Двигаемся дальше. Создадим файл ./luamodules/Child.lua


local base = _G


module('Child')

mtab = { __index = _M }


local Factory = base.require('Factory')

local Base = base.require('Base')


Factory.setBaseClass(_M, Base) -- устанавливаем Base как базовый класс


function new(param1, param2) -- передаем параметры в конструктор

  return Factory.create(_M, param1, param2)

end


function construct(self, param1, param2) -- конструктор с параметрами

  Base.construct(self) -- вызов конструктора базового класса

  base.print('Child created!', param1, param2)

end


function getField(self) -- переопределяем метод

  return 'zzz'

end


Модернизируем немного ./start.lua, добавив в него строки:


require('Child')


и


local child = Child.new(1, 2)

print(child:getField())

child:setField(1)

print(child:getField())


Запускаем его на выполнение. 


lua.exe start.lua


Должны получить:


Base created!

text

1

Base created!

Child created!  1       2

zzz

zzz


Завершающие штрихи. Как создасть статический член класса? Очень просто. Внутри модуля объявить его либо как


member = 1


либо (предпочтительнее, поскольку более очевидно чего вы хотите)


_M.member = 1


Соответственно, статический метод класса будет без первого параметра self:


function staticFun(param1, param2)

end




Автор публикации:

WiseBear WiseBear

Скачать:

Скачать

Дата:
Автор ресурса:

Автор не указан

Введение в скриптинг
Введение в скриптинг
12.02.2021, Статьи
Введение в скриптинг 2
Введение в скриптинг 2
24.12.2020, Клиенты MTA
Руководство по отладке - как найти ошибки в ваших скриптах
Руководство по отладке - как найти ошибки в ваших
24.12.2020, Статьи
Введение в скриптинг GUI
Введение в скриптинг GUI
24.12.2020, Статьи

Нет комментариев.Оставишь комментарий?