在Lua中,Metatable元表如何操作?

Lua中的Metatable(元表)是一个强大的特性,它允许我们改变表(table)的行为。下面是对Lua中的Metatable元表的详细介绍,包括语法规则和示例。

1.Metatable介绍

Metatable是一个普通的Lua表,它用于定义原始值在特定操作下的行为。每个表都可以有一个元表,这个元表通过特殊的键(以双下划线__开头)来定义元方法(metamethods),这些元方法可以响应不同的事件。Metatable可以控制对象在算术操作、顺序比较、连接、长度操作和索引时的行为。

为什么需要元表?为了给用户提供一种机制来定义非预定义的操作行为。例如,两个表不能直接相加,但是通过元表我们可以定义__add元方法来实现这一点。

什么是元方法?元表通过特殊的键来定义元方法,这些键通常以双下划线__开头。例如,__index用于定义当访问表中不存在的键时的行为,__newindex用于定义当对表中不存在的键进行赋值时的行为,__add用于定义两个表相加的操作等。

2.设置与获取元表

Metatable有两个很重要的函数用于处理元表,具体介绍见下表。

函数

作用

setmetatable(table, metatable)

为表设置元表,其中table是要设置元表的表,metatable是元表。需要注意的是,如果元表中已存在__metatable

字段,就不能再用setmetatable()修改该表的元表了。

getmetatable(table)

获取表的元表,其中table是要获取元表的表。

以下示例演示了如何对指定的表设置元表。

-- table_setget_test.lua脚本文件
local mytable = {}
print(getmetatable(mytable)) -- 输出nil,表示当前没有元表

local mymetatable = {}
setmetatable(mytable, mymetatable)
assert(getmetatable(mytable) == mymetatable) -- 确认mytable现在有关联的元表mymetatable

3.常用元方法

3.1 __index元方法

__index元方法表示,当访问一个不存在于表中的键时触发。它可以是另一个表或是一个函数。如果是表,Lua会在那个表中查找;如果是函数,则调用它并将原来的表和缺失的键作为参数传递。

我们可以使用lua命令进入交互模式来查看指定键的信息。

-- indext_key_test.lua脚本文件
local userInfo = {}
local user = {name="Tom", gender="男", age=24, phone="17858802222"}

user_info = setmetatable(userInfo, {__index = user})

print(userInfo.name)
print(userInfo.email)

执行以上脚本代码,程序输出结果如下。

Tom
nil

__index元方法查看表中元素是否存在,如果不存在,返回结果为nil;如果存在则由__index返回结果。示例代码见下。

-- index_function_test.lua脚本文件
local mytable = setmetatable({key1 = "value1"}, {
    __index = function(mytable, key)
      if key == "key2" then
        return "value2"
      else
        return nil
      end
    end
  })

print(mytable.key1, mytable.key2)

执行以上脚本代码,程序输出结果如下。

value1    value2

对上述示例做如下的解析:

  • mytable表赋值为{key1 = "value1"}。
  • mytable设置了元表,元方法为__index
  • 在mytable表中查找"key1",如果找到,返回该键的值"value1",找不到则继续。
  • 在mytable表中查找"key2",如果找到,返回该键的值"value2",找不到则继续。
  • 判断元表有没有__index方法,如果__index方法是一个函数,则调用该函数。
  • 元方法中查看是否传入"key2"键的参数(mytable.key2已设置),如果传入"key2"参数返回"value2",否则返回nil。

Lua查找一个表元素时的规则,可以总结成如下的三个步骤。

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

3.2 __newindex元方法

在Lua编程语言中,__newindex是一个元方法(metamethod),它允许你自定义对表(table)中不存在的键进行赋值时的行为。当尝试给一个表中的非现有字段赋值,并且该表有一个带有__newindex元方法的元表(metatable)时,Lua不会直接设置这个新字段,而是调用__newindex元方法。

__newindex的行为取决于它是如何定义的:

  • 如果__newindex是一个函数,那么它将接收三个参数:事件发生时的表本身(或其代理)、被赋值的键、以及被赋值的值。你可以在这个函数内部定义任何逻辑来处理赋值操作。
  • 如果__newindex是一个表,那么Lua将在这个表中创建一个新的条目,而不是在原始表中创建。这可以用来实现继承或重定向赋值。

下面是一个简单的例子,展示了如何使用__newindex来拦截对表的新索引赋值。

-- newindex_test.lua脚本文件
-- 创建一个普通的表
local myTable = {}

-- 创建一个带有__newindex元方法的元表
local metaTable = {
  __newindex = function(tbl, key, value)
    print("Setting " .. tostring(key) .. " to " .. tostring(value))
    rawset(tbl, key, value) -- 使用rawset来避免递归调用__newindex
  end
}

