【Lua】元表初学

来源:互联网 发布:淘宝的旺旺名怎么修改 编辑:程序博客网 时间:2024/06/01 21:45

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) doset[v] = trueendreturn setendfunction Set.union(tableA, tableB)if getmetatable(tableA) ~= mt or getmetatable(tableB) ~= mt thenprint("Set.union metatable error")returnendlocal set = Set.new{}--类似Set.new({})。很关键,新表的生成调用new函数,保证绑定了元表for k in pairs(tableA) doset[k] = trueendfor k in pairs(tableB) doset[k] = trueendreturn setendfunction Set.print(table)for k, v in pairs(table) doprint(k, " ", v)endendmt.__add = Set.unionlocal setA = Set.new({1, 2, 3})local setB = Set.new({4, 5})print(getmetatable(setA))print(getmetatable(setB))local setC = setA + setBlocal setD = 2 + setCSet.print(setC)


输出为:

table: 0x213cad0table: 0x213cad0Set.union metatable error1 true2 true3 true4 true5 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) dotab[#tab + 1] = kendreturn "{" .. table.concat(tab, ", ") .. "}"endmt.__tostring = Set.tostringlocal setA = Set.new({1, 2, 3})local setB = Set.new({4, 5})local setC = setA + setBprint(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) doset[v] = trueendmt.__metatable = "You cannot get metatable"return setendlocal setA = Set.new({1, 2, 3})local setB = Set.new({4, 5})print(getmetatable(setA))print(setmetatable(setB, {}))

你会得到:

You cannot get metatablelua: 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 personend-- 定义__index元方法Person.mt.__index = function (table, key)return Person.default[key]endlocal person = Person.new({sex = "female", year = 18})print(person.sex)print(person.year)print(person.height)

输出为:

female180


__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"))
的输出结果为:

female18nil

原创粉丝点击