Информация Гайд Классы в Lua: перегрузка операторов и стандартных функций. C-подобные конструкторы.

movebx

Известный
Автор темы
51
160

Введение.

Всем хай, здрасьте, привет и здарова. Никак не доходили руки рассказать про метатаблицы в Lua и их правильное использование. Чтож, в этой теме вы увидите:
1. Создание класса и его экземпляра, используя метатаблицы.
2. Создание методов, взаимодействующих с данным классом.
3. Перегрузка операторов +, - (в том числе урнарный), *, /, #, *. tostring и прочих.
5. Создание C-подобного конструктора через метаметод __call

Создание простого класса.

Единственное, что нужно понимать - все классы будут использоваться на метатаблицах. Отличие от класса и его экземпляра в том, что класс - лишь "заготовка" для того, чтобы создать и в дальнейшем использовать уже его экземпляр.
Приведу пример: vector - класс, vector( 1, 2, 3 ) ( Под 1, 2, 3 я имею ввиду уже значение этого вектора ) - уже экземпляр. Объяснение может быть немного непонятным, но в дальнейшем вы поймете разницу.

Классы мы будем создавать на примере вектора. Это некий объект, который имеет в себе 3 координаты: x, y, z и методы, с помощью которых мы взаимодействуем с этими координатами.
Начать стоит с того, что для класса мы будем использовать обычную таблицу, которую мы в будущем укажем как метатаблицу.
Lua:
local vector = { }
Хорошо, таблицу мы создали. Пора сделать конструктор для того, чтобы мы могли создавать экземпляры класса.
Лично я привык делать его через метод таблицы new. Сделаем так же, только первым аргументом нам нужно будет передать self, а следующими те - которые будут задействованы в объекте.
Lua:
vector.new = function( self, x, y, z )
        
end
Или же более компактный вариант
Lua:
function vector:new( x, y, z )

end
Напомню, что когда мы создаем функцию через :func_name( ... ), то первым аргументом всегда передается self, что будет являться референсом на таблицу, которая содержит этот метод.
Лично я использую первый вариант, тут уж, дело вкуса.