-- 将元表应用到普通表上
setmetatable(myTable, metaTable)

-- 现在当我们尝试为myTable中不存在的键赋值时
myTable.x = 10
-- 我们会看到输出: Setting x to 10

-- 已经存在的键仍然可以直接赋值
myTable.x = 20 -- 这里不会触发__newindex因为键已经存在

在这个例子中,当你尝试为myTable设置一个新的键时,__newindex元方法会被调用,并打印出正在设置的键和值。对于已经存在的键,直接赋值不会触发__newindex元方法。如果你想要对所有赋值都应用自定义行为,你需要更复杂的逻辑来检查键是否已经存在于表中。

3.3 __tostring元方法

在Lua中,__tostring元方法允许你自定义当尝试将一个表转换为字符串时的行为。通常情况下,当你对一个表使用tostring函数时,如果没有指定__tostring元方法,它会返回类似table: 0x地址的默认字符串表示,其中的地址是该表在内存中的位置。然而,通过设置__tostring元方法,你可以让Lua在转换时返回更友好的、自定义的字符串表示。

__tostring元方法用于修改表的输出行为。以下示例我们自定义了表的输出内容。

-- tostring_test.lua脚本文件
-- 创建一个普通的表,用于存储一些数据
local data = { name = "Alice", age = 30 }

-- 定义元表,并添加__tostring元方法
local mt = {
  __tostring = function(tbl)
    -- 自定义输出格式
    return string.format("Name: %s, Age: %d", tbl.name, tbl.age)
  end
}

-- 将元表应用到数据表上
setmetatable(data, mt)

-- 使用tostring函数来获取表的字符串表示
print(tostring(data)) -- 输出:Name: Alice, Age: 30

-- 直接打印表也会调用__tostring元方法
print(data) -- 输出:Name: Alice, Age: 30

执行以上脚本代码,程序输出结果如下。

Name: Alice, Age: 30
Name: Alice, Age: 30

在这个例子中,我们创建了一个名为data的普通表,并为其指定了一个包含__tostring元方法的元表 mt。当我们使用tostring或者直接打印这个表的时候,Lua会调用__tostring方法并按照我们自定义的方式输出表的内容。这种方式可以使得调试信息更加清晰,或者让日志记录更为友好。

3.4 __call元方法

在Lua中,__call元方法允许将表(table)当作函数来调用。当一个表被调用(就像调用函数一样,使用表名加上括号和参数,例如myTable()语法)时,如果这个表的元表(metatable)中定义了__call元方法,Lua就会调用这个元方法,并将表本身以及任何传递给它的参数作为参数传递给__call。

这特别有用当你想创建一种“可调用”的对象时,比如闭包、类的实例化构造器、或者任何你想要通过函数调用语法来触发行为的地方。

以下是一个简单的例子,展示如何使用__call元方法来创建一个可调用的表。

-- call_test.lua脚本文件
-- 创建一个简单的表
local greet = {}

-- 定义元表,并添加__call元方法
local mt = {
  __call = function(tbl, name)
    -- 当表被调用时返回问候语
    return "Hello, " .. tostring(name) .. "!"
  end
}

-- 将元表应用到greet表上
setmetatable(greet, mt)

-- 现在可以像调用函数一样调用greet表
print(greet("World")) -- 输出:Hello, World!
print(greet("Lua"))   -- 输出:Hello, Lua!

执行以上脚本代码,程序输出结果如下。

Hello, World!
Hello, Lua!

通过以上内容的学习,我们知道元表可以很好的简化我们的代码功能,所以了解Lua的元表,可以让我们写出更加简单优秀的Lua代码。

4.其他元方法

表中对应的操作列表如下(需要注意的是,__是两个下划线)。例如,__add键包含在元表中,并进行相加操作。

模式

描述

__add

加,对应算数运算符'+',接收两个操作数作为参数,并返回结果。

__sub

减,对应算数运算符'-',接收两个操作数作为参数,并返回结果。

__mul

乘,对应算数运算符'*',接收两个操作数作为参数,并返回结果。

__div

除,对应算数运算符'/',接收两个操作数作为参数,并返回结果。

__mod

取模,对应算数运算符'%',接收两个操作数作为参数,并返回结果。

__pow

幂运算,对应算数运算符'^',接收两个操作数作为参数,并返回结果。

__unm

定义了一元减法(取负)的行为。

__concat

定义字符串连接操作符'..'的行为。

__eq

定义等于'=='比较操作符的行为。

__lt

定义小于'<'比较操作符的行为。

