Метатаблица в Lua
В таблице Lua мы можем обратиться к соответствующему ключу, чтобы получить значение, но не можем выполнять операции над двумя таблицами (например, сложение).
Поэтому Lua предоставляет метатаблицы, позволяя нам изменять поведение таблиц. Каждое поведение связано с соответствующим метаметодом.
Например, используя метатаблицы, мы можем определить, как Lua вычисляет операцию сложения a+b двух таблиц.
Когда Lua пытается сложить две таблицы, она сначала проверяет, есть ли у любой из них метатаблица, затем проверяет, существует ли поле с именем__addЕсли найдено, вызывается соответствующее значение.__addТакие поля; соответствующие им значения (часто функция или таблица) — это "метаметоды".
Существуют две очень важные функции для работы с метатаблицами:
- setmetatable(table,metatable):Устанавливает метатаблицу для указанной таблицы. Если метатаблица содержит ключ __metatable, setmetatable завершится ошибкой.
- getmetatable(table):Возвращает метатаблицу объекта.
Следующий пример демонстрирует, как задать метатаблицу для указанной таблицы:
mymetatable = {} -- Метатаблица
setmetatable(mytable,mymetatable) -- Сделать mymetatable метатаблицей mytable
Приведённый выше код также можно записать в одну строку:
mytable = setmetatable({},{})
Следующее возвращает метатаблицу объекта:
getmetatable(mytable) -- 这会返回 mymetatable
__index — метаметод
Это наиболее часто используемый ключ в метатаблице.
Когда вы обращаетесь к таблице по ключу, если у ключа нет значения, Lua будет искать ключ __index в метатаблице таблицы (при условии, что метатаблица существует). Если __index содержит таблицу, Lua ищет соответствующий ключ в этой таблице.
Мы можем использовать команду lua, чтобы войти в интерактивный режим и проверить:
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
Если __index содержит функцию, Lua вызовет эту функцию, передавая таблицу и ключ в качестве аргументов функции.
Метаметод __index проверяет, существует ли элемент в таблице. Если его нет, возвращаемый результат — nil; если он есть, результат возвращается с помощью __index.
Пример
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
Вывод примера:
value1 metatablevalue
Анализ примера:
Таблица mytable задана как{key1 = "value1"}。
У mytable установлена метатаблица, а метаметод — __index.
Ищите key1 в таблице mytable. Если найден, верните элемент; если не найден, продолжите.
Ищите key2 в таблице mytable. Если найден, верните metatablevalue; если не найден, продолжите.
Определите, есть ли у метатаблицы метод __index. Если метод __index является функцией, вызовите эту функцию.
В метаметоде проверьте, передан ли параметр ключа "key2" (mytable.key2 установлен). Если параметр "key2" передан, верните "metatablevalue"; в противном случае верните соответствующее значение mytable для этого ключа.
Мы можем переписать приведённый выше код проще так:
print(mytable.key1,mytable.key2)
Итог
Правила, по которым Lua ищет элемент таблицы, на самом деле состоят из следующих 3 шагов:
- 1. Посмотрите в таблице. Если найден, верните элемент; если не найден, продолжите.
- 2. Определите, есть ли у таблицы метатаблица. Если у неё нет метатаблицы, верните nil; если метатаблица есть, продолжите.
- 3. Определите, есть ли у метатаблицы метод __index. Если метод __index равен nil, верните nil; если метод __index является таблицей, повторите шаги 1, 2 и 3; если метод __index является функцией, верните возвращаемое значение этой функции.
Эта часть взята у автора Huanzi: https://blog.csdn.net/xocoder/article/details/9028347
Метамеметод __newindex
Метамеметод __newindex используется для обновления таблиц, а __index — для доступа к таблицам.
Когда вы присваиваете значение отсутствующему индексу таблицы, интерпретатор ищет метаметод __newindex: если он существует, он вызывает эту функцию, не выполняя присваивание.
Следующий пример демонстрирует применение метаметода __newindex:
Пример
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print(mytable.key1)
mytable.newkey = "new value 2"
print(mytable.newkey,mymetatable.newkey)
mytable.key1 = "new value 1"
print(mytable.key1,mymetatable.key1)
Вывод после выполнения приведённого выше примера:
value1 nil 新值2 新值1 nil
В приведенном выше примере для таблицы установлен метаметод __newindex. При присваивании по новому ключу индекса (newkey) (mytable.newkey = "new value 2") вызывается метаметод, и присваивание не выполняется. При присваивании по существующему ключу индекса (key1) присваивание выполняется, и метаметод __newindex не вызывается.
В следующем примере используется функция rawset для обновления таблицы:
Пример
__newindex = function(mytable, key, value)
rawset(mytable, key, "\""..value.."\"")
end
})
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1,mytable.key2)
Вывод после выполнения приведённого выше примера:
new value "4"
Добавление операторов к таблицам
В следующем примере демонстрируется сложение двух таблиц:
Пример
function table_maxn(t)
local mn = 0
for k, _ in pairs(t) do
if type(k) == "number" and k > mn then
mn = k
end
end
return mn
end
-- Операция сложения двух таблиц
mytable = setmetatable({ 1, 2, 3 }, {
__add = function(mytable, newtable)
local max_key_mytable = table_maxn(mytable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, max_key_mytable + i, newtable[i])
end
return mytable
end
})
secondtable = {4, 5, 6}
mytable = mytable + secondtable
for k, v in ipairs(mytable) do
print(k, v)
end
Вывод после выполнения приведённого выше примера:
1 1 2 2 3 3 4 4 5 5 6 6
Ключ __add включён в метатаблицу и выполняет операцию сложения. Список соответствующих операций в таблице следующий: (Примечание:__это два символа подчёркивания)
| Паттерн | Описание |
|---|---|
| __add | Соответствующий оператор '+'. |
| __sub | Соответствующий оператор '-'. |
| __mul | Соответствующий оператор '*'. |
| __div | Соответствующий оператор '/'. |
| __mod | Соответствующий оператор '%'. |
| __unm | Соответствующий оператор '-'. |
| __concat | Соответствующий оператор '..'. |
| __eq | Соответствующий оператор '=='. |
| __lt | Соответствующий оператор '<'. |
| __le | Соответствующий оператор '<='. |
метаметод __call
Метаметод __call вызывается, когда Lua вызывает значение. Следующий пример демонстрирует вычисление суммы элементов в таблице:
Пример
-- Пользовательская функция table_maxn для вычисления максимального значения ключа в таблице, то есть вычисления количества элементов в таблице
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- Определение метаметода __call
mytable = setmetatable({10}, {
__call = function(mytable, newtable)
sum = 0
for i = 1, table_maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1, table_maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
})
newtable = {10,20,30}
print(mytable(newtable))
Вывод после выполнения приведённого выше примера:
70
метаметод __tostring
Метаметод __tostring используется для изменения поведения вывода таблицы. В следующем примере мы настраиваем выводимое содержимое таблицы:
Пример
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "Сумма всех элементов в таблице равна " .. sum
end
})
print(mytable)
Вывод после выполнения приведённого выше примера:
表所有元素的和为 60
Из этой статьи мы можем узнать, что метатаблицы могут эффективно упростить функциональность нашего кода, поэтому понимание метатаблиц Lua может помочь нам писать более простой и лучший код на Lua.
Другие расширения