Skip to content

元表(Metatable)

元表(Metatable)是 Lua 中一个非常强大的特性,它允许我们自定义表的行为。本章节将介绍 Lua 中元表的概念、使用方法和常见应用。

什么是元表

元表是一个表,它定义了另一个表的行为。当 Lua 对一个表进行某些操作时,如果该表有元表,Lua 会查找元表中对应的方法来执行操作。

元表的设置

使用 setmetatable 函数设置表的元表:

lua
local t = {}
local mt = {}
setmetatable(t, mt)

使用 getmetatable 函数获取表的元表:

lua
local t = {}
local mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt)  -- 输出 true

元方法

元表中的键称为元方法(Metamethod),它们定义了表的特殊行为。以下是一些常见的元方法:

__index 元方法

当访问表中不存在的字段时,Lua 会调用 __index 元方法:

lua
local mt = {
  __index = function(t, key)
    return "默认值"
  end
}

local t = {a = 1, b = 2}
setmetatable(t, mt)

print(t.a)  -- 输出 1
print(t.c)  -- 输出 默认值

__index 也可以是一个表,当访问表中不存在的字段时,Lua 会在 __index 表中查找:

lua
local defaults = {c = 3, d = 4}
local mt = {
  __index = defaults
}

local t = {a = 1, b = 2}
setmetatable(t, mt)

print(t.a)  -- 输出 1
print(t.c)  -- 输出 3
print(t.d)  -- 输出 4

__newindex 元方法

当给表中不存在的字段赋值时,Lua 会调用 __newindex 元方法:

lua
local mt = {
  __newindex = function(t, key, value)
    print("赋值:" .. key .. " = " .. value)
    rawset(t, key, value)
  end
}

local t = {a = 1, b = 2}
setmetatable(t, mt)

t.c = 3  -- 输出 赋值:c = 3
print(t.c)  -- 输出 3

__add 元方法

当对表进行加法操作时,Lua 会调用 __add 元方法:

lua
local mt = {
  __add = function(t1, t2)
    local result = {}
    for k, v in pairs(t1) do
      result[k] = v
    end
    for k, v in pairs(t2) do
      result[k] = v
    end
    return result
  end
}

local t1 = {a = 1, b = 2}
local t2 = {c = 3, d = 4}
setmetatable(t1, mt)
setmetatable(t2, mt)

local t3 = t1 + t2
print(t3.a, t3.b, t3.c, t3.d)  -- 输出 1  2  3  4

__call 元方法

当将表作为函数调用时,Lua 会调用 __call 元方法:

lua
local mt = {
  __call = function(t, ...)
    print("调用表:", ...)
    return "返回值"
  end
}

local t = {}
setmetatable(t, mt)

local result = t(1, 2, 3)  -- 输出 调用表:  1  2  3
print(result)  -- 输出 返回值

__tostring 元方法

当使用 tostring 函数或 print 函数输出表时,Lua 会调用 __tostring 元方法:

lua
local mt = {
  __tostring = function(t)
    local str = "{"
    local first = true
    for k, v in pairs(t) do
      if not first then
        str = str .. ", "
      end
      str = str .. k .. " = " .. v
      first = false
    end
    str = str .. "}"
    return str
  end
}

local t = {a = 1, b = 2, c = 3}
setmetatable(t, mt)

print(t)  -- 输出 {a = 1, b = 2, c = 3}

其他元方法

元方法描述
__sub减法操作
__mul乘法操作
__div除法操作
__mod取模操作
__pow幂运算操作
__unm一元负号操作
__concat字符串连接操作
__eq等于比较操作
__lt小于比较操作
__le小于等于比较操作
__len长度操作
__gc垃圾回收操作

元表的应用

示例 1:实现只读表

lua
function readonly(t)
  local mt = {
    __index = t,
    __newindex = function(t, key, value)
      error("试图修改只读表")
    end
  }
  return setmetatable({}, mt)
end

local original = {a = 1, b = 2}
local readOnly = readonly(original)

print(readOnly.a)  -- 输出 1
readOnly.c = 3  -- 报错:试图修改只读表

示例 2:实现面向对象编程

lua
local Person = {}
Person.__index = Person

function Person:new(name, age)
  local obj = {}
  setmetatable(obj, self)
  obj.name = name
  obj.age = age
  return obj
end

function Person:sayHello()
  print("Hello, my name is " .. self.name)
end

local person = Person:new("张三", 30)
person:sayHello()  -- 输出 Hello, my name is 张三

示例 3:实现单例模式

lua
local Singleton = {}
local instance

function Singleton:new()
  if not instance then
    instance = {}
    setmetatable(instance, self)
    self.__index = self
  end
  return instance
end

local s1 = Singleton:new()
local s2 = Singleton:new()
print(s1 == s2)  -- 输出 true

示例 4:实现代理模式

lua
function proxy(t)
  local mt = {
    __index = function(_, key)
      return t[key]
    end,
    __newindex = function(_, key, value)
      t[key] = value
    end,
    __pairs = function()
      return pairs(t)
    end,
    __ipairs = function()
      return ipairs(t)
    end,
    __len = function()
      return #t
    end
  }
  return setmetatable({}, mt)
end

local original = {1, 2, 3}
local p = proxy(original)

print(p[1])  -- 输出 1
p[4] = 4
print(#p)  -- 输出 4
for i, v in ipairs(p) do
  print(v)
end

元表的继承

元表可以实现继承:

lua
local Animal = {}
Animal.__index = Animal

function Animal:new(name)
  local obj = {}
  setmetatable(obj, self)
  obj.name = name
  return obj
end

function Animal:speak()
  print("动物发出声音")
end

local Dog = {}
Dog.__index = Dog
setmetatable(Dog, Animal)

function Dog:new(name)
  return Animal.new(self, name)
end

function Dog:speak()
  print("汪汪汪")
end

local dog = Dog:new("旺财")
dog:speak()  -- 输出 汪汪汪

小结

本章节介绍了 Lua 中元表的概念、设置方法、元方法和常见应用,包括 __index__newindex__add__call__tostring 等元方法,以及元表在实现只读表、面向对象编程、单例模式和代理模式等方面的应用。掌握元表的使用,对于编写 Lua 程序非常重要。