__le

定义小于等于'<='比较操作符的行为。

__len

定义长度操作符'#'的行为。

__gc

对于用户数据类型(userdata),可以定义垃圾回收期间的动作。

下面是一个简单的例子,展示如何使用__add元方法来定义两个表的加法操作。假设我们有两个表,每个表包含一个字段value,我们希望对这些表的value字段进行加法运算。

-- add_test.lua脚本文件
-- 定义两个表
local table1 = {value = 10}
local table2 = {value = 20}

-- 定义元表,其中包含__add元方法
local mt = {
  __add = function(a, b)
    -- 创建一个新表,其value字段是两个表value字段的和
    return {value = a.value + b.value}
  end
}

-- 将元表设置为这两个表的元表
setmetatable(table1, mt)
setmetatable(table2, mt)  -- 通常只需设置一个参与运算的表的元表,但为展示效果,这里都设置了

-- 执行加法操作
local result = table1 + table2

-- 输出结果
print(result.value)  -- 输出:30

执行以上脚本代码,程序输出结果如下。

30

5.总结

Lua的元表(Metatable)机制提供了对表行为的深度定制能力,允许开发者定义非预定义操作的行为。通过将一个普通的Lua表作为元表关联到另一个表上,可以利用一系列以双下划线开头的特殊键(元方法),来响应如算术运算、比较、索引访问等事件。例如,`__add`元方法可以让两个表相加,而`__index`和`__newindex`则分别控制读取和设置不存在键的行为。此外,还有诸如`__tostring`用于自定义表转字符串表示,以及`__call`使得表能像函数一样被调用。

设置与获取元表的功能由`setmetatable()`和`getmetatable()`两个内置函数提供。当访问或修改表中不存在的键时,Lua会检查表是否有关联的元表,并根据其中定义的元方法执行相应逻辑。这不仅增加了语言的灵活性,也使得实现继承、代理模式等高级特性成为可能。元表是Lua语言的一个强大工具,极大地扩展了表这一核心数据结构的功能性,让开发者能够编写出更加简洁且高效的代码。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Ubuntu20.04】Apollo10.0 Docker容器部署+常见错误解决

官方参考文档【点击我】 Apollo 10.0 版本开始&#xff0c;支持本机和Docker容器两种部署方式。 如果您使用本机部署方式&#xff0c;建议使用x86_64架构的Ubuntu 22.04操作系统或者aarch64架构的Ubuntu 20.04操作系统。 如果您使用Docker容器部署方式&#xff0c;可以使用x…

springboot整合Logback

Logback介绍 描述 Logback是由log4j创始人设计的另外一种开源日志组件&#xff0c;性能比log4j要好。相对是一个可靠、通用、快速而又灵活的Java日志框架。 Logback主要分三个模块 1、logback-core&#xff1a;其他两个模块的基础模块 2、logback-classic&#xff1a;它是lo…

仓库叉车高科技安全辅助设备——AI防碰撞系统N2024G-2

在当今这个高效运作、安全第一的物流时代&#xff0c;仓库作为供应链的中心地带&#xff0c;其安全与效率直接关系到企业的命脉。 随着科技的飞速发展&#xff0c;传统叉车作业模式正逐步向智能化、安全化转型&#xff0c;而在这场技术革新中&#xff0c;AI防碰撞系统N2024G-2…

学习笔记|arduino uno r3| RGB 灯珠|Atmega328P|PWM|analogWrite|analogRead函数: RGB灯珠呼吸灯

目录 RGB 灯珠呼吸灯实验RGB 灯珠实验概述工作原理组件清单接线程序代码编译和执行 Tips&#xff1a; Arduino常用的函数解释analogWrite(pin, value)函数analogRead(pin)函数 总结 RGB 灯珠呼吸灯实验 RGB 灯珠实验概述 1-三色LED黑板模块的PCB颜色为黑色&#xff0c;使用5M…

杰发科技——使用ATCLinkTool解除读保护

0. 原因 在jlink供电电压不稳定的情况下&#xff0c;概率性出现读保护问题&#xff0c;量产时候可以通过离线烧录工具避免。代码中开了读保护&#xff0c;但是没有通过can/uart/lin/gpio控制等方式进行关闭&#xff0c;导致无法关闭读保护。杰发所有芯片都可以用本方式解除读保…

ICLR2017 | I-FGSM | 物理世界中的对抗样本

Adversarial Examples in The Physical World 摘要-Abstract引言-Introduction生成对抗图像的方法-Methods of Generating Adversarial Images对抗样本的图片-Photos of Adversarial Examples对抗图像的破坏率-Destruction Rate of Adversarial Images实验设置-Experimental Se…

