【Lua学习笔记】Lua进阶——Table(3) 元表

news2024/11/13 23:34:03

在这里插入图片描述
接上文

文章目录

  • 元表
    • `__tostring`
    • `__call`
    • `__index`
    • `__newindex`
    • 运算符元方法
    • 其它元表操作


元表

Q:为什么要使用元表?

A:在Lua中,常常会需要表与表之间的操作。元表中提供了一些元方法,通过自定义元方法可以实现想要的功能,相当于面向对象中给你一系列方法让你重载。

使用下列代码设置元表:

meta={}
table={}
setmetatable(table,meta) -- 第一个元素为子表,第二个元素为元表

通常在元表中操作分为三步:

  1. 操作子表
  2. 检测是否有元表
  3. 若有元表,检测有无对应元方法,没有元方法则返回对应操作本来的处理。若有对应元方法执行元方法。

元方法索引一般以"__"(两个下划线,我想这是因为命名私有静态变量常常以一个下划线作为开头)作为开头,例如下面的例子:

__tostring

meta = {}
table={}
setmetatable(table, meta)
print(table)
输出:
table: 00ABA2A8
meta = {
    __tostring = function ()  <--注意两个下划线
        return "123"
    end
}
table={}
setmetatable(table, meta)
print(table)

输出:
123

上例相当于使用元方法重载print函数

meta = {
    __tostring = function (t)
        return t.name
    end
}
table={name = "233"}
setmetatable(table, meta)
print(table)

输出:
233

在上例中,即使我们未指定元方法的入参,但是因为子表和元表的关联,元方法会自动地将子表作为参数传入元方法。


__call

让我们再定义一个__call元方法

meta = {
    __tostring = function ()
        return "456"
    end,
    __call = function(a)
        print(a.name)
        print(a)
        print("call")
    end
}
table={name = "123"}
setmetatable(table, meta)
table(2)  --无论table(x)中给出的x为几,结果都是一样的

输出:
123
456
call

定义了__call元方法后,我们使用table(index),发现__call元方法打印了123,456和call,123是子表的元素,而456是__tostring的返回结果。这意味着__call方法调用了子表作为入参a。并且像我们上面的打印样例一样,print(a)方法又同时会调用__tostring的元方法。而table(2)中的参数2很明显被无视了。

现在我们可以确定一个基本规则:当使用__tostring__call元方法时,我们定义的第一个参数一定是子表table本身,只有定义更多参数才能接收其它入参:

meta = {
    __tostring = function ()
        return "456"
    end,
    __call = function(a,b)
        print(a)
        print(b)
        print("call")
    end
}
table={name = "123"}
setmetatable(table, meta)
table(2)

输出:
456
2
call

__index

meta = {
}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)

输出:
nil

我们想要找到表中的name索引,当然是没有的,输出结果是nil
那能不能让元表拥有这个索引呢?答案是不行,因为找的还是table1:

meta = {name =1}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)

输出:
nil

现在我们定义一个元方法 __index,它会指向一个其他的查询表

meta = { name = 2 }
meta.__index = meta
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)

输出:
2

整个查找的流程其实是:

  1. 查询子表
  2. 子表查询失败,查询元表有无 __index元方法
  3. 若有,则查询 __index元方法指向的表

但是还有种情况,建议不要把__index元方法在元表内部定义:

meta = {
    name = 2,
    __index = meta,
}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)


输出:
nil   --不明觉厉

还可以套娃

meta = {
}
metaFather = {
    name =4,
}
table1 ={age =1}
meta.__index = meta  --让元表的index指向meta,子表找不到就去meta里找
metaFather.__index =metaFather --让元表的index指向metaFather,子表找不到就去metaFather里找
setmetatable(meta,metaFather)
setmetatable(table1, meta)
print(table1.name)

输出:i
4  --允许套娃

__newindex

看4个例子:

meta = {}
table1 ={}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)

输出:
1

meta = {}
table1 ={age = 1}
meta.__newindex = {}
setmetatable(table1, meta)
print(table1.age)

