当前位置: 首页 > news >正文

Lua之元表和元方法

Lua之元表和元方法

  • 元表
  • 元方法
    • __add
    • __index
    • __newindex
    • ps
  • 忽略元表:rawget和rawset
    • rawget
    • rawset

元表

Lua中的每个值都可以有一个元表!这个元表就是一个普通的Lua表,它用于定义原始值在特定操作下的行为。
如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。例如当你对非数字值做加操作时,Lua会检查该值的元表中的"__add"域下的函数。如果能找到,Lua则调用这个函数来完成加这个操作。
在Lua中,你不可以改变表以外其它类型的值的元表(除非你使用调试库),若想改变这些非表类型的值的元表,请使用C API。
表和完全用户数据有独立的元表(当然,多个表和用户数据可以共享同一个元表)。其它类型的值按类型共享元表,也就是说所有的数字都共享同一个元表,所有的字符串共享另一个元表等等。默认情况下,值是没有元表的,但字符串库在初始化的时候为字符串类型设置了元表。
元表决定了一个对象在数学运算、位运算、比较、连接、取长度、调用、索引时的行为。元表还可以定义一个函数,当表对象或用户数据对象在垃圾回收时调用它。

使用getmetatable函数来获取任何值的元表,使用setmetatable来替换一张表的元表。

local testStr = "hello, fightsyj"
local testTbl = {name = "fightsyj", age = 666}
-- getmetatable获取对象的元表
print("testStr`s metatable is ->", getmetatable(testStr))  -- 字符串库在初始化的时候为字符串类型设置了元表
print("testTbl`s metatable is ->", getmetatable(testTbl))

