码迷,mamicode.com
首页 > 其他好文 > 详细

lua元表与元方法

时间:2015-07-31 06:49:32      阅读:130      评论:0      收藏:0      [点我收藏+]

标签:

    lua中每个值都有一套预定义的操作集合,比如数字是可以相加的,字符串是可以连接的,但是对于两个table类型,则不能直接进行“+”操作。这需要我们进行一些操作。在lua中有一个元表(metatable),我们可以通过元表来改变一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作。比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b,具体的在Lua中是按照以下步骤进行的:

    1.先判断a和b两者之一是否有元表

    2.检查该元表中是否有一个叫__add的字段

    3.如果找到该字段,就调用该字段对应的值,这个值对应的是一个metamethod(元方法)

    4.调用__add对应的元方法计算a和b的值


    在table中,我们可以重新定义的元方法有以下几个:

    __add(a, b) --加法

    __sub(a, b) --减法

    __mul(a, b) --乘法

    __div(a, b) --除法

    __mod(a, b) --取模

    __pow(a, b) --乘幂

    __unm(a) --相反数

    __concat(a, b) --连接

    __len(a) --长度

    __eq(a, b) --相等

    __lt(a, b) --小于

    __le(a, b) --小于等于

    __index(a, b) --索引查询

    __newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲)

    __call(a, ...) --执行方法调用

    __tostring(a) --字符串输出

    __metatable --保护元表


    下面来进行一些个别的说明:

    1.__add

