Lua 元表(Metatable)

Lua 语言中的 table 可以通过访问对应的 key 来得到 value 值,但是却无法对两个 table 进行各种操作

但 Lua 提供的 元表 (Metatable) 可以改变 table 默认的行为,每个行为关联了对应的元方法。比如我们可以通过元表中定义的方法来实现两个 table 的相加操作 a+b

通过使用元表,当 Lua 试图对两个 table 进行相加操作时,先检查两者之一是否有 元表,之后检查是否有一个叫__add 的字段,若找到,则调用对应的值。

__add 等字段,其对应的值(往往是一个函数或是 table)就是 元方法

处理元表

Lua 提供了两个函数来处理元表:

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

  2. getmetatable(table) 返回对象的元表 (metatable)

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

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

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

以上代码也可以直接写成一行:

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

mytable = setmetatable({tencent="Tencent"},{})

以下为返回对象元表:

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

getmetatable(mytable)                 -- 这回返回mymetatable

__index 元方法

Lua 语言中的 __index 元方法是 metatable 最常用的元方法

如果 __index 是另一个 table

当通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的 metatable 中的 __index 键。如果 __index 包含一个 table ,Lua 则会在 table 中查找相应的键

范例

$ lua -- 进入 Lua 交互式命令行模式
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> mytable = { baidu = 17 }
> t = setmetatable({},{ __index = mytable})
> t.baidu
17
> t.ali
nil
> 

如果 __index 是一个函数

如果 __index 是一个函数,那么 Lua 就会调用那个函数,table 和键会作为参数传递给函数

__index 元方法查看表中元素是否存在:

  • 如果不存在,返回结果为 nil
  • 如果存在则由 __index 返回结果
-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

mytable = setmetatable({ tencent = "Tencent"}, 
{
  __index = function(mytable, key)
    if key == "tencent" then
      return "metatable value"
    else
      return "not exist"
    end
  end
})

print(mytable.tencent)
print(mytable.baidu)

运行以上 Lua 脚本,输出结果如下:

$ lua main.lua
Tencent
not exist

范例解析:

  • mytable 表赋值为 { tencent = "Tencent"}

  • mytable 设置了元表,元方法为 __index

  • 在 mytable 表中查找 tencent,如果找到,返回该元素,找不到则继续。

  • 在 mytable 表中查找 baidu,如果找到,返回 metatablevalue,找不到则继续。

  • 判断元表有没有 __index 方法,如果 __index 方法是一个函数,则调用该函数

  • 元方法中查看是否传入 "baidu" 键的参数( mytable.baidu 没有设置),如果传入 "baidu" 参数返回 "not exist",否则返回 mytable 对应的键值。

上面的范例可以简写为:

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

mytable = setmetatable({tencent = "Tencent"}, { __index = { baidu = "metatablevalue" } })
print(mytable.tencent,mytable.baidu)

运行以上 Lua 脚本,输出结果如下

$ lua main.lua
Tencent metatablevalue

__index 元函数总结

Lua查找一个表元素时的规则,其实就是如下3个步骤:

  1. 在表中查找,如果找到,返回该元素,找不到则继续
  2. 判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
  3. 判断元表有没有__index方法,如果__index方法为nil,则返回nil; 如果 __index 方法是一个表,则重复1、2、3; 如果 __index 方法是一个函数,则返回该函数的返回值

__newindex 元方法

Lua 语言中的 __newindex 元方法用于对 表(table) 进行更新

当给表的一个新的的索引赋值时,解释器就会查找该表的 __newindex 元方法: 如果存在则调用这个函数而不进行赋值操作

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

mymeta = {}

mytable = setmetatable( {tencent="Tencent"}, {
     __newindex = mymetatable
})

print(mytable[1])

mytable["baidu"] = "Baidu"
print(mytable["baidu"],mymeta["baidu"])

mytable["ali"] = "Ali"
print(mytable["ali"],mymeta["ali"])

运行以上 Lua 脚本,输出结果如下:

$ lua main.lua
nil
Baidu   nil
Ali nil

以上范例为表设置了元方法 __newindex:

  • 在对新索引键(newkey)赋值时 mytable["baidu"] = "Baidu",会调用元方法,而不进行赋值。
  • 如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex

范例 :给 __newindex 赋值一个 rawset 函数

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

mytable = setmetatable({ tencent = "Tencent"}, 
{
    __newindex = function(mytable, key, value)
          rawset(mytable, key, "\""..value.."\"")
    end
})

mytable.ali = "Ali"
mytable.twle = 28

print(mytable.ali,mytable.twle)

运行以上 Lua 脚本,输出结果如下:

$ lua main.lua
"Ali"   "28"

为表(table) 添加操作符

重写 __add() 方法可以实现两个 table 相加

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

-- 该函数和 Lua5.2 中的 table.maxn 类似
-- table.maxn 在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大值函数 maxn_of

function maxn_of(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, 3, 5 }, {
__add = function(mytable, newtable)
  for i = 1, maxn_of(newtable) do
    table.insert(mytable, maxn_of(mytable)+1,newtable[i])
  end
  return mytable
end
})

secondtable = {7,11,13}

mytable = mytable + secondtable
for k,v in ipairs(mytable) do
print(k,v)
end

运行以上 Lua 脚本,输出结果如下:

$ lua main.lua
1   1
2   3
3   5
4   7
5   11
6   13

下表列出了元表可用的操作符

模式 描述
__add 对应的运算符 '+'
__sub 对应的运算符 '-'
__mul 对应的运算符 '*'
__div 对应的运算符 '/'
__mod 对应的运算符 '%'
__unm 对应的运算符 '-'
__concat 对应的运算符 '..'
__eq 对应的运算符 '=='
__lt 对应的运算符 '<'
__le 对应的运算符 '<='

__call 元方法

Lua 语言中的 __call 元方法用于直接调用 table 变量名时时候

__call 元方法语法格式如下

mixed __call(table,newtable)

范例 : 使用 __call 计算 table 的总和

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

-- 该函数和 Lua5.2 中的 table.maxn 类似
-- table.maxn 在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大值函数 maxn_of

function maxn_of(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({17}, 
{
  __call = function(mytable, newtable)
    sum = 0
    for i = 1, maxn_of(mytable) do
      sum = sum + mytable[i]
    end

    for i = 1, maxn_of(newtable) do
        sum = sum + newtable[i]
    end
    return sum
  end
})
newtable = {1,3,5}
print(mytable(newtable))

运行以上 Lua 脚本,输出结果如下:

$ lua main.lua
26

__tostring 元方法

__tostring 元方法用于修改表的输出行为

__tostring 元方法语法格式如下

string  __tostring()

范例 : 使用 __tostring() 方法自定义 table 的输出

-- !/usr/bin/lua
-- -*- encoding:utf-8 -*-
-- filename: main.lua
-- author: 简单教程(www.twle.cn)
-- Copyright © 2015-2065 www.twle.cn. All rights reserved.

tbl1 = {"baidu","tencent","ali","twle"}

mytable = setmetatable(tbl1, 
{
  __tostring = function(mytable)
    str1 = ""
    sep = ""
    for k, v in pairs(mytable) do
      str1 = str1 .. sep .. k .. ":" .. v
      if k == 1
      then 
        sep = ","
      end
    end
    return str1
  end
})

print(mytable)

运行以上 Lua 脚本,输出结果如下

$ lua main.lua
1:baidu,2:tencent,3:ali,4:twle
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.