local testMetaTbl = {}
-- setmetatable设置元表,返回设置元表之后的对象
local finalTbl = setmetatable(testTbl, testMetaTbl)
dump(finalTbl, "finalTbl->")
print("testTbl`s metatable is ->", getmetatable(testTbl))
--[[
testStr`s metatable is ->	table: 00AA9780
testTbl`s metatable is ->	nil
- "finalTbl->" = {
-     "age"  = 666
-     "name" = "fightsyj"
- }
testTbl`s metatable is ->	table: 00DF1E60
]]

元方法

元表中的键对应着不同的事件名,键关联的那些值被称为元方法。在上面那个例子中引用的事件为"add",完成加操作的那个函数就是元方法。
接下来会给出一张元表可以控制的事件的完整列表。每个操作都用对应的事件名来区分。每个事件的键名用加有"__“前缀的字符串来表示。例如"add"操作的键名为字符串”__add"。需要注意的是Lua从元表中直接获取元方法,访问元表中的元方法永远不会触发另一次元方法。下面的代码模拟了Lua从一个对象obj中获取一个元方法的过程:
rawget(getmetatable(obj) or {}, “__” … event_name)
对于一元操作符(取负、求长度、位反),元方法调用的时候,第二个参数是个哑元,其值等于第一个参数。这样处理仅仅是为了简化Lua的内部实现(这样处理可以让所有的操作都和二元操作一致),这个行为有可能在将来的版本中移除。(使用这个额外参数的行为都是不确定的)

元方法描述
add+ 操作。如果任何不是数字的值(包括不能转换为数字的字符串)做加法,Lua就会尝试调用元方法。首先Lua检查第一个操作数(即使它是合法的),如果这个操作数没有为"__add"事件定义元方法,Lua就会接着检查第二个操作数。一旦Lua找到了元方法,它将把两个操作数作为参数传入元方法,元方法的结果(调整为单个值)作为这个操作的结果。如果找不到元方法,将抛出一个错误。
sub- 操作。行为和"add"操作类似。
mul* 操作。行为和"add"操作类似。
div/ 操作。行为和"add"操作类似。
mod% 操作。行为和"add"操作类似。
pow^ (次方)操作。行为和"add"操作类似。
unm- (取负)操作。行为和"add"操作类似。
idiv// (向下取整除法)操作。行为和"add"操作类似。
band& (按位与)操作。行为和"add"操作类似,不同的是Lua会在任何一个操作数无法转换为整数时尝试取元方法。
bor| (按位或)操作。行为和"band"操作类似。
bxor~ (按位异或)操作。行为和"band"操作类似。
bnot~ (按位非)操作。行为和"band"操作类似。
shl<< (左移)操作。行为和"band"操作类似。
shr>> (右移)操作。行为和"band"操作类似。
concat.. (连接)操作。行为和"add"操作类似,不同的是Lua在任何操作数既不是一个字符串,也不是数字(数字总能转换为对应的字符串)的情况下尝试元方法。
len# (取长度)操作。如果对象不是字符串Lua会尝试它的元方法。如果有元方法,则调用它并将对象以参数形式传入,而返回值(被调整为单个)则作为结果。如果对象是一张表且没有元方法,Lua使用表的取长度操作。其它情况,均抛出错误。
eq== (等于)操作。和 “add” 操作行为类似,不同的是Lua仅在两个值都是表或都是完全用户数据,且它们不是同一个对象时才尝试元方法。调用的结果总会被转换为布尔量。
lt< (小于)操作。和"add"操作行为类似,不同的是Lua仅在两个值不全为整数也不全为字符串时才尝试元方法。调用的结果总会被转换为布尔量。
le<= (小于等于)操作。和其它操作不同,小于等于操作可能用到两个不同的事件。首先,像"lt"操作的行为那样,Lua在两个操作数中查找"__le"元方法。如果一个元方法都找不到,就会再次查找"__lt"事件,它会假设a <= b等价于not (b < a)。而其它比较操作符类似,其结果会被转换为布尔量。
index索引table[key]。当table不是表或是表table中不存在key这个键时,这个事件被触发。此时,会读出table相应的元方法。尽管名字取成这样,这个事件的元方法其实可以是一个函数也可以是一张表。如果它是一个函数,则以table和key作为参数调用它。如果它是一张表,最终的结果就是以key取索引这张表的结果。(这个索引过程是走常规的流程,而不是直接索引,所以这次索引有可能引发另一次元方法。)
newindex索引赋值table[key] = value。和索引事件类似,它发生在table不是表或是表table中不存在key这个键的时候。此时,会读出table相应的元方法。同索引过程那样,这个事件的元方法既可以是函数,也可以是一张表。如果是一个函数,则以table、key以及value为参数传入。如果是一张表,Lua对这张表做索引赋值操作。(这个索引过程是走常规的流程,而不是直接索引赋值,所以这次索引赋值有可能引发另一次元方法。)一旦有了"newindex"元方法,Lua就不再做最初的赋值操作。(如果有必要,在元方法内部可以调用rawset来做赋值。)
call函数调用操作func(args)。当Lua尝试调用一个非函数的值的时候会触发这个事件(即func不是一个函数)。查找func的元方法,如果找得到,就调用这个元方法,func作为第一个参数传入,原来调用的参数(args)后依次排在后面。
mode弱表属性,赋予一张表弱引用属性。
gc在对象被GC的时候,会先调用元表里面的"gc"域。
tostring当调用tostring(obj)的时候,会先查找obj的元方法中的"__tostring",如果有就调用,没有就会打印obj的内存位置。
pairs迭代器的元方法,在执行pairs(t)的时候,会先找表t的元方法"__pairs",如果有就以t为参数调用他,如果没有,就返回三个值next函数,t已经nil。
metatable函数setmetatable和getmetatable会触发"__metatable"元方法。当Lua中的值拥有该元方法时,getmetatable就会返回这个字段的值,而setmetatable则会引发一个错误。因此我们可以使用"__metatable"元方法来保护任意值的元表,这样值的元表就不会被随意修改了。

下面着重介绍一下__add、__index和__newindex这三个元方法。

__add

__add类似于C++中的运算符重载,对算术运算符+进行重载,重新定义+的操作行为!

local testTbl1 = {name = "fightsyj", age = 666}
local testTbl2 = {sex = "Male"}
-- local testTbl3 = testTbl1 + testTbl2  -- 报错
local testMetaTbl = {}
testMetaTbl.__add = function(tbl1, tbl2)
   for key, value in pairs(tbl2) do
   	tbl1[key] = value
   end
   return tbl1
end
setmetatable(testTbl1, testMetaTbl)
local testTbl3 = testTbl1 + testTbl2
dump(testTbl3)
--[[
- "<var>" = {
-     "age"  = 666
-     "name" = "fightsyj"
-     "sex"  = "Male"
- }
]]

__index

查询表中不存在的元素时触发!这个事件的元方法可以是一个函数也可以是一张表。如果它是一个函数,则以table和key作为参数调用它。如果它是一张表,最终的结果就是以key取索引这张表的结果。

  • __index方法是一个表
local testTbl = {name = "fightsyj", age = 666}
local testMetaTbl = {}
testMetaTbl.__index = {sex = "Male"}
setmetatable(testTbl, testMetaTbl)
print(testTbl.name, testTbl.sex, testTbl.place)
-- fightsyj 	Male 	nil
  • __index方法是一个函数
local testTbl = {name = "fightsyj", age = 666}
local testMetaTbl = {}
testMetaTbl.__index = function(tbl, key)
   return string.format("%s is not exist !", key)
end
setmetatable(testTbl, testMetaTbl)
print(testTbl.name)
print(testTbl.sex)
--[[
fightsyj
sex is not exist !
]]

Lua查找一个表元素时的规则,其实就是如下3个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续;
2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续;
3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复 1、2、3;如果__index方法是一个函数,则返回该函数的返回值。

__newindex

更新表中不存在的元素时触发!这个事件的元方法既可以是函数,也可以是一张表。如果是一个函数,则以table、key以及value作为参数传入。如果是一张表,则对这张表做索引赋值操作。

  • __newindex方法是一个表
local testTbl = {name = "fightsyj"}
local newindex = {}
local testMetaTbl = {__newindex = newindex}
setmetatable(testTbl, testMetaTbl)
testTbl.name = "fightsyj2"
testTbl.age = 666
dump(testTbl, "testTbl->")
dump(testMetaTbl, "testMetaTbl->")
--[[
- "testTbl->" = {
-     "name" = "fightsyj2"
- }
- "testMetaTbl->" = {
-     "__newindex" = {
-         "age" = 666
-     }
- }
]]
  • __newindex方法是一个函数
local testTbl = {name = "fightsyj"}
local newindex = function(tbl, key, value)
	print(tbl, key, value)
end
local testMetaTbl = {__newindex = newindex}
setmetatable(testTbl, testMetaTbl)
testTbl.age = 666
dump(testTbl, "testTbl->")
dump(testMetaTbl, "testMetaTbl->")
--[[
table: 00A291E0	age	666
- "testTbl->" = {
-     "name" = "fightsyj"
- }
- "testMetaTbl->" = {
-     "__newindex" = function: 00A32688
- }
]]

ps

  1. 一旦有了"newindex"元方法,Lua就不再做最初的赋值操作;
  2. "newindex"元方法用来对表进行更新(类似set),"index"元方法则用来对表进行查询(类似get);

忽略元表:rawget和rawset

有时候我们希望直接改动或获取表中的值时,就需要rawget和rawset方法了。

rawget

rawget可以让你直接获取到表中索引的实际值,而不通过元表的__index元方法。

local testTbl = {name = "fightsyj", age = 666}
local testMetaTbl = {}
testMetaTbl.__index = {sex = "Male"}
setmetatable(testTbl, testMetaTbl)
-- 通过rawget直接获取testTbl中sex对应的值,不会触发元表的__index事件
local sexValue = rawget(testTbl, "sex")
print(sexValue)  -- nil

rawset

rawset可以让你直接为表中索引的赋值,而不通过元表的__newindex元方法。

local testTbl = {name = "fightsyj"}
local newindex = {}
local testMetaTbl = {__newindex = newindex}
setmetatable(testTbl, testMetaTbl)
-- 通过rawset直接对testTbl进行赋值操作,不会触发元表的__newindex事件
rawset(testTbl, "age", 666)
dump(testTbl, "testTbl->")
dump(testMetaTbl, "testMetaTbl->")
--[[
- "testTbl->" = {
-     "age"  = 666
-     "name" = "fightsyj"
- }
- "testMetaTbl->" = {
-     "__newindex" = {
-     }
- }
]]

参考:
Lua 5.3 参考手册
Lua查找表元素过程
Lua中的元表与元方法学习总结

相关文章:

  • 30分钟搞定BASH脚本编程[zz]
  • Lua之面向对象的实现
  • 不同种类的webservice错误信息
  • Lua中的require与package.loaded
  • 配置文件多个一个符号,导致struts抛出了匪夷所思的错误
  • 签名不对,请检查签名是否与开发平台上填写的一致
  • win终端工具Cmder的配置与使用
  • hibernate的lazy配置引起的问题
  • Lua实战之table.remove
  • 老婆说明书
  • cocos2d-lua:改变子节点优先级reorderChild
  • 几天工作中的三个技巧!
  • cocos2d-lua:动作Actions的使用
  • Lua实战之判断连续
  • Lua实战之统计字符串中任意字符出现的次数
  • 网络传输文件的问题
  • Bootstrap JS插件Alert源码分析
  • css属性的继承、初识值、计算值、当前值、应用值
  • JavaScript 奇技淫巧
  • JavaScript异步流程控制的前世今生
  • Java-详解HashMap
  • Java小白进阶笔记(3)-初级面向对象
  • jquery ajax学习笔记
  • Nacos系列:Nacos的Java SDK使用
  • Netty 4.1 源代码学习:线程模型
  • sessionStorage和localStorage
  • SpiderData 2019年2月13日 DApp数据排行榜
  • SpriteKit 技巧之添加背景图片
  • Web设计流程优化:网页效果图设计新思路
  • ------- 计算机网络基础
  • 使用SAX解析XML
  • 试着探索高并发下的系统架构面貌
  • 我建了一个叫Hello World的项目
  • 用jquery写贪吃蛇
  • 仓管云——企业云erp功能有哪些?
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • # Kafka_深入探秘者(2):kafka 生产者
  • #ifdef 的技巧用法
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据
  • (二)PySpark3:SparkSQL编程
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (规划)24届春招和25届暑假实习路线准备规划
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (终章)[图像识别]13.OpenCV案例 自定义训练集分类器物体检测
  • (转)【Hibernate总结系列】使用举例
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net CF下精确的计时器
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
  • .Net Remoting(分离服务程序实现) - Part.3