【Lua】元表初学

metatable,Lua中的元表,是Lua中重要的内容。

参考自:Lua中的元表与元方法(果冻想)


在Lua代码中,只能设置table的元表。若要设置其它类型的值的元表,则必须通过C代码来完成。还存在一个特例,对于字符串,标准的字符串程序库为所有的字符串都设置了一个元表,而其它类型在默认情况下都没有元表。


下面用一个简单的例子初探元表:


__add元方法:

Set = {}
local mt = {}

function Set.new(table)
	local set = {}
	setmetatable(set, mt)
	for _, v in pairs(table) do
		set[v] = true
	end

	return set
end

function Set.union(tableA, tableB)
	if getmetatable(tableA) ~= mt or getmetatable(tableB) ~= mt then
		print("Set.union metatable error")
		return
	end

	local set = Set.new{}		--类似Set.new({})。很关键,新表的生成调用new函数,保证绑定了元表
	for k in pairs(tableA) do
		set[k] = true
	end
	for k in pairs(tableB) do
		set[k] = true
	end

	return set
end

function Set.print(table)
	for k, v in pairs(table) do
		print(k, " ", v)
	end
end

mt.__add = Set.union

local setA = Set.new({1, 2, 3})
local setB = Set.new({4, 5})

print(getmetatable(setA))
print(getmetatable(setB))

local setC = setA + setB
local setD = 2 + setC

Set.print(setC)


输出为:

table: 0x213cad0
table: 0x213cad0
Set.union metatable error
1	 	true
2	 	true
3	 	true
4	 	true
5	 	true

Lua是按照以下步骤寻找对元方法:

1、对于二元操作符,如果第一个操作数有元表,并且元表中有所需要的字段定义,比如我们这里的__add元方法定义,那么Lua就以这个字段为元方法,而与第二个值无关;
2、对于二元操作符,如果第一个操作数有元表,但是元表中没有所需要的字段定义,比如我们这里的__add元方法定义,那么Lua就去查找第二个操作数的元表;
3、如果两个操作数都没有元表,或者都没有对应的元方法定义,Lua就引发一个错误。


上面的例子中,2 + setC 虽然 setC 具有元方法,但是其与数字 2 相加的并没有定义相关的方法,所以在Set.union进行了判断,保证两个入参对应同样的元表。



__tostring元方法

上面我们写了自己 print 函数,如果我们使用 lua 自带的 print 函数呢?不能格式化输出!怎么办?我们需要自己重新定义__tostring元方法,让print可以格式化打印出table类型的数据。

function Set.tostring(set)
	local tab = {}
	for k in pairs(set) do
		tab[#tab + 1] = k
	end

	return "{" .. table.concat(tab, ", ") .. "}"
end

mt.__tostring = Set.tostring

local setA = Set.new({1, 2, 3})
local setB = Set.new({4, 5})

local setC = setA + setB

print(setC)

这样打印出:

{1, 2, 3, 4, 5}


元表的设置与查看通过 setmetatable 和 getmetatable 两个函数可以方便得到,但如果我们不希望这么“方便”呢?我们希望在元表被设置后不被修改,元表中的一个字段,用于保护元表,该字段是__metatable。当我们想要保护集合的元表,让用户既不能看也不能修改元表,那么就需要使用__metatable字段了,在设置了该字段时,getmetatable就会返回这个字段的值,而setmetatable则会引发一个错误。如下:

Set = {}
local mt = {}

function Set.new(table)
	local set = {}
	setmetatable(set, mt)
	for _, v in pairs(table) do
		set[v] = true
	end
	mt.__metatable = "You cannot get metatable"

	return set
end

local setA = Set.new({1, 2, 3})
local setB = Set.new({4, 5})

print(getmetatable(setA))
print(setmetatable(setB, {}))

你会得到:

You cannot get metatable
lua: a.lua:48: cannot change a protected metatable




__index 元方法

这是最常见的元方法。我们访问一个table中不存在的字段时,得到的结果是nil,但是这种状况很容易被改变。Lua按照以下的步骤决定是返回nil还是其它值:

1、当访问一个table的字段时,如果table有这个字段,则直接返回对应的值;
2、当table没有这个字段,则会促使解释器去查找一个叫__index的元方法,接下来就就会调用对应的元方法,返回元方法返回的值;
3、如果没有这个元方法,那么就返回nil结果。


下面用例子说明:

Person = {}
Person.default = {sex = "male", year = 0, height = 0}

Person.mt = {}

function Person.new(person)
	setmetatable(person, Person.mt)
	return person
end

-- 定义__index元方法
Person.mt.__index = function (table, key)
	return Person.default[key]
end

local person = Person.new({sex = "female", year = 18})
print(person.sex)
print(person.year)
print(person.height)

输出为:

female
18
0


__index元方法不必一定是一个函数,它还可以是一个table。当它是一个函数时,Lua以table和不存在key作为参数来调用该函数,当它是一个table时,Lua就以相同的方式来重新访问这个table,所以上面的代码也可以是这样的:

Person.mt.__index = Person.default

可以达到同样的目的。


有时候我们并不想去查询元表,怎么办?

使用

rawget(table, key)
可以完成一次原生的访问。如:

print(rawget(person, "sex"))
print(rawget(person, "year"))
print(rawget(person, "height"))
的输出结果为:

female
18
nil

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值