• 友链

  • 首页

  • 文章归档
h u a n b l o g
h u a n b l o g

欢

HI,Friend

04月
21
Lua

Lua元表和元方法

发表于 2022-04-21 • 字数统计 4573 • 被 1,710 人看爆

元表

可以修改一个值在面对一个未知操作时的行为
元表是面向对象领域中的受限制类。
不支持继承

Lua语言中的每一个值都可以有元表。每一个表和用户数据类型都具有各自独立的元表,而其他类型的值则共享对应类型所属的同一个元表。

t = {}
print(getmetatable(t))      --> nil

可以使用函数setmetatable来设置或修改任意表的元表

t1 = {}
setmetatable(t, t1)         --将t1设置为t的元表
print(getmetatable(t) == t1)    --> true 

在Lua语言中,只能为表设置元表;如果要为其他类型的值设置元表,则必须通过C代码或调试库完成

算术运算符相关的元方法

例:一个用于集合的简单模块

local Set = {}
--使用指定的列表创建一个新的集合

function Set.new (l)
    local set = {}
    for _, v in ipairs(l) do 
        set[v] = true 
    end
    return set
end

function Set.union(a, b)
    local res = Set.new{}
    for k in pairs(a) do 
        res[k] = true 
    end
    for k in pairs(b) do 
        res[k] = true 
    end
    return res
end

function Set.intersection(a, b)
    local res = Set.new{}
    for k in pairs(a) do
        res[k] = b[k]
    end
    return res
end

--将集合表示为字符串
function Set.tostring(set)
    local l = {}        --保存集合中所有元素的列表
    for e in pairs(set) do
        l[#l + 1] = tostring(e)
    end
    return "i" .. table.concat(l, ", ") .. "}"
end

return Set

  现在,假设想使用加法操作符来计算两个集合的并集,那么可以让所有表示集合的表共享一个元表。这个元表中定义了这些表应该如何执行加法操作。

  首先,我们创建一个普通的表,这个表被用作集合的元表:local mt = {}

  然后,修改用于创建集合的函数Set.new。即将mt设置为函数Set.new所创建的表的元表

function Set.new(l)     --第二个版本
    local set = {}
    setmetatable(set, mt)   
    for _, v in ipairs(l) do 
        set[v] = true 
    end
    return set
end

  在此之后,所有由Set.new 创建的集合都具有了一个相同的元表:

s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(getmetatable(s1))         --> table: 0x00672B60
print(getmetatable(s2))         --> table: 0x00672B60

  最后,向元表中加入元方法__add,也就是用于描述如何完成加法的字段:

mt.__add = Set.union

  此后,只要Lua语言试图将两个集合相加,它就会调用函数Set.union,并将两个操作数作为参数传入。
  通过元方法,我们就可以使用加法运算符来计算集合的并集了:

s3 = s1 + s2
print(Set.tostring(s3))         --> {1, 10, 20, 30, 50}

乘法运算符来计算集合的交集

mt.__mul = Set.intersection
print(Set.tostring((s1 + s2)*s1))       -->{10,20,30,50}

算术运算符元方法

元方法描述
__add加法
__sub减法
__mul乘法
__div除法
__idivfloor除法
__unm负数
__mod取模
__pow幂运算

位操作元方法

元方法描述
__band按位与
__bor按位或
__bxor按位异或
__bnot按位取反
__shl向左移位
__shr向右移位

连接运算符__concat

  当我们把两个集合相加时,使用哪个元表是确定的。然而,当一个表达式中混合了两种具有不同元表的值时,例如:

s = Set.new{1, 2, 3}
s = s + 8

  Lua语言会按照如下步骤来查找元方法:如果第一个值有元表且元表中存在所需的元方法,那么Lua语言就使用这个元方法,与第二个值无关;如果第二个值有元表且元表中存在所需的元方法,Lua语言就使用这个元方法;否则,Lua语言就抛出异常。因此,上例会调用Set.union,而表达式10+S和"hello"+s同理(由于数值和字符串都没有元方法__add)。

  Lua语言不关心这些混合类型,但我们在实现中需要关心混合类型。如果我们执行了s = s + 8,那么在Set.union内部就会发生错误:bad argument #1 to 'pairs' ( table expected,got number)

关系运算符的元方法

元方法描述
__eq等于
__lt小于
__le小于等于

Lua语言会将a~=6转换为not(a == b),a > b转换为b < a, a >= b转换为b <= a

库定义的元方法

函数tostring元方法

__tostring对应tostring

那么以上面例

mt.__tostring = Set.tostring

--调用函数print时,print就会调用函数tostring,tostring又会调用Set.tostring
s1 = Set.new{10, 5, 8}
print(s1)   -->{5, 8, 10}

函数setmetatable和getmetatable的元方法

__metatable:用于保护元方法,使用户既不能看到也不能修改集合的元表。

getmetatable会返回这个字段的值,而setmetatable则会引发一个错误

mt.__metatable = "not your business"

s1 = Set.new{}
print(getmetatable(s1))     --> not your business
setmetatable(s1, {})        --> stdin:1: cannot change protected metatable

pairs元方法

__pairs:修改表被遍历的方式和为非表的对象增加遍历行为。

当一个对象拥有__pairs元方法时,pairs会调用这个元方法来完成遍历。

__index元方法

用来在表中访问字段,如果没有返回nil
--创建具有默认值的原型
prototype = {x = 0, y= 0, width = 100,height = 100}

local mt = {}       --创建一个元表

--声明构造函数
function new (o)
    setmetatable(o, mt)
    return o
end

--定义元方法__index
mt.__index = function(_, key) 
    return prototype[key]
end

w = new{x = 10, y = 20}
print(w.width)      --> 100

Lua语言会发现w中没有对应的字段width,但却有一个带有__index元方法的元表。因此,Lua语言会以w和"width"(不存在的键)为参数来调用这个元方法。元方法随后会用这个键来检索原型并返回结果。

  如果不想调用__index元方法,可以使用函数rawget(t, i),即不考虑元表的情况下对表进行简单访问

__newindex方法

元方法__newindex 与__index类似,不同之处在于前者用于表的更新而后者用于表的查询。

  当对一个表中不存在的索引赋值时,解释器就会查找_newindex元方法:如果这个元方法存在,那么解释器就调用它而不执行赋值。像元方法__index一样,如果这个元方法是一个表,解释器就在此表中执行赋值,而不是在原始的表中进行赋值。

  可使用rawset(t, k, v)等价于t[k] = v来绕过元方法

修改默认值

通过元表,可以修改字段的默认值(一般默认值为nil)

function setDefault(t, d)
    local mt = {__index = function() return d end}
    setmetatable(t, mt)
end

tab = {x=10, y=20}
print(tab.x, tab.z)     --> 10  nil
setDefault(tab, 0)  
print(tab.x, tab.z)     --> 10  0   

在调用setDefault后,任何对表tab中不存在字段的访问都将调用它的__index元方法,而这个元方法会返回零(这个元方法中的值是d)。

分享到:
Lua面向对象编程
Lua迭代器和泛型for
  • 文章目录
  • 站点概览
欢

网红 欢

你能抓到我么?

Email RSS
看爆 Top5
  • mac系统版本与Xcode版本有冲突 4,080次看爆
  • JAVA_HOME环境配置问题 3,730次看爆
  • AssetBundle使用 3,499次看爆
  • VSCode配置C++开发环境 3,257次看爆
  • Lua反射 3,133次看爆

Copyright © 2025 欢 粤ICP备2020105803号-1

由 Halo 强力驱动 · Theme by Sagiri · 站点地图