Metatable和Metamethod是用来干啥的?它们可以使得表a和b的表达式“a + b”变得有意义,其中metatable使两个不相关的表a和b之间可以进行操作,而操作的具体行为比如说"+"由metamethod来具体定义。

  Metatable和Metamethod大多数地方都翻译成“元表”和“元函数”,这是一种直译,相当不直观。根据Metatable的用法,我倾向于将Metatable翻译成关联表,Metamethod翻译成关联函数。通过给两个table设置Metatable可以使两个table产生联系,然后对两个table进行一些操作,具体的操作行为由Metamethod来定义。下面的例子中,在对表t1和t2设置关联表mt,并在mt中定义关联函数__add后,就可以对这两个表进行"+"相加操作了。

t1 = {1, 2, 3}
t2 = {4,5,6,7,8} mt = {}
mt.__add = function(a, b)
local ret = 0
for _, v in pairs(a) do
ret = ret + v
end
for _, v in pairs(b) do
ret = ret + v
end
return ret
end setmetatable(t1, mt)
setmetatable(t2, mt)
print(t1 + t2)

从上面的代码中可以看到关联表就是一个表,而关联函数就是一个函数。当碰到表达式"t1+t2"时,Lua首先查找他们的关联表,找到关联表mt后,会在mt中找与相加操作对应的关联函数__add,找到__add后就将t1和t2作为参数来执行该函数,最后返回结果。

下面是一个使用关联表来对集合(用table实现的集合)进行操作的示例,实例中定义了集合的并集、交集、比较等运行:

Set = {}

