转自:http://www.jb51.net/article/56690.htm

Lua 中 metatable 是一个普通的 table,但其主要有以下几个功能:

1.定义算术操作符和关系操作符的行为
2.为 Lua 函数库提供支持
3.控制对 table 的访问

Metatables 定义操作符行为

Metatable 能够被用于定义算术操作符和关系操作符的行为。例如:Lua 尝试对两个 table 进行加操作时,它会按顺序检查这两个 table 中是否有一个存在 metatable 并且这个 metatable 是否存在 __add 域,如果 Lua 检查到了这个 __add 域,那么会调用它,这个域被叫做 metamethod。

Lua 中每个 value 都可以有一个 metatable(在 Lua 5.0 只有 table 和 userdata 能够存在 metatable)。每个 table 和 userdata value 都有一个属于自己的 metatable,而其他每种类型的所有 value 共享一个属于本类型的 metatable。在 Lua 代码中,通过调用 setmetatable 来设置且只能设置 table 的 metatable,在 C/C++ 中调用 Lua C API 则可以设置所有 value 的 metatable。默认的情况下,string 类型有自己的 metatable,而其他类型则没有:

  1. print(getmetatable('hi')) --> table: 003C86B8
  2. print(getmetatable()) --> nil

Metamethod 的参数为操作数(operands),例如:

  1. local mt = {}
  2. function mt.__add(a, b)
  3. return 'table + ' .. b
  4. end
  5. local t = {}
  6. setmetatable(t, mt)
  7. print(t + )

每个算术操作符有对应的 metamethod:

+ __add
* __mul
- __sub
/ __div
- __unm (for negation)
% __mod
^ __pow

对于连接操作符有对应的 metamethod:__concat

同样,对于关系操作符也都有对应的 metamethod:

== __eq
< __lt
<= __le

其他的关系操作符都是用上面三种表示:
a ~= b 表示为 not (a == b)
a > b 表示为 b < a
a >= b 表示为 b <= a

和算术运算符不同的是,关系运算符用于比较拥有不同的 metamethod(而非 metatable)的两个 value 时会产生错误,例外是比较运算符,拥有不同的 metamethod 的两个 value 比较的结果是 false。

不过要注意的是,在整数类型的比较中 a <= b 可以被转换为 not (b < a),但是如果某类型的所有元素并未适当排序,此条件则不一定成立。例如:浮点数中 NaN(Not a Number)表示一个未定义的值,NaN <= x 总是为 false 并且 x < NaN 也总为 false。

为 Lua 函数库提供支持

Lua 库可以定义和使用的 metamethod 来完成一些特定的操作,一个典型的例子是 Lua Base 库中 tostring 函数(print 函数会调用此函数进行输出)会检查并调用 __tostring metamethod:

  1. local mt = {}
  2. mt.__tostring = function(t)
  3. return '{' .. table.concat(t, ', ') .. '}'
  4. end
  5.  
  6. local t = {, , }
  7. print(t)
  8. setmetatable(t, mt)
  9. print(t)

另外一个例子是 setmetatable 和 getmetatable 函数,它们定义和使用了 __metatable 域。如果你希望设定的 value 的 metatable 不被修改,那么可以在 value 的 metatable 中设置 __metatable 域,getmetatable 将返回此域,而 setmetatable 则会产生一个错误:

  1. mt.__metatable = "not your business"
  2. local t = {}
  3. setmetatable(t, mt)
  4. print(getmetatable(t)) --> not your business
  5. setmetatable(t, {})
  6. stdin:: cannot change protected metatable

