metatable简介:
1.在Lua 中每个 value( table 和 userdata ) 都可以有一个属于自己的 metatable,相同类型的value共享一个属于本类型的 metatable。
2.任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)。
3.在 Lua 代码中,通过调用 setmetatable 来设置且只能设置 table 的 metatable,C/C++ 调用 Lua C API 则可以设置所有 value 的 metatable。(默认的情况下,string 类型有自己的 metatable,而其他类型则没有)。
lua提供metatable的功能,主要有一下几种作用:
1.使用metatable控制对table的访问
当查询table的某个键的时候,如果该table的这个键没有值,那么Lua就会寻找该table的metatable中的__index元方法:如果__index指向一个table,Lua会在此table中查找相应的键, 并且,__index也可以指向一个方法
当对table的某个键进行赋值时,如果该table存在该字段则为之赋值,若没有,那么Lua就会寻找该table的metatable中的__newindex元方法:如果__newindex指向一个table时,Lua会对这个table进行赋值操作,如果__newindex指向一个方法,Lua则会调用该方法
__index元方法
__index指向表时:
如果__index包含一个表格,Lua会在此表格中查找相应的键。(__index也可以指向一个方法)
在使用之前我们需要知道,当我们在一个表中查找一个元素时
首先在该表中查找,有则返回对应值,无则查看该表是否有元表metatable
若无元表则返回nil
有元表时,lua并不是在其元表中查找,而是在其元表的__index域中查找
因此:仅使用setmetatable()方法设置元表,并不能取得对应元表中的元素
BaseClass = {theKey1 = "the string value1"}
--[[
--Lua默认创建一个不带metatable的新表
DerivedClass = {}
--使用setmetatable方法为一个表设置metatable
setmetatable(DerivedClass,BaseClass)
]]--
--注释的两句可简写为:
DerivedClass = setmetatable({},BaseClass)
res = DerivedClass.theKey1
print(res)
设置对应元表的__index键之后:
BaseClass = {theKey1 = "the string value1"}
BaseClass.__index = BaseClass
DerivedClass = setmetatable({},BaseClass)
res = DerivedClass.theKey1
print(res)
也可以换一种简写方法:
BaseClass = {theKey1 = "the string value1"}
--即直接设 AnonymousTable = {__index = BaseClass} 为DerivedClass的元表,查找时可直接在AnonymousTable中的__index域对应的表BaseClass中查找
DerivedClass = setmetatable({},{__index = BaseClass})
res = DerivedClass.theKey1
print(res)
所以,lua中的继承可以表示为:
--file:deriveTest.lua
local Car = {}
Car.__index = Car
function Car.New(o)
o = o or {}
setmetatable(o,Car)
return o
end
function Car.Run()
print("Car's run func.")
end
--直接调用“父类”中的run方法
Car.Run()
local FordCar = Car.New()
--子类调用“父类”中的run方法
FordCar.Run()
--重写fordCar中的run方法
function FordCar.Run()
print("FordCar's run func.")
end
--重写之后调用
FordCar.Run()
__index指向一个方法时
__index指向一个方法时,在我们访问该 table 的不存在的域时,Lua 会尝试调用 __index 元方法metamethod (__index metamethod 可以接受两个参数 table 和 key)
local t1 = {}
t1.__index = function(table,key)
print("call the __index metamethod")
print("table"..tostring(table))
print("key"..key)
return key.." from the __index"
end
--set t1 as t2's metatable
local t2 = setmetatable({},t1)
--pass the table and the key to __indexmetamethod
local res = t2.key1
print("the result is :"..res)
print("------------------")
res = t2.key2
print("the result is :"..res)
__newindex元方法
如果对 table 的一个不存在的域赋值时,Lua 将检查 __newindex metamethod:
如果 __newindex指向 函数
Lua 将调用函数而不是进行赋值(如果 __newindex 为一个函数,它可以接受三个参数 table key value。如果希望忽略 __newindex 方法对 table 的域进行赋值,可以调用 rawset(t, k, v))
如果 __newindex 指向 table
Lua 将对此 table 进行赋值
--file:newindex.lua
local mt = {
key1 = "key1's value"
}
local other = {
key1 = "the old value"
}
mt.__index = mt
--[[
mt.__newindex = function()
print "Can not set value"
end
--]]
mt.__newindex = other
local t = setmetatable({},mt)
t.key1 = "the new value from the table t."
print(t.key1)
--对__newindex指向的table中的key1进行更新操作,但是table t 中仍然没有键key1,进行t.key1的查询时,查询的仍然是__index中对应的键key1的值
print(other.key1)
__index 和 __newindex结合实现OOP等
1.OOP
2.Read-only table
3.Tables with default values
OOP
--file:LuaClass.lua
--声明Lua类,这里声明了类名还有属性,并且给出了属性的初始值。
LuaClass = {x = 0, y = 0}
--定义LuaClass的元表的__index索引为自身,仅在自身上查相应属性
LuaClass.__index = LuaClass
--构造函数New()
--使用:冒号来声明、调用的函数,会自动传入“调用者”table自身,即self
--如果在声明时使用了冒号,而在调用时使用点来调用,则会报错:找不到名为self的定义以及变量
function LuaClass:New(x, y)
local temp = {}; --构造临时表,如果没有这句,那么类所建立的对象改变,其他对象都会改变
setmetatable(temp, LuaClass); --将临时表temp的元表metatable设定为LuaClass
self.x = x;
self.y = y;
return temp; --返回自身
end
--测试打印方法--
function LuaClass:Test()
print("x: " .. self.x .. " y: " .. self.y);
end
测试代码:
--file:classTest.lua
require "LuaClass"
local luaClass = LuaClass:New(31,60);
luaClass:Test();
rawget和rawset的使用
--file: raw.lua
datas = {}
datas.mt = {}
function datas.new(o)
o = o or {}
setmetatable(o,datas.mt)
return o
end
datas.mt.__index = function(t,k)
return 'default'
end
datas.mt.__newindex = function(t,k,v)
if k == "test" then
rawset(t,"test","value")
end
end
d = datas.new()
--查询table d 中不存在的元素 x 时,会调用__index指向的函数
print(d.x) -->defaule
--调用了rawget(t,k,v),绕过了元表的__index和__newindex,因为table d为空表,所以返回nil
print(rawget(d,d.test)) -->nil
--为table d中不存在的元素 test 赋值,调用元表对应的__newindex,参数" t = d , k = test , v = nil"
--元表中的__newindex对应的函数调用rawset,绕过元表,直接为table d 赋值,此时d["test"] = "vale"
d.test = "something"
--table d中“test”对应的值为“value”
print(d.test) --value
--table d中“test”对应的值为“value”
print(rawget(d,"test")) --value
2. 为 Lua 函数库提供支持
Lua 库可以定义和使用 metamethod 来完成一些特定的操作
一个典型的例子是 Lua Base 库中 __tostring 函数,print 函数会调用此函数进行输出,调用print时,会检查并调用 __tostring metamethod
--file:tablelib.lua
local mt = {}
mt.__tostring = function(t)
return '{'..table.concat(t,',')..'}'
end
local t = {1,2,3}
print(t)
print("-----------------")
setmetatable(t,mt)
print(t)
3. 重载算数运算符和关系运算符的行为
算术运算符的metamethods
例如:Lua 尝试对两个 table 进行加操作时,它会按顺序检查这两个 table 中是否有一个存在 metatable 并且这个 metatable 是否存在 __add 域,如果 Lua 检查到了这个 __add 域,那么会调用它,这个域被叫做 metamethod。(Metamethod 的参数为操作数)
eg:
--file:operators.lua
Set = {}
Set.mt = {}
function Set.new(t)
local set = {}
setmetatable(set,Set.mt) -->Make the Set.mt handle the lookup feature
for _,v in ipairs(t) do
set[v] = true
end
return set
end
function Set.union(a,b)
local res = Set.new{} --It will be error if you use " = 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
function Set.intersection(a,b)
local res = Set.new{} --It will be error if you use " = Set.new()"
for k in pairs(a) do
res[k] = b[k]
end
return res
end
function Set.tostring (set)
local s = "{"
local sep = ""
for e in pairs(set) do
s = s..sep..e
sep = ","
end
return s.."}"
end
function Set.print(s)
print(Set.tostring(s))
end
Set.mt.__add = Set.union
Set.mt.__mul = Set.intersection
s1 = Set.new{1,2,3,4,5,6}
s2 = Set.new{5,6,7,8,9}
print(getmetatable(s1)) -->table:02749908
print(getmetatable(s2)) -->table:02749908
--[[
Lua选择metamethod的原则:如果第一个参数存在带有__add域的metatable,Lua使用它作为metamethod,和第二个参数无关;
--为避免该错误可以在Set.union中进行判断
function Set.union(a,b)
if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then
error("Attempt to add a Set with a non-set value",2)
end
--之前的代码
end
--]]
s3 = s1+s2 --It will be error if you write s3 = s1 + 1
s4 = s1*s2
Set.print(s3) -->{1,2,3,4,5,6,7,8,9}
Set.print(s4) -->{5,6}
关系运算符的metamethods
Metatables允许我们使用metamethods:__eq(等于),__lt(小于),和__le(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的metamethod,Lua将a ~= b转换为not (a == b);a > b转换为b < a;a >= b转换为 b <= a。
--在file:operators.lua后添加以下lua代码,即可判断集合的包含问题 :<=代表集合的包含:a <= b表示集合a是集合b的子集。这种意义下,可能a <= b和b < a都是false;因此,我们需要将__le和__lt的实现分开:
print("____________________")
Set.mt.__le = function(a,b) --b include or equal a
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
print(s3 >= s4) -->true
print(s3 < s4) -->false
print(s3 > s4) -->true
print(s3 == s4) -->false
REF
http://book.luaer.cn/
http://www.jb51.net/article/56690.htm