元表
元表:处理Lua中的不可预见操作的table,如:两个table相加,对函数作比较,调用字符串。
- 元表定义的是实例的行为,只能给出预先定义的操作集合的行为,不支持继承
- 每一个值都可以有元表
- table和userdata类型有独立的元表
- 其他类型共享对应类型所属的同一个元表(必须通过C代码或调试库)
- 在创建时不带元表
- 可以使用setmetatable(table,metatable)设置元表,getmetatable()取得元表
所有字符串在创建时都设置了同一个元表
t = {}
print(getmetatable("str1"))
print(getmetatable("str2"))
print(getmetatable(1))
print(getmetatable(t))
注:一个table可以成为任意值的元表;一组相关的table也可共享描述他们共同行为的元表;table还可成为自己的元表。
元方法
元方法:元表中修改一个值在遇到未知操作时的行为,(是一个函数)
setmetatable(table,metatable):给table设置元表metatable
getmetatable(table):查询table是否有元表,有则返回,没有则返回__metatable关联数值
算数运算相关的元方法
每种算数运算符都有一个对应的元方法 | 算数运算符 | 元方法 |
| 算数运算符 | 元方法 | | —- | —- | —- | —- | —- | | + | add | | &(按位与) | band | | - | sub | | |(按位或) | bor | | * | mul | | ~(按位异或) | bxor | | / | div | | ~(按位取反) | bnot | | // | idiv | | <<(逻辑左移) | shl | | -(负数) | unm | | >>(逻辑右移) | shr | | % | mod | | 连接运算符 | concat | | ^(幂运算) | __pow | | | |
Lua查找元方法步骤
- 如果第一个值有元表,且元表中存在所需元方法
- 使用这个元方法,与第二个值无关
- 结束
- 如果第二个值有元表,且元表中存在所需元方法
- 使用这个元方法
- 结束
- Lua抛出异常
示例:集合并和交集运算
```lua local Set = {} local mt = {} —作为元表
function Set.new(t) —将table变为集合,并设置元表 local set = {} setmetatable(set, mt) —设置输入table的元表为mt for k, v in pairs(t) do —集和化table set[v] = true end return set end
function Set.tostring(set) —将集合字符化 local t = {} for k, v in pairs(set) do t[#t + 1] = k end return “{“ .. table.concat(t, “,”) .. “}” end
—[[ 开始设置元表的元方法 —]] mt.add = function(a, b) —设置add元方法,为求并集 local res = Set.new {}
for k in pairs(a) do --加入集合a的元素
res[k] = true
end
for k in pairs(b) do --加入结合b的元素
res[k] = true
end
return res
end
mt.mul = function(a, b) —设置mul元方法,为求交集 local res = Set.new {}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
local s1 = Set.new {10, 20, 30, 50} —创建集合s1,s2并为其设置元表 local s2 = Set.new {30, 1}
local s3 = s1 + s2 —table s1和s2遇到不可预测的操作,对应元方法为求并集 print(Set.tostring(s3)) —输出:{1,10,20,30,50} s3 = nil s3 = (s1 + s2) * s1 print(Set.tostring(s3)) —输出:{10,20,30,50}
<a name="o70FN"></a>
# 关系运算相关的元方法
- 只有三种关系运算符可以指定其元方法,**其他三个关系运算符没有独立的元方法**
- **注:如果两个对象类型不同,则不会调用其元方法,直接返回false**
- Lua会对**没有独立元方法**的关系运算符进行**转换**:
- a ~= b → not ( a == b )
- a > b → b < a
- a >= b → b <= a
| 关系运算符 | 元方法 |
| --- | --- |
| ==(等于) | __eq |
| <(小于) | __lt |
| <=(小于等于) | __le |
<a name="w0mwG"></a>
### 部分有序问题
- **并非所有类型**的元素都能够**正确排序**
- 如:Not a Number(NaN)是不可以完全排序的
- 规定上,涉及NaN的比较都返回false(NaN<=x为false,x<NaN为false)
<a name="QnW2L"></a>
### 示例:集合包含问题
```lua
local Set = {}
local mt = {} --作为元表
function Set.new(t) --将table变为集合,并设置元表
local set = {}
setmetatable(set, mt) --设置输入table的元表为mt
for k, v in pairs(t) do --集和化table
set[v] = true
end
return set
end
mt.__le = function(a, b) --设置<=对应的元方法,求a是否是b的真子集
for k, v in pairs(a) do
if not b[k] then --如果b中不含a则返回false
return false
end
end
return true
end
mt.__lt = function(a, b) --设置<对应的元方法,求a是否是b的子集
return a <= b and not (b <= a) --利用a是b的真子集 且 b不是a的真子集 来排除2个集合相等的情况
end
mt.__eq = function(a, b) --设置==对应的元方法
return a <= b and b <= a --利用a是b的真子集,b又是a的真子集,来确定a和b相等
end
local s1 = Set.new {2, 4}
local s2 = Set.new {4, 10, 2}
print(s1 <= s2) --输出:true
print(s1 < s2) --输出:true
print(s1 > s1) --输出:false
- 注:上方程序中, 全部使用<=来实现,使得避免了因为部分有序而出现的问题
库定义相关的元方法
- 使用函数tostring进行格式化时
- 函数tostring会先检查元方法__tostring
- 元方法存在,则tostring函数调用该元方法
- tostring函数的参数传递给该元方法
- 元方法的返回值作为tostring函数的返回值 ```lua local Set = {} local mt = {} —作为元表
function Set.new(t) —将table变为集合,并设置元表 local set = {} setmetatable(set, mt) —设置输入table的元表为mt for k, v in pairs(t) do —集和化table set[v] = true end return set end
mt.__tostring = function(set) local t = {} for k, v in pairs(set) do t[#t + 1] = k end return “{“ .. table.concat(t, “,”) .. “}” end
local s1 = Set.new {2, 4} print(s1)
<a name="KsTxh"></a>
### __metatable字段:保护元表
- **给元表设置__metatable**字段并**添加给table后**,用户既不能看到也**不能修改table的元表**
- **设置__metatable**字段,并将元表**添加**给**table后**:
- 对table调用函数**getmetatable**,会**返回该字段**
- 对table调用函数**setmetatable**,会**引发错误**
```lua
local t = {}
local mt = {}
local mt2 = {}
mt.__metatable = "mot your business" --设置元表的__metatable字段,保护元表
setmetatable(t, mt) --给table t设置元表mt
print(getmetatable(t)) --输出:__metatable内容,mot your business
setmetatable(t, mt2) --报错
__pairs元方法
- Lua5.2后添加了pairs的元方法,当一个对象有__pair元方法时,pairs函数会调用该元方法来完成遍历
- __pairs元方法的输入参数只能为一个(输入参数与pairs函数相同) ```lua local t = {} local mt = {}
mt.pairs = function(t) —设置pairs元方法 local i = 0 return function() —闭包 i = i + 1 if t[i] == nil then return nil else return t[i] end end end
t = {10, 20, 30, 40} setmetatable(t, mt)
for v in pairs(t), 0 do —使用pairs时调用的是__pairs元方法 print(v) end —[[ 输出: 10 20 30 40 —]]
<a name="JeG58"></a>
#
<a name="MTYrh"></a>
# table相关的元方法
<a name="MQn9w"></a>
### __index元方法:访问时调用
- **访问**一个**table**中**不存在**的字段会得到nil
- 实际的**Lua解释器**操作:
1. Lua解释器**查找**名为**__index**的**元方法**
1. **没有**,**返回nil**
1. **有**,由这个**元方法提供**最终**结果(设置为table本身,就是直接访问table)**
- 使用元方法**__index可以实现继承**
- **函数作**__index**元方法**可实现**单继承**,该方法的**开销更高**,**但是灵活**
- **元方法可以是函数,或是table**
- __index元方法是**函数**时:会以table和**不存在的键作为参数调用**该函数
- __index元方法是**table**时:**访问**该table
```lua
local prototpe = {x = 0, y = 0, width = 100, height = 100} --作为父类
local mt = {}
function new(t)
setmetatable(t, mt) --设置元表
return t
end
mt.__index = function(t, key) --设置__index元方法
return prototpe[key]
end
local w = new {x = 10, y = 20} --实例化新地类w
print(w.width) --输出:100
--[[
table w中并没有width说对应的数值,但是因为调用w.width时,w中没有width字段,
会使用__index元方法,通过元方法调用了父类中width,因此输出了父类
--]]
rawget():不调用元方法访问表
rawget(table,index):不调用__index元方法,访问table
与__index类似,用于table的更新(table中键的赋值)
- Lua解释器的使用过程:
- 对table中不存在的索引进行赋值
- 查找__newindex元方法是否存在
- 存在:
- 是函数:调用而不执行赋值
- 是table:在table中执行赋值
- 不存在,在原table中进行赋值 ```lua local t = {x=1} local mt = {}
- Lua解释器的使用过程:
mt.newindex = function(t,k,v) —设置newindex元方法为函数,在对新索引赋值时,会调用该函数 print(“hello”) end
setmetatable(t, mt) —设置元表 t.x = 2 —成功赋值 t.a = 1 —a不在table t中,调用元方法__newindex,输出:hello
local t = {x = 1} local mt = {} local save = {}
mt.newindex = save —设置newindex元方法为table,对新索引赋值时,就会在该table中进行 setmetatable(t, mt) —设置元表
t.a = 3 —对新索引a赋值 print(t.a) —输出:nil print(save.a) —输出:3
<a name="aMOGv"></a>
#### rawset():不调用元方法赋值
- **rawset(table,key,value)**:**在table中的索引key赋值时,绕过元方法__newindex**
- table:要操作的table
- key:table中的索引
- value:table中索引对应的值
<a name="cze7J"></a>
### 具有默认值的table
```lua
function setDefault(t, d)
local mt = {
__index = function()
return d
end
}
setmetatable(t, mt)
end
local t = {x = 10, y = 20}
print(t.z) --输出:nil
setDefault(t, 0) --设置索引的初始值为0
print(t.z) --输出:0
- 上方程序中,函数创建了一个闭包和新的元表。但是如果有很多表需要设定初始值,会照成比较大开销。
- 因为每个table都是和不同的元表连接在一起的(也就是不同的闭包函数)。
- 因此为了减少开销,需要让多个table可以使用同一个元表 ```lua local mt = { —将元表的定义放在函数之外 index = function(t, k) return t.only —元方法index返回的是参数table中的键only,该键在调用函数时设定 end }
function setDefault(t, d) t.only = d setmetatable(t, mt) end
local t = {x = 10, y = 20} print(t.z) —输出:nil setDefault(t, 0) —设置索引的初始值为0 print(t.z) —输出:0
- 上方程序中,将**初始值保存在**了**table中**,这样就可以**让多个table使用同一个元表**,减少开销
<a name="TjSXB"></a>
### 跟踪对table的访问
- 监控一个table的所有访问,**创建一个代理**
- **捕获**一个table的**所有访问**的唯一方式是**保持table是空的**
- 因为**__index**和**__newindex**只有在**索引不存在**时,才有用
```lua
function track(t)
local proxy = {} --作为table t的代理table
local mt = { --创建代理的元表
__index = function(t, k)
print("*aaccess to element " .. tostring(k))
return t[k] --访问原来的table
end,
__newindex = function(t, k, v)
print("*uptate of element " .. tostring(k) .. "to" .. tostring(v))
t[k] = v --更新原来的table
end,
__pairs = function()
return function(t, k) --迭代函数
local nextKey, nextValue = next(t, k)
if nextKey ~= nil then
print("*traversing element " .. tostring(nextKey))
end
return nextKey, nextValue
end
end,
__len = function()
print("*check table long")
return #t
end
}
setmetatable(proxy, mt)
return proxy
end
t = {}
t = track(t) --给table t加一个代理,用于最终操作
t[1] = "hello"
print(t[1])
--[[
输出:
*uptate of element 1 to hello
*aaccess to element 1
hello
--]]
- 上方的程序中,根据设计的规则跟踪每个访问,并将其重定向到原来的table中。
- 对代理table来说,因为其是空table,所以所有的索引都是未知的
- 如果需要同时监控几个table,可以像给table设置默认值一样,用一个共享的元表,减少开销
只读table
- 使用代理的概念,跟踪对table的更新操作并抛出异常
- 对__index,直接使用原来的table来替代(更快也更简单)
```lua
function readOnly(t)
local proxy = {}
local mt = {
index = t,
newindex = function()
end } setmetatable(proxy, mt) return proxy enderror("attempt to upadte a read-only table")
- 对__index,直接使用原来的table来替代(更快也更简单)
```lua
function readOnly(t)
local proxy = {}
local mt = {
index = t,
newindex = function()
days = readOnly {“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”}
print(days[1]) —输出:Sunday days[2] = “null” —输出:attempt to upadte a read-only table ```