Lua 5.2 手册翻译之元表和元方法(Metatables and Metamethods)

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



     

你可能感兴趣的:(Lua)