八、Lua中的元表与元方法

Lua中的元表和元方法|果冻想

本文是对于“果冻想”的《Lua入门系列》所作笔记,原文来自上面链接。

前言

比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b,具体的在lua中是按照以下步骤进行的:
1.先判读a和b两者之一是否有元表;
2.检查该元表中是否有一个叫__add的字段;
3.如果找到了该字段,就调用该字段对应的值,这个值对应的是一个metamethod;
4.调用__add对应的metamethod计算a和b的值

在lua中,每个值都有一个元表,table和userdata类型的每个变量都可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。

lua在创建新的table时不会创建元表,比如以下代码就可以演示:
   
   
  1. a = {}
  2. print(getmetatable(a)) -- nil

任何table都可以作为任何值的元表

在lua代码中,只能设置table的元表。若要设置其他类型的值的元表,则必须通过c代码来完成。还存在一个特列,对于字符串,标准的字符串程序库为所有的字符串都设置了一个元表,而其他类型在默认情况下都没有元表。
   
   
  1. print(getmetatable("Hello")) -- table: 0072B0C0
  2. print(getmetatable(10)) -- nil

在table中,我们可以重新定义的元方法有以下几个:
   
   
  1. __add(a, b) --加法
  2. __sub(a, b) --减法
  3. __mul(a, b) --乘法
  4. __div(a, b) --除法
  5. __mod(a, b) --取模
  6. __pow(a, b) --乘幂
  7. __unm(a) --相反数
  8. __concat(a, b) --连接
  9. __len(a) --长度
  10. __eq(a, b) --相等
  11. __lt(a, b) --小于
  12. __le(a, b) --小于等于
  13. __index(a, b) --索引查询
  14. __newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲)
  15. __call(a, ...) --执行方法调用
  16. __tostring(a) --字符串输出
  17. __metatable --保护元表

算术类的元方法

   
   
  1. Set = {}
  2. local mt = {}
  3. function Set.new( l )
  4. local set = {}
  5. setmetatable(set, mt)
  6. mt.__add = Set.union
  7. for _, v in pairs(l) do set[v] = true end
  8. return set
  9. end
  10. function Set.union( a, b )
  11. local reSet = Set.new{}
  12. for v in pairs(a) do
  13. reSet[v] = true
  14. end
  15. for v in pairs(b) do
  16. reSet[v] = true
  17. end
  18. return reSet
  19. end
  20. function Set.intersection( a, b )
  21. local retSet = Set.new{}
  22. for v in pairs(a) do retSet[v] = b[v] end
  23. return retSet
  24. end
  25. function Set.toString( set )
  26. local tb = {}
  27. for e in pairs(set) do
  28. tb[#tb + 1] = e
  29. end
  30. return "{" .. table.concat(tb, ", ") .. "}"
  31. end
  32. function Set.print( s )
  33. print(Set.toString(s))
  34. end

   
   
  1. require("Set")
  2. local set1 = Set.new({10, 20, 30})
  3. local set2 = Set.new({1, 2})
  4. print(getmetatable(set1))
  5. print(getmetatable(set2))
  6. local set3 = set1 + set2
  7. Set.print(set3)

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

__tostring元方法

函数print总是调用tostring来进行格式化输出,当格式化任意值时,tostring会检查该值是否有一个__tostring的元方法,如果有这个元方法,tostring就用该值作为参数来调用这个元方法,剩下实际的格式化操作就有__tostring元方法引用的函数去完成,改函数最终返回一个格式化完成的字符串。例如以下代码:
   
   
  1. mt.__tostring = Set.toStirng

如何保护我们的“奶酪”——元表

在lua中,函数setmetatable和getmetatable函数会用到元表中的一个字段,用于保护元表,该字段是__metatable。当我们想要保护集合的元表,使用户既不能看也不能修改集合中的元表,那么就需要使用__metatable字段了;当设置了该字段时,getmetatable就会返回改字段的值,而setmetatable则会引发一个错误;如以下演示代码:
   
   
  1. function Set.new(l)
  2. local set = {}
  3. setmetatable(set, mt)
  4. for _, v in pairs(l) do set[v] = true end
  5. mt.__metatable = "You cannot get the metatable" -- 设置完我的元表以后,不让其他人再设置
  6. return set
  7. end
  8. local tb = Set.new({1, 2})
  9. print(tb)
  10. print(getmetatable(tb))
  11. setmetatable(tb, {})

__index元方法

当我们访问一个table中不存在的字段是,得到的结果是nil还是是其他值:

__newindex元方法


丢掉那该死的元表


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值