MySQL(四)MySQL Select语句

1. MySQL Select语句 1.1. 基本查询语句 mysql>select 列名 from 表名;(基本结构查询某一列) mysql>select 列名1,列名2 from 表名;(查询所有列多列) mysql>select * from 表名;(*代表查询所有列) 查询时可以给列设定别名通过as 关键字&#xff0c;别名可以是汉字&a…

高并发写利器-组提交,我的Spring组件实战

高并发写优化理论 对于高并发的读QPS优化手段较多&#xff0c;最经济简单的方式是上缓存。但是对于高并发写TPS该如何提升&#xff1f;业界常用的有分库分表、异步写入等技术手段。但是分库分表对于业务的改造十分巨大&#xff0c;涉及迁移数据的麻烦工作&#xff0c;不会作为…

C++Primer 变量

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

【模型】Qwen2-VL 服务端UI

1. 前言 最近在测试VLM模型&#xff0c;发现官方的网页demo&#xff0c;代码中视频与图片分辨率可能由于高并发设置的很小&#xff0c;导致达不到预期效果&#xff0c;于是自己研究了一下&#xff0c;搞了一个简单的前端部署&#xff0c;自己在服务器部署了下UI界面&#xff0…

分布式事务介绍 Seata架构与原理+部署TC服务 示例:黑马商城

1. 什么是分布式事务? 在分布式系统中&#xff0c;如果一个业务需要多个服务合作完成&#xff0c;而且每一个服务都有事务&#xff0c;多个事务必须同时成功或失败&#xff0c;这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务。 打个比…

uni-app:实现普通选择器,时间选择器,日期选择器,多列选择器

效果 选择前效果 1、时间选择器 2、日期选择器 3、普通选择器 4、多列选择器 选择后效果 代码 <template><!-- 时间选择器 --><view class"line"><view classitem1><view classleft>时间</view><view class"right&quo…

C++Primer 基本类型

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

纯前端实现将pdf转为图片(插件pdfjs)

需求来源 预览简历功能在移动端&#xff0c;由于用了一层iframe把这个功能嵌套在了app端&#xff0c;再用一个iframe来预览&#xff0c;只有ios能看到&#xff0c;安卓就不支持&#xff0c;查了很多资料和插件&#xff0c;原理基本上都是用iframe实现的。最终转换思路&#xf…

基于FPGA的出租车里程时间计费器

基于FPGA的出租车里程时间计费器 功能描述一、系统框图二、verilog代码里程增加模块时间增加模块计算价格模块上板视频演示 总结 功能描述 &#xff08;1&#xff09;&#xff1b;里程计费功能&#xff1a;3公里以内起步价8元&#xff0c;超过3公里后每公里2元&#xff0c;其中…

Unix 域协议汇总整理

Unix 域协议是一种用于同一台计算机上进程间通信&#xff08;IPC&#xff09;的技术。它提供了一种比基于网络的套接字更高效的方式来在本地进程中交换数据。Unix 域协议使用文件系统作为通信的媒介&#xff0c;并且只限于在同一台计算机上运行的进程之间进行通信。 Unix 域套接…

JVM学习:CMS和G1收集器浅析

总框架 一、Java自动内存管理基础 1、运行时数据区 运行时数据区可分为线程隔离和线程共享两个维度&#xff0c;垃圾回收主要是针对堆内存进行回收 &#xff08;1&#xff09;线程隔离 程序计数器 虚拟机多线程是通过线程轮流切换、分配处理器执行时间来实现的。为了线程切换…

1.C语言教程:历史、特点、版本与入门示例

目录 1.历史2.特点3.版本4.编译5.Hello World 示例 1.历史 本篇原文为&#xff1a;C语言教程&#xff1a;历史、特点、版本与入门示例。 更多C进阶、rust、python、逆向等等教程&#xff0c;可去此站查看&#xff1a;酷程网 C 语言的诞生源于 Unix 系统的开发需求。 1969 年…

lec7-路由与路由器

lec7-路由与路由器 1. 路由器硬件 路由器的硬件部分&#xff1a; 断电失去&#xff1a; RAM断电不失去&#xff1a;NVRAM&#xff0c; Flash&#xff0c; ROMinterface也算是一部分 路由器是特殊组件的计算机 console 口进行具体的调试 辅助口&#xff08;Auxiliary&…

spring防止重复点击,两种注解实现(AOP)

第一种&#xff1a;EasyLock 简介 为了简化可复用注解&#xff0c;自己实现的注解&#xff0c;代码简单随拿随用 使用方式 1.创建一个注解 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface EasyLock {long waitTime() default …