Хорошо, теперь в конструкторе нам нужно создать еще одну таблицу, которая будет хранить в себе значения всех аргументов, которые мы передали в конструктор.
Lua:
vector.new = function( self, x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0
end
Обратите внимание на то, что я использую vector.x = x or 0.0 и т.д. Это делается для того, чтобы указать аргументы как необязательные. То-есть если человек захочет нулевой вектор (вектор, у которого все координаты равны нулю), то ему не придется указывать их вручную. Так же происходит и с 2-х мерным вектором, пользователю банально не придется указывать Z координату как 0.0, ему достаточно будет указать только X и Y

Теперь нам нужно зарегистрировать нашу внутреннюю таблицу как класс, делается это таким образом:
Lua:
vector.new = function( self, x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0

    return setmetatable( vector, {
       __index = self
    } )
end
Вкратце про __index = self. __index это такой метаметод, который вызывается, если в текущей таблице не найдено значение, которое индексируется.
То-есть если имеется таблица AAA и BBB, но в таблицу BBB __index указан как таблица AAA, то при попытке получить значение, которого нет в BBB, Lua попытается обратиться к таблице AAA и найти там значение. Вот так вот.
__index может быть не только ссылкой на таблицу, а еще и функцией, которая обрабатывает тот ключ, который вы пытаетесь получить и возвращает результат, который указали вы.

В данном случае мы указали __index = self (где self - ссылка на внешнюю таблицу vector) для того, чтобы позже создать методы, которые будут описаны не во внутренней таблице, а где то вне.

Создание методов класса.

Для начала поясним, чем метод отличается от обычной функции. Метод - это функция, в которую первым аргументом всегда передается self (или this в C-подобных языках). А функция - все остальные виды функций.
Пояснение еще проще - методы присутствуют только у классов и структур.

И так, для создание методов мы будем использовать уже ВНЕШНЮЮ таблицу vector.
Вот как это выглядит:
Lua:
vector.new = function( self, x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0

    return setmetatable( vector, {
        __index = self
    } )
end

vector.length = function( self )

end
Длина вектора высчитывается по формуле: sqrt( x^2 + y^2 + z^2 ), это мы и опишем в данном методе.
Lua:
vector.length = function( self )
     return math.sqrt( math.pow( self.x, 2 ) + math.pow( self.y, 2) + math.pow( self.z, 2 ) )
end
Теперь поясню откуда взялся self и что он есть на самом деле. Немного выше я уже говорил про __index, так вот:
Из-за того, что мы указали в конструкторе __index = self (self - внешняя vector таблица), то при попытке найти метод length во внутренней таблице, из-за того, что там ее нет Lua обратиться ко внешней таблице, где этот метод и лежит и затем вызовет его.
Давайте добавим еще пару методов:
Lua:
vector.clone = function( self )
    return vector.new( self.x, self.y, self.z )
end

vector.dist = function( self, v_other )
    return math.sqrt(
        math.pow( v_other.x - self.x, 2 ) + math.pow( v_other.y - self.y, 2 ) + math.pow( v_other.z - self.z, 2 )
    )
end
Метод clone возвращает новый вектор с такими же координатами, а dist - дистанция текущего вектора до вектора, переданного в параметры.

Перегрузка операторов.

Перегрузка операторов это ни что иное, как изменение поведения и результата при определенных операторах.
Например мы можем сделать так, чтобы при складывании двух чисел они перемножались, при делении - вычитались и т.д.
Давайте вернемся к первому пункту и немного перепишем код:
1. Уберем self первым параметром из vector.new (Дальше будет объяснено почему)
2. Заменим таблицу, передаваемую вторым параметром в setmetatable на новую, указав ссылку на нее вместо явного определения
3. Укажем __index = vector, для того, чтобы не потерять взаимодействия с методами, т.к. self у нас больше не имеется.
В итоге получаем такой код:
Lua:
local vector_mt = { } do
    vector.mt.__index = vector
end

vector.new = function( x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0

    return setmetatable( vector, vector_mt )
end
Для чего это сделано? Для того, чтобы перегрузить операторы, нам нужно задать их реализацию непосредственно в таблице с данными (внутренней).
Это все из-за того, что внешняя таблица vector не является метатаблицы для внутренней.
Да, это конечно можно сделать подобным образом:
Lua:
local vector = { } do
    vector.__index = vector
    
    vector.new = function( self, x, y, z )
        local vector = { }
            vector.x = x or 0.0
            vector.y = y or 0.0
            vector.z = z or 0.0
            
        return setmetatable( vector, self )
    end
end
Но это, как по мне, сделает более грязным. Это чисто дело вкуса, каждый дрочит как он хочет.

Чтож, вернемся к перегрузке операторов.
Для ее реализации нам понадобятся следующие метаметоды:
__add - эквивалент ( + ) сложению
__sub - эквивалент ( - ) вычитанию
__unm - эквивалент ( -num ) урнарному минусу
__mul - эквивалент ( * ) умножению
__div - эквивалент ( / ) делению
__len - эквивалент ( # ) оператору длины в Lua
А так же метаметод __tostring. Он используется при приведению какого-либо типа данных в строку. Как его использовать поясню чуть ниже.
И так, возвращаемся в нашу новую таблицу vector_mt и пишем следующее:
Lua:
vector_mt.__add = function( self, v_other )

end

vector_mt.__sub = function( self, v_other )

end

vector_mt.__unm = function( self )

end 

vector_mt.__mul = function( self, v_other )

end

vector_mt.__div = function( self, v_other )

end

vector_mt.__len = function( self )

end
По стандарту перегрузки операторов должны возвращать новый экземпляр класса для того, чтобы иметь возможность выстраивать цепочку математических операторов.
Описываем логику для каждого метаметода.
Lua:
vector_mt.__add = function( self, v_other )
    return vector.new(
        self.x + v_other.x, self.y + v_other.y, self.z + v_other.z
    )
end

vector_mt.__sub = function( self, v_other )
    return vector.new(
        self.x - v_other.x, self.y - v_other.y, self.z - v_other.z
    )
end

vector_mt.__unm = function( self )
              return vector.new( -self.x, -self.y, -self.z )
end

vector_mt.__mul = function( self, v_other )
    return vector.new(
        self.x * v_other.x, self.y * v_other.y, self.z * v_other.z
    )
end

vector_mt.__div = function( self, v_other )
    return vector.new(
        self.x / v_other.x, self.y / v_other.y, self.z / v_other.z
    )
end

vector_mt.__len = function( self )
    return self:length( )
end
Таким образом мы перегрузили операторы. Теперь при вызове vector + vector2 нам вернется сумма соответствующих координат в новом векторе, а при попытке узнать длину вектора через # нам вернется его длина.

Так же сделаем перегрузку tostring. Сделаем так, что при попытке привести вектор к строке нам вернется его конструктор (который мы сделаем чуть позже)
Lua:
vector_mt.__tostring = function( self )
    return string.format( "vector( %.1f, %.1f, %.1f )", self.x, self.y, self.z )
end

Создание C-подобного конструктора.

Окей, для начала поясню, что такое C-подобный конструктор. Например, в C++ он выглядит так:
Vector vec1 = Vector( 1.0f, 1.2f, 1.3f );
То-есть мы явно не вызываем метод new, а сразу обращаемся с вызовом к таблице vector.
Реализуется он с помощью метаметода __call, который срабатывает когда таблицу пытаются вызвать как функцию.

Сделаем это так:
Сделаем метатаблицей внешнюю таблицу vector, которой определим метод __call:
Lua:
setmetatable( vector, {
    __call = function( self, x, y, z )
        return vector.new( x, y, z )
    end
} )
Обратите внимание на то, что мы передаем self первым аргументом, т.к. каждый метаметод обязан иметь таковой.

И теперь со спокойной душой мы можем протестировать все это дело:
Lua:
local vec1 = vector( 1, 2, 3 )
local vec2 = vector( 2, 3, 4 )
print( "first vector: ", vec1 )
print( "second vector: ", vec2 )
local sum = vec1 + vec2
print( "sum of 2 vectors: ", sum )
local sub = vec1 - vec2
print( "sub of 2 vectors: ", sub )
local mul = vec1 * vec2
print( "mul of 2 vectors: ", mul )
local div = vec1 / vec2
print( "div of 2 vectors: ", div )
local length = #vec1
print( "length of vec1: ", length )
local length2 = vec2:length( )
print( "length of vec2: ", length2 )

Полный код:
Lua:
local vector = { } do
    local vector_mt = { } do
        vector_mt.__index = vector

        vector_mt.__add = function( self, v_other )
            return vector.new(
                self.x + v_other.x, self.y + v_other.y, self.z + v_other.z
            )
        end

        vector_mt.__sub = function( self, v_other )
            return vector.new(
                self.x - v_other.x, self.y - v_other.y, self.z - v_other.z
            )
        end

        vector_mt.__mul = function( self, v_other )
            return vector.new(
                self.x * v_other.x, self.y * v_other.y, self.z * v_other.z
            )
        end

        vector_mt.__div = function( self, v_other )
            return vector.new(
                self.x / v_other.x, self.y / v_other.y, self.z / v_other.z
            )
        end

        vector_mt.__len = function( self )
            return self:length( )
        end

        vector_mt.__tostring = function( self )
            return string.format( "vector( %.1f, %.1f, %.1f )", self.x, self.y, self.z )
        end
    end

    vector.new = function( x, y, z )
        local vector = { }
            vector.x = x or 0.0
            vector.y = y or 0.0
            vector.z = z or 0.0

        return setmetatable( vector, vector_mt )
    end

    vector.length = function( self )
        return math.sqrt( math.pow( self.x, 2 ) + math.pow( self.y, 2) + math.pow( self.z, 2 ) )
    end

    vector.clone = function( self )
        return vector.new( self.x, self.y, self.z )
    end
    
    vector.dist = function( self, v_other )
        return math.sqrt(
            math.pow( v_other.x - self.x, 2 ) + math.pow( v_other.y - self.y, 2 ) + math.pow( v_other.z - self.z, 2 )
        )
    end

    setmetatable( vector, {
        __call = function( self, x, y, z )
            return vector.new( x, y, z )
        end
    } )
end

local vec1 = vector( 1, 2, 3 )
local vec2 = vector( 2, 3, 4 )
print( "first vector: ", vec1 )
print( "second vector: ", vec2 )
local sum = vec1 + vec2
print( "sum of 2 vectors: ", sum )
local sub = vec1 - vec2
print( "sub of 2 vectors: ", sub )
local mul = vec1 * vec2
print( "mul of 2 vectors: ", mul )
local div = vec1 / vec2
print( "div of 2 vectors: ", div )
local length = #vec1
print( "length of vec1: ", length )
local length2 = vec2:length( )
print( "length of vec2: ", length2 )






 
Последнее редактирование:

why ega

РП игрок
Модератор
2,546
2,236

Введение.

Всем хай, здрасьте, привет и здарова. Никак не доходили руки рассказать про метатаблицы в Lua и их правильное использование. Чтож, в этой теме вы увидите:
1. Создание класса и его экземпляра, используя метатаблицы.
2. Создание методов, взаимодействующих с данным классом.
3. Перегрузка операторов +, - (в том числе урнарный), *, /, #, 4. tostring и прочих.
5. Создание C-подобного конструктора через метаметод __call

Создание простого класса.

Единственное, что нужно понимать - все классы будут использоваться на метатаблицах. Отличие от класса и его экземпляра в том, что класс - лишь "заготовка" для того, чтобы создать и в дальнейшем использовать уже его экземпляр.
Приведу пример: vector - класс, vector( 1, 2, 3 ) ( Под 1, 2, 3 я имею ввиду уже значение этого вектора ) - уже экземпляр. Объяснение может быть немного непонятным, но в дальнейшем вы поймете разницу.

Классы мы будем создавать на примере вектора. Это некий объект, который имеет в себе 3 координаты: x, y, z и методы, с помощью которых мы взаимодействуем с этими координатами.
Начать стоит с того, что для класса мы будем использовать обычную таблицу, которую мы в будущем укажем как метатаблицу.
Lua:
local vector = { }
Хорошо, таблицу мы создали. Пора сделать конструктор для того, чтобы мы могли создавать экземпляры класса.
Лично я привык делать его через метод таблицы new. Сделаем так же, только первым аргументом нам нужно будет передать self, а следующими те - которые будут задействованы в объекте.
Lua:
vector.new = function( self, x, y, z )
        
end
Или же более компактный вариант
Lua:
function vector:new( x, y, z )

end
Напомню, что когда мы создаем функцию через :func_name( ... ), то первым аргументом всегда передается self, что будет являться референсом на таблицу, которая содержит этот метод.
Лично я использую первый вариант, тут уж, дело вкуса.

Хорошо, теперь в конструкторе нам нужно создать еще одну таблицу, которая будет хранить в себе значения всех аргументов, которые мы передали в конструктор.
Lua:
vector.new = function( self, x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0
end
Обратите внимание на то, что я использую vector.x = x or 0.0 и т.д. Это делается для того, чтобы указать аргументы как необязательные. То-есть если человек захочет нулевой вектор (вектор, у которого все координаты равны нулю), то ему не придется указывать их вручную. Так же происходит и с 2-х мерным вектором, пользователю банально не придется указывать Z координату как 0.0, ему достаточно будет указать только X и Y

Теперь нам нужно зарегистрировать нашу внутреннюю таблицу как класс, делается это таким образом:
Lua:
vector.new = function( self, x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0

    return setmetatable( vector, {
       __index = self
    } )
end
Вкратце про __index = self. __index это такой метаметод, который вызывается, если в текущей таблице не найдено значение, которое индексируется.
То-есть если имеется таблица AAA и BBB, но в таблицу BBB __index указан как таблица AAA, то при попытке получить значение, которого нет в BBB, Lua попытается обратиться к таблице AAA и найти там значение. Вот так вот.
__index может быть не только ссылкой на таблицу, а еще и функцией, которая обрабатывает тот ключ, который вы пытаетесь получить и возвращает результат, который указали вы.

В данном случае мы указали __index = self (где self - ссылка на внешнюю таблицу vector) для того, чтобы позже создать методы, которые будут описаны не во внутренней таблице, а где то вне.

Создание методов класса.

Для начала поясним, чем метод отличается от обычной функции. Метод - это функция, в которую первым аргументом всегда передается self (или this в C-подобных языках). А функция - все остальные виды функций.
Пояснение еще проще - методы присутствуют только у классов и структур.

И так, для создание методов мы будем использовать уже ВНЕШНЮЮ таблицу vector.
Вот как это выглядит:
Lua:
vector.new = function( self, x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0

    return setmetatable( vector, {
        __index = self
    } )
end

vector.length = function( self )

end
Длина вектора высчитывается по формуле: sqrt( x^2 + y^2 + z^2 ), это мы и опишем в данном методе.
Lua:
vector.length = function( self )
     return math.sqrt( math.pow( self.x, 2 ) + math.pow( self.y, 2) + math.pow( self.z, 2 ) )
end
Теперь поясню откуда взялся self и что он есть на самом деле. Немного выше я уже говорил про __index, так вот:
Из-за того, что мы указали в конструкторе __index = self (self - внешняя vector таблица), то при попытке найти метод length во внутренней таблице, из-за того, что там ее нет Lua обратиться ко внешней таблице, где этот метод и лежит и затем вызовет его.
Давайте добавим еще пару методов:
Lua:
vector.clone = function( self )
    return vector.new( self.x, self.y, self.z )
end

vector.dist = function( self, v_other )
    return math.sqrt(
        math.pow( v_other.x - self.x, 2 ) + math.pow( v_other.y - self.y, 2 ) + math.pow( v_other.z - self.z, 2 )
    )
end
Метод clone возвращает новый вектор с такими же координатами, а dist - дистанция текущего вектора до вектора, переданного в параметры.

Перегрузка операторов.

Перегрузка операторов это ни что иное, как изменение поведения и результата при определенных операторах.
Например мы можем сделать так, чтобы при складывании двух чисел они перемножались, при делении - вычитались и т.д.
Давайте вернемся к первому пункту и немного перепишем код:
1. Уберем self первым параметром из vector.new (Дальше будет объяснено почему)
2. Заменим таблицу, передаваемую вторым параметром в setmetatable на новую, указав ссылку на нее вместо явного определения
3. Укажем __index = vector, для того, чтобы не потерять взаимодействия с методами, т.к. self у нас больше не имеется.
В итоге получаем такой код:
Lua:
local vector_mt = { } do
    vector.mt.__index = vector
end

vector.new = function( x, y, z )
    local vector = { }
        vector.x = x or 0.0
        vector.y = y or 0.0
        vector.z = z or 0.0

    return setmetatable( vector, vector_mt )
end
Для чего это сделано? Для того, чтобы перегрузить операторы, нам нужно задать их реализацию непосредственно в таблице с данными (внутренней).
Это все из-за того, что внешняя таблица vector не является метатаблицы для внутренней.
Да, это конечно можно сделать подобным образом:
Lua:
local vector = { } do
    vector.__index = vector
    
    vector.new = function( self, x, y, z )
        local vector = { }
            vector.x = x or 0.0
            vector.y = y or 0.0
            vector.z = z or 0.0
            
        return setmetatable( vector, self )
    end
end
Но это, как по мне, сделает более грязным. Это чисто дело вкуса, каждый дрочит как он хочет.

Чтож, вернемся к перегрузке операторов.
Для ее реализации нам понадобятся следующие метаметоды:
__add - эквивалент ( + ) сложению
__sub - эквивалент ( - ) вычитанию
__mul - эквивалент ( * ) умножению
__div - эквивалент ( / ) делению
__len - эквивалент ( # ) оператору длины в Lua
А так же метаметод __tostring. Он используется при приведению какого-либо типа данных в строку. Как его использовать поясню чуть ниже.
И так, возвращаемся в нашу новую таблицу vector_mt и пишем следующее:
Lua:
vector_mt.__add = function( self, v_other )

end

vector_mt.__sub = function( self, v_other )

end

vector_mt.__mul = function( self, v_other )

end

vector_mt.__div = function( self, v_other )

end

vector_mt.__len = function( self )

end
По стандарту перегрузки операторов должны возвращать новый экземпляр класса для того, чтобы иметь возможность выстраивать цепочку математических операторов.
Описываем логику для каждого метаметода.
Lua:
vector_mt.__add = function( self, v_other )
    return vector.new(
        self.x + v_other.x, self.y + v_other.y, self.z + v_other.z
    )
end

vector_mt.__sub = function( self, v_other )
    return vector.new(
        self.x - v_other.x, self.y - v_other.y, self.z - v_other.z
    )
end

vector_mt.__mul = function( self, v_other )
    return vector.new(
        self.x * v_other.x, self.y * v_other.y, self.z * v_other.z
    )
end

vector_mt.__div = function( self, v_other )
    return vector.new(
        self.x / v_other.x, self.y / v_other.y, self.z / v_other.z
    )
end

vector_mt.__len = function( self )
    return self:length( )
end
Таким образом мы перегрузили операторы. Теперь при вызове vector + vector2 нам вернется сумма соответствующих координат в новом векторе, а при попытке узнать длину вектора через # нам вернется его длина.

Так же сделаем перегрузку tostring. Сделаем так, что при попытке привести вектор к строке нам вернется его конструктор (который мы сделаем чуть позже)
Lua:
vector_mt.__tostring = function( self )
    return string.format( "vector( %.1f, %.1f, %.1f )", self.x, self.y, self.z )
end

Создание C-подобного конструктора.

Окей, для начала поясню, что такое C-подобный конструктор. Например, в C++ он выглядит так:
Vector vec1 = Vector( 1.0f, 1.2f, 1.3f );
То-есть мы явно не вызываем метод new, а сразу обращаемся с вызовом к таблице vector.
Реализуется он с помощью метаметода __call, который срабатывает когда таблицу пытаются вызвать как функцию.

Сделаем это так:
Сделаем метатаблицей внешнюю таблицу vector, которой определим метод new:
Lua:
setmetatable( vector, {
    __call = function( self, x, y, z )
        return vector.new( x, y, z )
    end
} )
Обратите внимание на то, что мы передаем self первым аргументом, т.к. каждый метаметод обязан иметь таковой.

И теперь со спокойной душой мы можем протестировать все это дело:
Lua:
local vec1 = vector( 1, 2, 3 )
local vec2 = vector( 2, 3, 4 )
print( "first vector: ", vec1 )
print( "second vector: ", vec2 )
local sum = vec1 + vec2
print( "sum of 2 vectors: ", sum )
local sub = vec1 - vec2
print( "sub of 2 vectors: ", sub )
local mul = vec1 * vec2
print( "mul of 2 vectors: ", mul )
local div = vec1 / vec2
print( "div of 2 vectors: ", div )
local length = #vec1
print( "length of vec1: ", length )
local length2 = vec2:length( )
print( "length of vec2: ", length2 )

Полный код:
Lua:
local vector = { } do
    local vector_mt = { } do
        vector_mt.__index = vector

        vector_mt.__add = function( self, v_other )
            return vector.new(
                self.x + v_other.x, self.y + v_other.y, self.z + v_other.z
            )
        end

        vector_mt.__sub = function( self, v_other )
            return vector.new(
                self.x - v_other.x, self.y - v_other.y, self.z - v_other.z
            )
        end

        vector_mt.__mul = function( self, v_other )
            return vector.new(
                self.x * v_other.x, self.y * v_other.y, self.z * v_other.z
            )
        end

        vector_mt.__div = function( self, v_other )
            return vector.new(
                self.x / v_other.x, self.y / v_other.y, self.z / v_other.z
            )
        end

        vector_mt.__len = function( self )
            return self:length( )
        end

        vector_mt.__tostring = function( self )
            return string.format( "vector( %.1f, %.1f, %.1f )", self.x, self.y, self.z )
        end
    end

    vector.new = function( x, y, z )
        local vector = { }
            vector.x = x or 0.0
            vector.y = y or 0.0
            vector.z = z or 0.0

        return setmetatable( vector, vector_mt )
    end

    vector.length = function( self )
        return math.sqrt( math.pow( self.x, 2 ) + math.pow( self.y, 2) + math.pow( self.z, 2 ) )
    end

    vector.clone = function( self )
        return vector.new( self.x, self.y, self.z )
    end
    
    vector.dist = function( self, v_other )
        return math.sqrt(
            math.pow( v_other.x - self.x, 2 ) + math.pow( v_other.y - self.y, 2 ) + math.pow( v_other.z - self.z, 2 )
        )
    end

    setmetatable( vector, {
        __call = function( self, x, y, z )
            return vector.new( x, y, z )
        end
    } )
end

local vec1 = vector( 1, 2, 3 )
local vec2 = vector( 2, 3, 4 )
print( "first vector: ", vec1 )
print( "second vector: ", vec2 )
local sum = vec1 + vec2
print( "sum of 2 vectors: ", sum )
local sub = vec1 - vec2
print( "sub of 2 vectors: ", sub )
local mul = vec1 * vec2
print( "mul of 2 vectors: ", mul )
local div = vec1 / vec2
print( "div of 2 vectors: ", div )
local length = #vec1
print( "length of vec1: ", length )
local length2 = vec2:length( )
print( "length of vec2: ", length2 )






еще один интересный вид создания классов видел давно на хабре и адаптировал его под себя (пример бредовый, но пойдет):
Lua:
 -- создаем класс Vector
local Vector = {}


-- создаем конструктор
function Vector:new(...)
    -- создаем прототипы для таблиц, которые служат аналогом модификатора доступа
    local private
    local public

   
     -- инициализируем таблицу, в которой будет информация, достуная только внутри класса
    private = {}
   
    -- приватные поля
    private.x = 0
    private.z = 0
    private.y = 0

    -- приватный метод
    function private:printVector()
        return print(private.x, private.y, private.z)
    end
   
   
    public = {}
   
    -- можно добавить также поля, но покажу только метод
    function public:getData() -- недоступно для изменения
        private:printVector()
        return {x = private.x, y = private.y, z = private.z}
    end
   
    function public:getVector()
        return private
    end
   
    print("Вызван конструктор класса. Параметры: ", ...)
   
    setmetatable(public, self)
    self.__index = self
    return public
end


local newVector = Vector:new()
newVector:getVector().x = 10
print(newVector:getData().x)
 
  • Нравится
Реакции: kru_tin, Savok и movebx

movebx

Известный
Автор темы
51
160
еще один интересный вид создания классов видел давно на хабре и адаптировал его под себя (пример бредовый, но пойдет):
Lua:
 -- создаем класс Vector
local Vector = {}


-- создаем конструктор
function Vector:new(...)
    -- создаем прототипы для таблиц, которые служат аналогом модификатора доступа
    local private
    local public

  
     -- инициализируем таблицу, в которой будет информация, достуная только внутри класса
    private = {}
  
    -- приватные поля
    private.x = 0
    private.z = 0
    private.y = 0

    -- приватный метод
    function private:printVector()
        return print(private.x, private.y, private.z)
    end
  
  
    public = {}
  
    -- можно добавить также поля, но покажу только метод
    function public:getData() -- недоступно для изменения
        private:printVector()
        return {x = private.x, y = private.y, z = private.z}
    end
  
    function public:getVector()
        return private
    end
  
    print("Вызван конструктор класса. Параметры: ", ...)
  
    setmetatable(public, self)
    self.__index = self
    return public
end


local newVector = Vector:new()
newVector:getVector().x = 10
print(newVector:getData().x)
Я задрот всего оптимизированного. Созданием новой таблице ты только насрешь сборщику мусора в руку, а так же выделишь новую память. Если и юзаю инкапсуляцию, то только так (из питона как то само перекочевало):

Lua:
local cls = { }

cls.new = function( )
    local data = { }
        data.blablabla = 1.0 --public method
        data.__sperma1337 --два андерскора делают метод приватным
end
Если ты работаешь в какой то ide, то индексировать НЕ приватные методы будет легче, тебе банально не будут подсказывать методы с андерскором.
А так прикольный метод реализации инкапсуляции, кому то точно понравится
 
  • Нравится
Реакции: why ega

imring

Ride the Lightning
Всефорумный модератор
2,355
2,516
Лично я привык делать его через метод таблицы new. Сделаем так же, только первым аргументом нам нужно будет передать self, а следующими те - которые будут задействованы в объекте.

Сделаем метатаблицей внешнюю таблицу vector, которой определим метод __call:
Lua:
setmetatable( vector, {
__call = function( self, x, y, z )
return vector.new( x, y, z )
end
} )
Обратите внимание на то, что мы передаем self первым аргументом, т.к. каждый метаметод обязан иметь таковой.
ты забыл передать self для вектора, ибо будешь индексировать число.
Lua:
setmetatable(vector, {
    __call = function (self, x, y, z)
        return self:new(x, y, z)
    end
})

-- или

setmetatable(vector, { __call = vector.new })
 
  • Bug
Реакции: movebx

movebx

Известный
Автор темы
51
160
ты забыл передать self для вектора, ибо будешь индексировать число.
Lua:
setmetatable(vector, {
    __call = function (self, x, y, z)
        return self:new(x, y, z)
    end
})

-- или

setmetatable(vector, { __call = vector.new })
Я не забыл его не передать. Будь добр, перечитай тему заново.
Давайте вернемся к первому пункту и немного перепишем код:
1. Уберем self первым параметром из vector.new (Дальше будет объяснено почему)
2. Заменим таблицу, передаваемую вторым параметром в setmetatable на новую, указав ссылку на нее вместо явного определения
3. Укажем __index = vector, для того, чтобы не потерять взаимодействия с методами, т.к. self у нас больше не имеется.
В итоге получаем такой код:
Lua:
local vector_mt = { } do
vector.mt.__index = vector
end

vector.new = function( x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0

return setmetatable( vector, vector_mt )
end
Для чего это сделано? Для того, чтобы перегрузить операторы, нам нужно задать их реализацию непосредственно в таблице с данными (внутренней).
Это все из-за того, что внешняя таблица vector не является метатаблицы для внутренней.
Да, это конечно можно сделать подобным образом:
Lua:
local vector = { } do
vector.__index = vector

vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0

return setmetatable( vector, self )
end
end
Но это, как по мне, сделает более грязным. Это чисто дело вкуса, каждый дрочит как он хочет.
Я сделал новую таблицу vector_mt, в которой __index равен вектору.
Переменная self в конструкторе сделана для того, чтобы задать управляющую таблицу таблице-экземпляру, а т.к. vector_mt.__index = vector, то этого делать нет необходимости.

Напомню: каждый дрочит как он хочет
 

why ega

РП игрок
Модератор
2,546
2,236
А для чего он нужен? Где, как его применять?
я не гуру ООП, да и пример пока очень сырой (этот код даже не пример, а просто отрывок из моего наброска). Если что, это исходник небольшого снет сервера
Lua:
local SNET = require("snet")
local SNETPacket = require("packets")


local Server = {}


function Server:new(snet)
    self.server = snet.server("*", 13322)
    self.server.max_clients_connected = 5
    self.packets = SNETPacket:new(self)
    self.connectedPool = {}

    self:setServerField("sendAll", function(self, packetId, bs, priority, address, port)
        for addressAndPort in pairs(self.server.clients) do
            local clientAddress, clientPort = addressAndPort:match("([^:]+):([^:]+)")
            clientPort = tonumber(clientPort)
            self:send(packetId, bs, priority, clientAddress, clientPort)
        end
    end)
    self:setServerField("sendAllExceptSender", function(self, packetId, bs, priority, address, port)
        for addressAndPort in pairs(self.clients) do
            local clientAddress, clientPort = addressAndPort:match("([^:]+):([^:]+)")
            clientPort = tonumber(clientPort)
            if (clientPort ~= port) then
                self:send(packetId, bs, priority, clientAddress, clientPort)
            end
        end
    end)

    return setmetatable(self, {
        __index = function(self, key) return self.server[key] or self[key] end
    })
end

function Server:setServerField(name, val) self.server[name] = val end


local server = Server:new(SNET)

server:add_event_handler("onReceivePacket", function(packetId, bs, address, port)
    local callback = server.packets.handlers[packetId]
    if callback then
        callback(packetId, bs, address, port)
    end
end)

while true do server:process() end
 
Последнее редактирование:
  • Нравится
Реакции: Madeo Capaldi

movebx

Известный
Автор темы
51
160
А для чего он нужен? Где, как его применять?
Перегрузке именно математических операторов можно найти различные решения, даже если они никак не связаны с математикой.
Допустим, хочешь изменить конкатенацию строк - пожалуйста, перегружаешь оператор __add, где возвращаешь self.str + str, переданный в аргументы.
Так же можешь перегрузить оператор == (его в этой теме нет, но при желании можешь найти весь список метаметодов).
Пример на том же векторе:
Lua:
vector_mt.__eq = function( self, vec_other )
    return self.x == vec_other.x and self.y == vec_other.y and self.z == vec_other.z
end
теперь когда у тебя есть 2 вектора
vec1 = vector( 1, 2, 3 )
vec2 = vector( 4, 5, 6 )
то при их сравнении через оператор == вернется false и т.д.
метаметоду __call так же можно найти много вариантов применения. Например в данной статье я использовал ее для того, чтобы не вызывать постоянно метод new, а сразу обращаться к таблице и сделал C-подобный конструктор.
Вообще я рекомендую перегружать метаметод __call для функций, которые вызываются очень часто.
Например, у тебя есть какой то UI фреймворк в луа на праймал рендере, сделал ты себе там чекбоксы, слайдеры, дропдауны и т.д, унаследовал это все дело и у них есть общий метод get, так вот. Чтобы не писать каждый раз ui_element:get()
можешь сделать так:
Lua:
ui_element_mt.__call = function( self, ... )
    return self:get( ... )
end
и уже для того, чтобы получит значение делаешь не ui_element:get() а ui_element()
 
  • Нравится
Реакции: Madeo Capaldi