输出:
1

meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age = 1
print(table1.age)

输出:
nil

meta = {}
table1 ={}
meta.__newindex = {}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)

输出:
1

有没有很诡异?其实很好理解,这是由于__newindex元方法,它的作用其实是将子表新加入的元素加入到__newindex所指向的表而不修改子表(当然__newindex也可以套娃):

meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
print(meta.__newindex.age)

输出:
nil
1


运算符元方法

meta = {
    __sub = function (t1,t2)
        return t1.age - t2.age
    end
}
table1={age=1}
table2={age=2}
setmetatable(table1, meta)  --无论把元表设置给table1还是table2,结果都一样
print(table1 - table2)

输出:
-1

我们发现使用运算符元方法的时候,第一个参数也不默认是绑定的子表了。而是根据运算符的左右变量依次给元方法赋值。而且无论元表设置给减数还是被减数,最终结果都是不变的。运算符元方法较多,这里就不详细列举了,从菜鸟教程上直接抄了个表格:

元方法描述
__add对应的运算符 ‘+’
__sub对应的运算符 ‘-’
__mul对应的运算符 ‘*’
__div对应的运算符 ‘/’
__mod对应的运算符 ‘%’
__unm对应的运算符 ‘-’
__concat对应的运算符 ‘..’
__pow对应的运算符 ‘^’
__eq对应的运算符 ‘==’
__lt对应的运算符 ‘<’
__le对应的运算符 ‘<=’

在这之中除了正常的数学运算,我们还需要讲一下比较特殊的逻辑判断的元方法(就是上表中加粗的几列),这几个元方法需要运算符的双方都绑定同一个元表。原因是元方法没有提供大于符号,运算a>b的元方法其实相当于运算b<a的原方法,这就需要b也绑定元表,因此逻辑运算硬性要求运算双方都绑定同一个元表,让我们看看下列的例子:

meta = {
    __eq= function (t1,t2)
        return true
    end,
}
table1 = { age = 1 }
table2 = { name = nil }
setmetatable(table1, meta)
print(table1 == table2)
setmetatable(table2, meta)
print(table1 == table2)

输出:
false
true

我们可以发现上述的相等的逻辑判断是有问题的,第一个print出false,第二个print出true。

在第一个print的时候,只有table1绑定了元表,而table2没有,之所以return false是因为在逻辑运算的时候需要左右双方都绑定同一个元表。

而在第二个print的时候打印了true,即使table1table2内容不同。因为这是我们自定义的元方法,我们默认返回的就是true

几道例题,请思考下列几个例子的输出结果:

meta = {
    __eq= function (t1,t2)
        if t1.age == t2.age then
            return true
        end
    end,
}
table1 = { age = 1 }
table2 = { age = 2 }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)

输出:
false

上述例子中,我们用__eq元方法来判断age索引值是否相同,显然是不同的,因此是false

meta = {
    __eq= function (t1,t2)
        if t1.name == t2.name then
            return true
        end
    end,
}
table1 = { age = 1 }
table2 = { name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)

输出:
true

上述例子中,我们用元方法判断name索引值是否相同,而table1没有name索引,默认为nil,table2有name = nil,因此是true

meta = {
    __eq= function (t1,t2)
        if t1 == t2 then
            return true
        end
    end,
}
table1 = { age = 1 }
table2 = { name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)

输出:
栈溢出

上述例子中,我们在元方法内部判断t1==t2,而进行判断时相当于再次调用了__eq元方法,因此会无限循环最终导致栈溢出。


其它元表操作

print(getmetatable(table1)) --使用getmetatable方法获得table1的元表地址

输出:
table: 00BBA320
meta = { name = 2 }
meta.__index = meta
table1 = { age = 1 }
setmetatable(table1, meta)
print(rawget(table1,"nane"))  --rawget方法只查询子表,无视元表index

输出:
nil
meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
rawset(table1, "age", 1) --rawset方法可以无视元表的newindex,直接修改子表
print(table1.age)