local mytable = setmetatable({1,2,3},{
	__add = function(mytable,newtable)
		for i=1,#newtable do 
			table.insert(mytable,#mytable+1,newtable[i])
		end
		return mytable 
	end
})

local secondtable = {4,5,6}

mytable = mytable + secondtable 

for k,v in ipairs(mytable) do 
	print(k,v)
end

   输出为:

1	1
2	2
3	3
4	4
5	5
6	6

   注:由于mytable和secondtable都是table,所以它们不能简单的相加。这个时候,lua会去找mytable和secondtable,去看它们有没有__add方法,这时候,lua发现mytable中有__add,因此调用了__add对应的函数,__add对应的函数传入的第一个参数是mytable,第二个参数是secondtable。实际上就是相当于把secondtable的值都附加到mytable中。

    现在大家有没有想过,如果两个table同时都有__add方法,那lua会执行哪个呢?我们看一下下面的代码:

local mytable = setmetatable({1,2,3},{
	__add = function(mytable,newtable)
		print("这是第一个table")
		for i=1,#newtable do 
			table.insert(mytable,#mytable+1,newtable[i])
		end
		return mytable 
	end
})

local secondtable = setmetatable({4,5,6},{
	__add = function(mytable,newtable)
		print("这是第二个table")
		for i=1,#newtable do 
			table.insert(mytable,#mytable+1,newtable[i])
		end
		return mytable
    end
})

mytable = secondtable + mytable + secondtable + mytable 

for k,v in ipairs(mytable) do 
	print(k,v)
end

    输出为:

这是第二个table
这是第二个table
这是第二个table
1	4
2	5
3	6
4	1
5	2
6	3
7	4
8	5
9	6
10	1
11	2
12	3
13	1
14	2
15	3

    注:由输出我们可以知道,哪个table在“+”操作最前面,则执行该table的__add方法,而且该语句后面也是都是调用第一个table的__add方法。还有朋友可能不太理解为什么会输出1-15,不应该是1-12吗?其实是这样的我们在secondtable的__add方法已经把secondtable的值就行改变了,当他执行完secondtable + mytable这个的时候,它已经变成了4,5,6,1,2,3而mytable是在secondtable + mytable + secondtable+ mytable 执行完了之后才被赋值的,所以在右边的两个mytable都是1,2,3


    接着我们在看一下__index元方法,当访问一个table中不存在的字段时,得到的结果为nil。这是对的,但并非完全正确。实际上,这些访问会促使解释器去查找一个叫__index的元方法。如果没有这个元方法,那么访问结果如前述的胃nil。否则,就由这个元方法来提供最终结果。看下面的例子:

window = {}
window.prototype = {x=0,y=0,width=100,height=100}
window.mt = {}

function window.new(o)
	setmetatable(o,window.mt)
	return o
end

window.mt.__index = function(table,key)
	print("-------------------",key)
	for k,v in pairs(table) do 
		print(k,v)
	end
	return window.prototype[key]
end

w = window.new{x=10,y=20}
print(w.width,w.x,w.y,w.height)

    输出:

-------------------	width
y	20
x	10
-------------------	height
y	20
x	10
100	10	20	100

    注:lua检测到w中没有某字段,但在其元表中却有一个__index字段,那么lua就会以w(table)和"width"(不存在的key)来钓鱼这个__index元方法。随后元方法用这个key来索引原型table,并返回结果。__index元方法不一定是一个函数,它还可以是一个table,当它是一个函数时,lua以table和不存在的key作为参数来调用该函数,而当它是一个table时,lua就以相同的方式来重新访问这个table(会在这个table中继续查找)。


    我们现在来看一下__newindex元方法:

    __newindex元方法于__index类似,__newindex用于table的更新,而__index用于table的查询。当对一个table不存在的索引赋值时,解释器就会查找__newindex元方法。如果有这个元方法,解释器就调用它,而不是执行赋值。如果这个元方法是一个table,解释器就在该table中执行赋值,而不是对原来的table。看下面的例子:

local man = {
	name = "大神",
	money = 30000,
	play = function()
		print("我去打篮球")
	end
}

local t = {}

local mt = {
	__index = man,
	__newindex = function(table,key,value)
		print(key .. "不存在,不要尝试给它赋值")
	end
}

setmetatable(t,mt)

t.play = function()
	print("我去踢足球")
end

t.play()

    输出:

play不存在,不要尝试给它赋值
我去打篮球

    注:由输出我们可以知道t.play = function() print("hi") end并没有起到作用,这是因为给t的sayhello字段赋值的时候,lua判断play字段不存在,所以会去调用元表里的__newindex元方法。__newindex元方法被调用的时候会传入3个参数:table本身,字段名,想要赋予的值。

    我们再看下面的例子:

local man = {
	name = "程序猿"
}

local updatetable = {
	name = "我是美男子"
}

local t = {}

local mt = {
	__index = man,
	__newindex = updatetable
}

setmetatable(t,mt)

print("updatetable赋值前:",updatetable.name)
t.name = "我是大帅哥"
print("updatetable赋值后:",updatetable.name)
print("t.name:",t.name)

    输出:

updatetable赋值前:	我是美男子
updatetable赋值后:	我是大帅哥
t.name:	程序猿

    这就是上面所说的:如果这个元方法是一个table,解释器就在该table中执行赋值,而不是对原来的table。看下面的例子。

    如果还不明白,在看下面的例子可能就会更加理解了:

mymetatable = {}
mytable = setmetatable({key = "value"},{__newindex=mymetatable})

print(mytable.key)

mytable.newkey = "newkey value"
print(mytable.newkey,mymetatable.newkey)

mytable.key = "key value"
print(mytable.key,mymetatable.key)

    输出:

value
nil	newkey value
key value	nil

    注:当lua执行mytable.newkey = "newkey value"这句时,会去mytable中查找newkey,发现没有newkey的时候就会去找__newindex这个元方法,然后对__newindex对应的table进行赋值,因此mytable.newkey为nil,mymetatable.newkey为newkey value。而执行mytable.key = "key value"语句的时候,mytable存在key,因此只修改mytable中的key对应的值。


    我们再来看一下__call这个元方法,这个元方法会在Lua 调用一个值时调用,看下面的例子:

mytable = setmetatable({10},{
	__call = function(mytable,newtable)
		sum = 0
		for i=1,#mytable do 
			sum = sum + mytable[i]
		end

		for i=1,#newtable do 
			sum = sum + newtable[i]
		end
		return sum
	end
})

newtable = {10,20,30}
print(mytable(newtable))

    输出:

70

    注:mytable是一个table,当调用其时(mytable(newtable))会执行__call这个元方法,第一个参数为mytable,第二个参数为newtable。


    __tostring元方法,它主要是更改打印语句,看下面的例子:

mytable = setmetatable({},{
	__tostring = function(mytable)
		return "我改变你的值"
  	end
})

print(mytable)

    输出:

我改变你的值

    

lua元表与元方法

标签:

原文地址:http://my.oschina.net/u/815187/blog/485908

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!