看一个完整的例子:

  1. Set = {}
  2.  
  3. local mt = {}
  4.  
  5. function Set.new(l)
  6. local set = {}
  7. -- Set 设置 metatable
  8. setmetatable(set, mt)
  9. for _, v in ipairs(l) do set[v] = true end
  10. return set
  11. end
  12.  
  13. function Set.union(a, b)
  14. -- 检查 a b 是否都是 Set
  15. if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
  16. -- error 的第二个参数为 level
  17. -- level 指定了如何获取错误的位置
  18. -- level 值为 1 表示错误的位置为 error 函数被调用的位置
  19. -- level 值为 2 表示错误的位置为调用 error 的函数被调用的地方
  20. error("attempt to 'add' a set with a not-set value", )
  21. end
  22. local res = Set.new{}
  23. for k in pairs(a) do res[k] = true end
  24. for k in pairs(b) do res[k] = true end
  25. return res
  26. end
  27.  
  28. function Set.intersection(a, b)
  29. local res = Set.new{}
  30. for k in pairs(a) do
  31. res[k] = b[k]
  32. end
  33. return res
  34. end
  35.  
  36. mt.__add = Set.union
  37. mt.__mul = Set.intersection
  38.  
  39. mt.__tostring = function(s)
  40. local l = {}
  41. for e in pairs(s) do
  42. l[#l + ] = e
  43. end
  44. return '{' .. table.concat(l, ', ') .. '}'
  45. end
  46.  
  47. mt.__le = function(a, b)
  48. for k in pairs(a) do
  49. if not b[k] then return false end
  50. end
  51. return true
  52. end
  53.  
  54. mt.__lt = function(a, b)
  55. return a <= b and not (b <= a)
  56. end
  57.  
  58. mt.__eq = function(a, b)
  59. return a <= b and b <= a
  60. end
  61.  
  62. local s1 = Set.new({, , })
  63. local s2 = Set.new({, , })
  64. print(s1 + s2)
  65. print(s1 ~= s2)

控制 table 的访问

__index metamethod

在我们访问 table 的不存在的域时,Lua 会尝试调用 __index metamethod。__index metamethod 接受两个参数 table 和 key:

  1. local mt = {}
  2. mt.__index = function(table, key)
  3. print('table -- ' .. tostring(table))
  4. print('key -- ' .. key)
  5. end
  6.  
  7. local t = {}
  8. setmetatable(t, mt)
  9. local v = t.a

__index 域也可以是一个 table,那么 Lua 会尝试在 __index table 中访问对应的域:

  1. local mt = {}
  2. mt.__index = {
  3. a = 'Hello World'
  4. }
  5.  
  6. local t = {}
  7. setmetatable(t, mt)
  8. print(t.a) --> Hello World

我们通过 __index 可以容易的实现单继承(类似于 JavaScrpit 通过 prototype 实现单继承),如果 __index 是一个函数,则可以实现更加复杂的功能:多重继承、caching 等。我们可以通过 rawget(t, i) 来访问 table t 的域 i,而不会访问 __index metamethod,注意的是,不要太指望通过 rawget 来提高对 table 的访问速度(Lua 中函数的调用开销远远大于对表的访问的开销)。

__newindex metamethod

如果对 table 的一个不存在的域赋值时,Lua 将检查 __newindex metamethod:

1.如果 __newindex 为函数,Lua 将调用函数而不是进行赋值
2.如果 __newindex 为一个 table,Lua 将对此 table 进行赋值

如果 __newindex 为一个函数,它可以接受三个参数 table key value。如果希望忽略 __newindex 方法对 table 的域进行赋值,可以调用 rawset(t, k, v)

结合 __index 和 __newindex 可以实现很多功能,例如:

1.OOP
2.Read-only table
3.Tables with default values

  1. function readOnly(t)
  2. local proxy = {}
  3. local mt = {
  4. __index = t,
  5. __newindex = function(t, k, v)
  6. error('attempt to update a read-only table', )
  7. end
  8. }
  9. setmetatable(proxy, mt)
  10. return proxy
  11. end
  12.  
  13. days = readOnly{'Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'}
  14. print(days[])
  15. days[] = 'Noday' --> stdin:1: attempt to update a read-only table

有时候,我们需要为 table 设定一个唯一的 key,那么可以使用这样的技巧:

  1. local key = {} -- unique key
  2. local t = {}
  3. t[key] = value

Lua中的metatable详解的更多相关文章

  1. php中关于引用(&)详解

    php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...

  2. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  3. AngularJS select中ngOptions用法详解

    AngularJS select中ngOptions用法详解   一.用法 ngOption针对不同类型的数据源有不同的用法,主要体现在数组和对象上. 数组: label for value in a ...

  4. 【转载】C/C++中extern关键字详解

    1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...

  5. oracle中imp命令详解 .

    转自http://www.cnblogs.com/songdavid/articles/2435439.html oracle中imp命令详解 Oracle的导入实用程序(Import utility ...

  6. Android中Service(服务)详解

    http://blog.csdn.net/ryantang03/article/details/7770939 Android中Service(服务)详解 标签: serviceandroidappl ...

  7. python中threading模块详解(一)

    python中threading模块详解(一) 来源 http://blog.chinaunix.net/uid-27571599-id-3484048.html threading提供了一个比thr ...

  8. Android中mesure过程详解

    我们在编写layout的xml文件时会碰到layout_width和layout_height两个属性,对于这两个属性我们有三种选择:赋值成具体的数值,match_parent或者wrap_conte ...

  9. Android中Intent组件详解

    Intent是不同组件之间相互通讯的纽带,封装了不同组件之间通讯的条件.Intent本身是定义为一个类别(Class),一个Intent对象表达一个目的(Goal)或期望(Expectation),叙 ...

随机推荐

  1. P2S、P2P、P2SP之对比

    P2S.P2P.P2SP之对比 一.下载原理分析 1.服务端下载技术(P2S):P2S下载方式分为HTTP与FTP两种类型,它们分别是Hyper Text Transportation Protoco ...

  2. 移动端js模拟截屏生成图片并下载功能的实现方案

    一.根据PM需求如下: 移动端wap 实现将二维码生成图片下载至用户手机相册保存 二.根据现有思路: 1.使用第三方工具html2canvas,将页面中指定范围的dom转换为canvas 2.随后使用 ...

  3. Linux服务器没有GUI的情况下使用matplotlib绘图

    最近看到关于 python3 中用matplotlib 不进行交互画图,而是直接将图保存到硬盘,主要的一个设置就是  matplotlib.use('agg') 注明: 其实不设置  matplotl ...

  4. Vue实现刷新当前路由

    Vue点击当前路由实现刷新 Vue点击当前路由实现刷新思路Code实现效果 前言:在后台管理系统中,有这样一个需求点击当前菜单栏时,页面依旧可以刷新. 点击当前路由实现数据请求页面刷新 思路 点击当前 ...

  5. Future接口和FutureTask类【FutureTask实现了Runnable和Future接口】

    Future API: public interface Future<V> { /** * Attempts to cancel execution of this task. This ...

  6. LuoguP4389 付公主的背包【生成函数+多项式exp】

    题目背景 付公主有一个可爱的背包qwq 题目描述 这个背包最多可以装10^5105大小的东西 付公主有n种商品,她要准备出摊了 每种商品体积为Vi,都有10^5105件 给定m,对于s\in [1,m ...

  7. 2018-2019-2 《网络对抗技术》Kali安装 Week1 20165212

    2018-2019-2 <网络对抗技术>Kali安装 Week1 20165212 1.完成安装linux kali和vm tools 装的第三遍成功安装.前面两次镜像文件不行,没驱动(网 ...

  8. 《DSP using MATLAB》Problem 4.12

    代码: function [As, Ac, r, v0] = invCCPP(b0, b1, a1, a2) % Determine the signal parameters Ac, As, r, ...

  9. 《DSP using MATLAB》Problem 3.20

    代码: %% ------------------------------------------------------------------------ %% Output Info about ...

  10. 极快瑞的函数式编程,Jquery涉及的一些函数

    $(function(){ 一些实现功能的代码:})————————————文档载入完成后执行的函数.$(function(){}) 是 $(document).ready(function(){}) ...