输出:
nil
1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/801888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux Day03

一、基础命令(在Linux Day02基础上补充) 1.10 find find 搜索路径 -name 文件名 按文件名字搜索 find 搜索路径 -cmin -n 搜索过去n分钟内修改的文件 find 搜索路径 -ctime -n搜索过去n分钟内修改的文件 1&#xff09;按文件名字 2&#xff09;按时间 1.11 grep 在文件中过…

m1 docker安装tomcat

背景 看到有同事尝试使用docker搭建tomcat服务&#xff0c;然后用nginx实现服务的负载均衡&#xff0c;但是遇到了挂载的问题&#xff0c;于是我在我自己的mac上尝试了一下。 实践过程 在本地新建tomcat的目录&#xff0c;下方挂载对应的数据文件和脚本文件。 其中&#xff…

小皮面板新增一个新网页页面

复制到根目录下&#xff0c;根目录 这里查看根目录。 然后点创建网站&#xff0c;基本配置里写入域名&#xff0c;还要刚才的网页文件夹&#xff0c;即index所在文件夹&#xff0c;index就是网页页面。 网址就是&#xff1a;http://xxxx.com/xxxx/ 参考小皮面板官网文章 htt…

【STL】模拟实现反向迭代器

目录 1. 读源码 2. 搭建框架 3. 迭代器的操作 operator*() operator->() operator() operator--() operator!() 4. 实现 list 的反向迭代器 5. 实现 vector 的反向迭代器 6. 源码分享 写在最后&#xff1a; 1. 读源码 我们之前实现的 vector&#xff0c;list…

购物车功能实现(小兔鲜儿)【Vue3】

购物车 流程梳理和本地加入购物车实现 购物车业务逻辑梳理拆解 整个购物车的实现分为两个大分支, 本地购物车操作和接口购物车操作由于购物车数据的特殊性,采取Pinia管理购物车列表数据并添加持久化缓存 本地购物车 - 加入购物车实现 添加购物车 基础思想&#xff1a;如果…

力扣每日一题--2050. 并行课程 III(拓补排序例题)

题目描述&#xff1a; 给你一个整数 n &#xff0c;表示有 n 节课&#xff0c;课程编号从 1 到 n 。同时给你一个二维整数数组 relations &#xff0c;其中 r e l a t i o n s [ j ] [ p r e v C o u r s e j , n e x t C o u r s e j ] relations[j] [prevCoursej, nextCou…

【云原生】Docker容器命令监控+Prometheus监控平台

