English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Метатаблицы Lua (metatable)

在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。

因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。

例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。

当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。

有两个很重要的函数来处理元表:

  • setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。

  • getmetatable(table): 返回对象的元表(metatable)。

以下示例演示了如何对指定的表设置元表:

mytable = {}                          -- 普通表 
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表

Этот код также можно записать одним предложением:}

mytable = setmetatable({},{})

Ниже приведен пример метатаблицы объекта, возвращаемой:

getmetatable(mytable)                 -- возвращает mymetatable

Метод __index

Это наиболее часто используемый ключ в метатаблице.

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

Мы можем использовать команду 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.

mytable = setmetatable({key1 = "value1"}, {
  __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 установлена метатаблица, метод meta __index.

  • В таблице mytable ищется key1, если найден, возвращается этот элемент, если не найден, продолжается.

  • В таблице mytable ищется key2, если найден, возвращается metatablevalue, если не найден, продолжается.

  • Проверяется, есть ли метод __index в метатаблице, если __index является функцией, то вызывается эта функция.

  • В методе meta проверяется, передан ли параметр с ключом "key2" (mytable.key2 уже настроен), если передан параметр "key2", возвращается "metatablevalue", в противном случае возвращается значение ключа mytable.

Мы можем упростить этот код следующим образом:

mytable = setmetatable({key1 = "value1"}, {__index = {key2 = "metatablevalue"}})
print(mytable.key1,mytable.key2)

Обобщение

Правила поиска элемента таблицы в Lua其实就是 следующие 3 шага:

  • 1. Найти в таблице, если найден, верните этот элемент, если не найден, продолжайте.

  • 2. Проверьте, есть ли у таблицы метатаблица. Если у таблицы нет метатаблицы, верните nil, если есть, продолжайте.

  • 3. Проверьте, есть ли у метатаблицы метод __index. Если метод __index равен nil, верните nil; если метод __index является таблицей, повторите шаги 1, 2, 3; если метод __index является функцией, верните значение, возвращаемое этой функцией.

Эта часть контента взята из статьи автора Хуан Цзинь: https://blog.csdn.net/xocoder/article/details/9028347

Метаметод __newindex

Метаметод __newindex используется для обновления таблицы, а __index используется для доступа к таблице.

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

Следующий пример демонстрирует применение метаметода __newindex:

mymetatable = {}
mytable = setmetatable({key1 = "value1"}, {__newindex = mymetatable})
print(mytable.key1)
mytable.newkey = "новое значение2"
print(mytable.newkey, mymetatable.newkey)
mytable.key1 = "новое значение1"
print(mytable.key1, mymetatable.key1)

Результат выполнения примера выше:

value1
nil    новое значение2
nil    новое значение1

В данном примере таблица установила метаметод __newindex, при присвоении нового индекса (mytable.newkey = "новое значение2"), вызывается метаметод, а не выполняется присвоение. А если присваивается существующий индекс (key1), то выполняется присвоение, а не вызывается метаметод __newindex.

Следующий пример использует функцию rawset для обновления таблицы:

mytable = setmetatable({key1 = "value1"}, {
  __newindex = function(mytable, key, value)
                rawset(mytable, key, "\""..value.."\"")
  end
)
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1,mytable.key2)

Результат выполнения примера выше:

Новый значений    "4"

Добавление оператора к таблице

Следующий пример демонстрирует операцию сложения двух таблиц:

-- Максимальное значение в таблице, table.maxn в версиях Lua 5.2 и выше уже не может быть использовано
-- Определение функции максимального ключа таблицы custom_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
-- Операция сложения двух таблиц
mytable = setmetatable({ 1, 2, 3 }, {
  __add = function(mytable, newtable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, table_maxn(mytable)+1,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 в версиях Lua 5.2 и выше уже не может быть использовано
-- Определение функции максимального ключа таблицы custom_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 для изменения поведения вывода таблицы. В следующем примере мы определили пользовательский вывод таблицы:

mytable = setmetatable({10, 20, 30}, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        end
    return "Сумма всех элементов таблицы составляет " .. sum
  end
)
print(mytable)

Результат выполнения примера выше:

Сумма всех элементов таблицы составляет 60

Из этой статьи мы можем узнать, что метатаблицы могут значительно упростить наши функции кода, поэтому знание метатаблиц Lua позволяет нам писать более простые и优秀的 код Lua.