0、手册应该是学习语言的最好的文档,一边看文档,一边想把它记录下来,lua 5.2 英文手册地址:http://www.lua.org/manual/5.2/manual.html
在lua中,任何值都可以拥有元表(metatable)。元表本质是就是lua中table,但同时通过元表可以自定义在某些值上的操作。通过设置元表中特定的域(fields)值,可以改变某些值的操作行为。比如,当非数值类型的值执行加法运算时,lua就会检测该值的元表是否存在"__add"域。如果能找到,lua就会调用该函数,执行加法运算。
元表中的keys命名来自所对应的操作名(event names),并且key对应的值被称为元方法(metamethods),实质就是一个函数,比如,加法操作的event就是"add",他的元方法就是执行加法的函数。
可以使用函数getmetatable得到任何值的元表。你也可以通过函数setmetatable来设置table的元表,但是不能改变lua其他类型的元表,除非你用C API。
table和full userdata类型数据,可以拥有自己的元表(当然他们也可以共享一个元表)。但lua中其他类型的数据,每种类型数据都是共享一个元表的,比如所有的number类型数据共享一个元表,所有的string类型数据共享一个元表。默认情况下,处理字符串类型数据拥有元表外,其他类型的数据是没有元表的。
元表能够定义一个对象的算术操作、比较操作、连接操作、求长度和索引的行为,元表也能够定义一个函数,这个函数可以在userdata和table类型被GC时调用。当某个值执行上面列出的某个操作时,lua就会检查该值的元表是否有相应的event,如果有,就会调用相应的元方法来执行相应的操作。
元表能够定义的操作会在下面列出。每种操作都有相应的操作名字。每个操作的名字都是以两个下划线"__"开始的,比如加法操作"add"对应的操作名就是"__add"。
可以通过lua函数来解释lua解释器是怎样来实现这一语法的。下面解释仅仅了为了理解,实质的解释器比这里更高效。实际上,为了查找一个对象的元方法,我们可以使用表达式:
metatable(obj)[event]
该表达式等同于
rawget(getmetatable(obj) or {},event)
这说明了访问某个对象的元方法,不会调用其他的元方法,并且访问一个没有元表的对象也不会fail,只是简单的返回nil。
对于一元操作符"-"和"#",传递给元方法的第二个参数是一个dummy,这样做只是为了简化lua的内部实现,这种做法可能会在lua的未来的版本去掉,因此下面的代码不会列出他们。
"add":+操作
下面定义的函数getbinhandler演示了lua怎样选择元方法。首先,lua会从第一个操作对象中查找,若没有,则才会从第二个操作对象中查找。
function getbinhandler(op1,op2,event)
return metatable(op1)[event] or metatable(op2)[event]
end
这样,lua中op1 + op2的行为等价以下代码:
function add_event(op1,op2)
local o1,o2 = tonumber(op1),tonubmer(op2)
if o1 and o2 then --都是数值类型
return o1 + o2
esle
lcoal h = getbinhandler(op1,op2,"__add")
if h then
return(h(op1,op2))
else
error(...)
end
end
end
"sub":-操作,类似于"add"操作
"mul":*操作,类似于"add"操作
"div":/操作,类似于"add操作
"mod":%操作,类似于"add"操作,等价于o1 - floor(o1/o2)*o2
"pow":^操作,类似于"add"操作,他的实现调用的是C数学库函数pow
"unm":一元操作:
function unm_event(op)
local o = tonumber(op)
if o then --如果是数值类型
return -o --直接有默认的操作返货
else
local h = metatable(op).__unm
if h then
return (h(op))
else
error(···)
end
end
end
"concat": .. 操作:
function concat_evnet(op1,op2)
if (type(op1) == "string" or type(op1) == "number") and
(type(op2) == "string" or type(op2) == "number") then
return op1 .. op2
else
local h = getbinhandler(op1,op2,"__concat")
if h then
return (h(op1,op2))
else
error(...)
end
end
end
"len": #操作
function len_evnet(op)
if type(op) == "string" then
return strlen(op)
else
local h = metatable(op).__len
if h then
return (h(op))
elseif type(op) == "table" then
return #op
else
error(...)
end
end
end
"eq":==操作,下面的getequalhandler函数展示了equality操作是怎么选择元方法的。只有当两个值的具有相同的类型和相同的元方法,才认为存在这样的元方法,并且只针对table和userdata类型。
function getequalhandler(op1,op2)
if type(op1) ~= type(op2) or
(type(op1) ~= "table" and type(op2) ~= "userdata") then
return nil
end
local mm1 = metatable(op1).__eq
local mm2 = metatable(op2).__eq
if mm1 == mm2 then
return mm1
else
return nil
end
en
"eq"evnt定义如下:
function eq_event(op1,op2)
if op1 == op2 then
return true
end
local h = getequalhandler(op1,op2)
if h then
return not not h(op1,op2) --这样做,一定会要么返回true或false,确保返回结果一定是boolean类型
else
return false
end
end
"lt":<操作
function lt_event(op1,op2)
if type(op1) == "number" and type(op2) == "number" then
return op1 < op2
elseif type(op1) == "string" and type(op2) == "string" then
return op1 < op2
else
local h = getbinhandler(op1,op2,"__lt")
if h then
return not not h(op1,op2) --保证返回结果为boolean型
esle
error(...)
end
end
end
"le": <=操作
function le_event(op1,op2)
if type(op1) == "number" and type(op2) == "number" then
return op1 <= op2
elseif type(op1) == "string" and type(op2) == "string" then
return op1 <= op2
else
local h = getbinhandler(op1,op2,"__le")
if h then
return not not h(op1,op2) --保证返回结果为boolean型
esle
h = getbinhandler(op1,op2,"__lt")
if h then
return not h(op2,op1)
else
error(...)
end
end
end
end
当不存在"le"的元方法时,lua会尝试使用"lt"的元方法,"a<=b"等价于"not (b < a)",同其他的比较操作运算一样,总是返回一个boolean类型。
"newindex": 访问table[key],注意只有当访问table[key]不存在时,才会调用该元方法:
function gettable_event(table,key)
local h
if type(table) == "table" then
local v = rawget(table,key)
if v ~= nil then
return v
end
h = metatable(table).__index
if h == nil then
return nil
end
else --不是table类型,始终调用元方法
h = metatable(table).__index
if h == nil then
error(...)
end
end
if type(h) == "function" then
return (h(table,key))
else
return h[key]
end
end
"newindex": table[key] = value,注意只有当访问table[key]不存在时,才会调用该元方法:
function settable_event(table,key,vale)
local h
if type(table) == "table" then
local v = rawget(table,key)
if v ~= nil then
rawset(table,key,vale)
return
end
h = metatable(table).__newindex
if h == nil then
rawset(table,key,vale)
return
end
else
h = metatable(table).__newindex
if h == nil then
error(...)
end
end
if type(h) == "function" then
h(table,key,value)
else
h[key] = value --注意这句,会重复这个操作
end
end
"call":lua值做为函数调用时使用:
function function_event(func,...)
if type(func) == "function" then
return func(...)
else
local h = metatable(func).__call
if h then
return h(func,...)
else
error(...)
end
end
end