目录 1.常用命令监控 docker ps docker top docker stats 2.weave scope 1.下载 2.安装 3.访问查询即可 3.Prometheus监控平台 1.部署数据收集器cadvisor 2.部署Prometheus 3.部署可视化平台Gragana 4.进入后台控制台 1.常用命令监控 docker ps [rootlocalhost ~…

手把手教你怎么写顺序表

目录 一、顺序表有什么功能&#xff1f; 二、实现顺序表的各个功能 1.前置准备 2.初始化顺序表 3.顺序表扩容 4.打印顺序表 5.增加顺序表成员 5.1尾增 5.2头增 6.删除顺序表中成员的内容 6.1尾删 6.2头删 7.查找成员 8.修改(替换) 9.插入(在目标位置插入成员) 10.定…

Kong Ming Qi hdu7321

Problem - 7321 题目大意&#xff1a;在(n2)*(m2)的棋盘上&#xff0c;中间摆满了n*m个棋子&#xff0c;每个棋子可以向上下左右四个方向移动&#xff0c;对于相邻的三格&#xff0c;移动前后分别为 有 有 无->无 无 有&#xff0c;问最后棋盘上最少能剩下几个棋子 1<n…

论文解读|PF-Net:用于 3D 点云补全的点分形网络

原创 | 文 BFT机器人 01 背景 从激光雷达等设备中获取的点云往往有所缺失&#xff08;反光、遮挡等&#xff09;&#xff0c;这给点云的后续处理带来了一定的困难&#xff0c;也凸显出点云补全作为点云预处理方法的重要性。 点云补全&#xff08;Point Cloud Completion&#x…

Drools用户手册翻译——第四章 Drools规则引擎(七)规则执行模式和线程安全

甩锅声明&#xff1a;本人英语一般&#xff0c;翻译只是为了做个笔记&#xff0c;所以有翻译错误的地方&#xff0c;错就错了&#xff0c;如果你想给我纠正&#xff0c;就给我留言&#xff0c;我会改过来&#xff0c;如果懒得理我&#xff0c;就直接划过即可。 drools的规则执…

HighTec 工程属性介绍2

目录 首先需要创建或导入一个工程&#xff0c;如下图&#xff1a; 有两种方式可以进入工程的属性面板&#xff1a; 选中工程&#xff0c;右键点击 Properies . 选中工程&#xff0c;按键 Alt Enter. 进入工程属性面板之后&#xff0c;左侧是书签标记&#xff0c;右侧是该书…

How to choose WIFI7 IPQ9554- WIFI6 IPQ8072? Who has the better performance?

How to choose WIFI7 IPQ9554- WIFI6 IPQ8072? Who has the better performance? The Wi-Fi standard is Wi-Fi 6, also known as 802.11ax. Wi-Fi 6 brings significant speed, capacity and performance improvements over the previous Wi-Fi 5 (802.11ac) standard. It p…

如何做好项目管理?年薪百万项目大佬一直在用这11张图!

日常工作中&#xff0c;我们会遇到各种大大小小的工作项目&#xff0c;如何能让项目保质保量的完成&#xff0c;就需要项目管理。项目管理是什么&#xff1f;一句话解释&#xff1a;在有限的时间内&#xff0c;在约束的范围中&#xff0c;集合有限资源来完成项目目标。 本周小编…

如何安装mmcv?官网解答

pip install -U openmim mim install mmcv

什么是宏变量和宏替换?

在Java中&#xff0c;宏变量和宏替换通常是与C/C预处理器相关的概念&#xff0c;而不是Java本身的特性。在Java中&#xff0c;我们通常不使用预处理器指令(例如宏定义)来进行代码替换。Java使用的编译器并不支持预处理器指令&#xff0c;因此没有宏替换的概念。 然而&#xff0…

移动硬盘文件或目录损坏且无法读取

早上插上硬盘&#xff0c;拔的时候不太规范&#xff0c;再插进去就显示无法读取了 搜了很多方法&#xff0c;很多让使用什么软件进行恢复 还参考了这个方法&#xff0c;但是我的属性打开跟博主的完全不一样 最后&#xff0c;参考移动硬盘“文件或目录损坏&#xff0c;无法读取…

[NOIP2007 普及组] 守望者的逃离

[NOIP2007 普及组] 守望者的逃离 题目背景 恶魔猎手尤迪安野心勃勃&#xff0c;他背叛了暗夜精灵&#xff0c;率领深藏在海底的娜迦族企图叛变。 题目描述 守望者在与尤迪安的交锋中遭遇了围杀&#xff0c;被困在一个荒芜的大岛上。 为了杀死守望者&#xff0c;尤迪安开始…

把一个列表拆成N个子列表的四种方法

编程的方法往往不止一种&#xff0c;比如怎么把一个Python种的列表拆成N个子列表&#xff0c;我们可以很容易找到N种方法&#xff0c;也许这就是编程的魅力所在。 一、列表表达式法 这种方法最为简洁&#xff0c;不过可读性差一些 这个方法中&#xff0c;即使原始列表的数量无…

途乐证券:美联储第11次加息 年内降息预期下降

7月26日&#xff0c;美国联邦储藏委员会主席鲍威尔在华盛顿到会记者会。新华社发 当地时刻7月26日&#xff0c;美国联邦储藏委员会一如预期加息25个基点。商场对美联储未来是否会持续加息尚未彻底构成共识&#xff0c;但对年内美联储将敞开降息周期的预期下降。 抑通胀 美联储…