--专门用来作为metatable,定义在Set里面以免影响外部的命名空间
Set.mt = {} --转化为string
Set.tostring = function (set)
local s = "{"
local sep = " "
for e in pairs(set) do
s = s .. sep .. e
sep = ", "
end
return s.."}"
end --打印
Set.print = function(s)
print(Set.tostring(s))
end Set.mt.__tostring = Set.tostring --新建一个集合
Set.new = function (t)
local set = {}
setmetatable(set, Set.mt) --指定所创建集合的metatable
for _, l in ipairs(t) do set[l] = true end
return set
end --并集
Set.union = function (a,b)
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end --给metatable增加__add函数(metamethod),当Lua试图对两个集合相加时,将调用这个函数,以两个相加的表作为参数
Set.mt.__add = Set.union --交集
Set.intersection = function (a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
--定义集合相乘操作为求交集
Set.mt.__mul = Set.intersection --先定义"<="操作,然后基于此定义"<"和"="
Set.mt.__le = function (a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end --小于
Set.mt.__lt = function(a, b)
return a<=b and not (b <= a)
end --等于
Set.mt.__eq = function(a, b)
return a <= b and b <= a
end --测试
s1 = Set.new{1, 2, 3}
s2 = Set.new{10, 20, 30, 40, 50}
print(getmetatable(s1))
print(getmetatable(s2))
s3 = s1 + s2 --等同于Set.union(s1, s2)
print(s3)
print(s3 * s2) print(s1 <= s3)
print(s1 == s3)
print(s1 < s3)
print(s1 >= s3)
print(s1 > s3) --起保护作用,getmetatable将返回这个域的值,而setmettable将会出错
Set.mt.__metatable = "not your business"
print(getmetatable(s1))
setmetatable(s1, {})

  当Lua试图对两个表进行相加时,他会检查两个表是否有一个表有Metatable,并且检查Metatable是否有__add域。如果找到则调用这个__add函数(所谓的Metamethod)去计算结果。当两个表有不同的Metatable时,以谁的为准呢?Lua选择metamethod的原则:

  (1)如果第一个参数存在带有__add域的metatable,Lua使用它作为metamethod,和第二个参数无关;

  (2)否则,第二个参数存在带有__add域的metatable,Lua使用它作为metamethod;

  (3)否则,报错。

  Lua中定义的常用的Metamethod如下所示:

  算术运算符的Metamethod:__add(加运算)、__mul(乘)、__sub(减)、__div(除)、__unm(负)、__pow(幂),__concat(定义连接行为)。

  关系运算符的Metamethod:__eq(等于)、__lt(小于)、__le(小于等于),其他的关系运算自动转换为这三个基本的运算。

  库定义的Metamethod:__tostring(tostring函数的行为)、__metatable(对表getmetatable和setmetatable的行为)。

  注意:__metatable不是函数,而是一个变量。假定你想保护你的集合使其使用者既看不到也不能修改metatables。如果你对metatable设置了__metatable的值,getmetatable将返回这个域的值,而调用用setmetatable将会出错:

  注意:相等比较从来不会抛出错误,如果两个对象有不同的metamethod,比较的结果为false,甚至可能不会调用metamethod。这也是模仿了Lua的公共的行为,因为Lua总是认为字符串和数字是不等的,而不去判断它们的值。仅当两个有共同的metamethod的对象进行相等比较的时候,Lua才会调用对应的metamethod。

  print总是调用tostring来格式化它的输出,tostring会首先检查对象是否存在一个带有__tostring域的metatable。

  表相关的Metamethod:

  (1)__index metamethod:在继承中使用较多。当访问表不存在的一个域时,会触发Lua解释器去查找__index metamethod,如果不存在,则返回nil,否则由__index metamethod返回结果。

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = function(table, key)
return Window[key]
end w = {x = 10, y = 20}
setmetatable(w, mt)
print(w.width)

  可以看到w没有width域,但有关联表mt,且关联表有__index,因此w.width会触发mt.__index的调用(Lua会将w作为第一个参数、width作为第二个参数来调用该函数)。

  __index除了作为一个函数,还可以直接作为一个表来使用当__index是一个表时,Lua会直接在这个表中查找width域。因此代码也可以像这样来写:

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = Window w = {x = 10, y = 20}
setmetatable(w, mt)
print(w.width)

  rawget(table, index)函数获取表中指定域的值,该函数可以绕过metamethod,直接返回表的域信息,看下面这个例子:

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = function(table, key) return Window[key] end w = {x = 10, y = 20}
setmetatable(w, mt) print(w.width) --100
print(rawget(w, "width")) --nil
print(rawget(w, "x")) --10

  看上面倒数第二行,rawget(w, "width")访问不存在的域不会触发查找__index。

  (2)__newindex metamethod:__newindex metamethod用来对表更新,__index则用来对表访问。

  当给表的一个不存在的域赋值时(比如w.add = 1),会触发Lua查找__newindex,如果不存在__newindex,则像一般的赋值行为一样导致表添加了一个域。

  (1)不存在__newindex,则像一般的赋值行为一样导致表添加了一个域(w多了一个域add,值为1)

  (2)存在__newindex,则不进行赋值操作,而是由__newindex拦截了赋值操作,并且将(table、域名、值)作为参数调用__newindex。

  也就是说,__newindex可以使得任何对表的添加元素的行为都要经过__newindex,这确实是一个很好的把关。

  rawset(t, k, v)函数也是一个等同于赋值的操作(w.add = 1相当于rawset(w, "add", 1)),但调用该函数可以绕过metamethod,即不会导致__newindex的调用:

mt = {}
mt.__newindex = function(table, key, value)
rawset(table, key, value)        --这里不能写成table.key = value;因为这个给不存在域的赋值操作又会导致__newindex的调用,因而陷入死循环
end w = {x = 10, y = 20}
setmetatable(w, mt) w.add = 1
print(w.add) -- 1

  和__index一样,__newindex也可以是一个表,如果__newindex是一个表,会导致对指定的那个表而不是原始的表进行赋值操作。

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__newindex = Window w = {x = 10, y = 20}
setmetatable(w, mt) w.add = 1
print(w.add) --nil
print(Window.add) --1

  赋值操作导致Windows添加了一个元素add,而w不影响。

  当__index和__newindex混合使用时,一定要注意区分每个行为都干了什么事情:

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = Window
mt.__newindex = Window w = {x = 10, y = 20}
setmetatable(w, mt) w.add = 1
print(w.add) --1
print(Window.add) --1

  __newindex为表时,w.add=1表示给Window添加了add,但通过__index,w也能访问到Window的add。

  再看下面这个例子:

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = function(table, key)
return Window[key]
end
mt.__newindex = function(table, key, value)
rawset(table, key, value)
end w = {x = 10, y = 20}
setmetatable(w, mt) w.add = 1
print(w.add) -- 1
print(Window.add) -- nil

  __newindex为函数时,w.add直接给w添加了add域,但Window并不存在add。所以结论是:当__newindex是函数时,给目标表w添加域;当__newindex是表时,给指向表添加域。

  关于__index和__newindex一定要注意区分,什么时候进入__index,什么时候进入__newindex:

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = function(table, key)
print("going here __index")
return Window[key]
end w = {x = 10, y = 20}
setmetatable(w, mt) s = w.width    -- going here __index 访问语句会进入__index
w.width = 1 -- 赋值语句不会进入__index,这里会导致w表添加width域

print(w.width)   -- 1
print(rawget(w, "width"))    -- 1
print(Window.width)     -- 100

  访问语句进入__index,赋值语句不进入__index。

mt = {}
mt.__newindex = function(table, key, value)
print("going here __newindex")
rawset(table, key, value)
end w = {x = 10, y = 20}
setmetatable(w, mt) s = w.add -- 访问语句不会进入__newindex
w.add = 1 -- going here __newindex 赋值语句进入__newindex
print(w.add) -- 1
print(rawget(w, "add")) -- 1

  赋值语句进入_newindex,访问语句不进入__newindex。

  结合上面这两句话就很容易理解下面这个例子了:

Window = {x = 0, y = 0, width = 100, height = 100}

mt = {}
mt.__index = function(table, key)
print("going here __index")
return Window[key]
end
mt.__newindex = function(table, key, value)
print("going here __newindex")
rawset(table, key, value)
end w = {x = 10, y = 20}
setmetatable(w, mt) s = w.width -- going here __index
w.width = 1 -- going here __newindex
print(w.width) -- 1
print(rawget(w, "width")) -- 1

  倒数第三条语句w.width = 1不会进入__index,所以会导致给w表添加新的域width。也就是说__index逻辑不会影响__newindex的判断,虽然__index可以访问到域width,但__newindex依然仍未w没有width域。

  这些概念非常的绕,而且Lua是一种弱类型化语言,所以对于很多概念的具体行为你一定要自己多加测试,不能够想当然。

http://www.cnblogs.com/sifenkesi/p/3834128.html

Metatable和Metamethod(转)的更多相关文章

  1. Metatable和Metamethod

    Metatable和Metamethod是用来干啥的?它们可以使得表a和b的表达式“a + b”变得有意义,其中metatable使两个不相关的表a和b之间可以进行操作,而操作的具体行为比如说&quo ...

  2. [Lua快速了解一下]Lua的MetaTable和MetaMethod

    MetaTable和MetaMethod是Lua中的重要的语法,MetaTable主要是用来做一些类似于C++重载操作符式的功能. 两个分数 fraction_a = {numerator=, den ...

  3. lua中的metatable和metamethod

    --元表和元方法给lua里的值设定一些操作,让我们可以对这些操作自定义 --创建一个新的table变量时,它是不存在元表的 --在Lua中,只能设置table的元表,其他类型的值的元表,只能通过C代码 ...

  4. lua metatable和metamethod元表和元方法

    Lua中提供的元表是用于帮助Lua数据变量完成某些非预定义功能的个性化行为,如两个table的相加.假设a和b都是table,通过元表可以定义如何计算表达式a+b.当Lua试图将两个table相加时, ...

  5. Lua 与 Redis

    Lua 与 Redis 标签: Java与NoSQL 从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis - 案例-实现访问频率限制: 实现访问者 $ip 在一定的 ...

  6. C/C++ Lua Parsing Engine

    catalog . Lua语言简介 . 使用 Lua 编写可嵌入式脚本 . VS2010编译Lua . 嵌入和扩展: C/C++中执行Lua脚本 . 将C++函数导出到Lua引擎中: 在Lua脚本中执 ...

  7. [转]LUA 学习笔记

    Lua 学习笔记 入门级 一.环境配置 方式一: 1.资源下载http://www.lua.org/download.html 2.用src中的源码创建了一个工程,注释调luac.c中main函数,生 ...

  8. 转:Lua简明教程

    需要注意的是:lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里. 这里很奇怪,为什么在函数内部声明的变量默认也是global的呢? 函数的返回值 和Go语言一样,可以一条语句上赋多 ...

  9. [2017.02.07] Lua入门学习记录

    #!/home/auss/Projects/Qt/annotated/lua -- 这是第一次系统学习Lua语言 --[[ 参考资料: 1. [Lua简明教程](http://coolshell.cn ...

随机推荐

  1. CentOS 7单用户模式修改root密码

    CentOS 7的单用户模式和6.5稍有不同 把ro改成 "rw init=/sysroot/bin/sh". 完成之后按 "Ctrl+x" chroot /s ...

  2. 2014鞍山现场赛C题HDU5072(素筛+容斥原理)

    Coprime Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others) Total ...

  3. logstash+ElasticSearch+Kibana VS Splunk

    logstash+ElasticSearch+Kibana VS Splunk 最近帮磊哥移植一套开源的日志管理软件,替代Splunk. Splunk是一个功能强大的日志管理工具,它不仅可以用多种方式 ...

  4. hdu1394(线段树求逆序对)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 线段树功能:update:单点增减 query:区间求和 分析:如果是0到n-1的排列,那么如果 ...

  5. (017)将一棵二叉查找树重构成链表(keep it up)

    给定一棵二叉查找树,设计算法,将每一层的全部结点构建为一个 链表(也就是说, 假设树有D层,那么你将构建出D个链表). 这个题实质是个BFS,可是实现起来有点麻烦,又不像常见的BFS, 所以编写代码时 ...

  6. android4.0 USB Camera示例(五个辅助)jpg压缩

    前的最后一个 我们说,一个直接yuv变成jpg该功能 但是转换不成功 主要功能是yuv420转jpg的 根据研究发现 yuv420的序列是这种 YYYY YYYY UVUV 而yuv422的隔行扫描的 ...

  7. 神经网络BP算法C和python代码

    上面只显示代码. 详BP原理和神经网络的相关知识,请参阅:神经网络和反向传播算法推导 首先是前向传播的计算: 输入: 首先为正整数 n.m.p.t,分别代表特征个数.训练样本个数.隐藏层神经元个数.输 ...

  8. 移动web性能优化笔记

    移动web性能优化 最近看了一些文章,对移动web性能优化方法,做一个简单笔记 笔记内容主要出自 移动H5前端性能优化指南和移动前端系列——移动页面性能优化

  9. 深入浅出KnockoutJS

    深入浅出KnockoutJS 写在前面,本文资料大多来源网上,属于自己的学习笔记整理. 其中主要内容来自learn.knockoutjs.com,源码解析部分资料来自司徒正美博文<knockou ...

  10. windows phone (12) 小试自定义样式

    原文:windows phone (12) 小试自定义样式 样式在BS开发中经常用到,在wp中系统也提供了解决办法,就是对设置的样式的一种资源共享,首先是共享资源的位置,它是在App类中,之前我们已经 ...