Lua 基础语法(三) 元表 metatable、元方法、模拟面向对象

核心知识点 :getmetatable/setmetatable;**index 查找元方法;**newindex 赋值元方法;运算符元方法;__tostring;点号与冒号调用区别、self 关键字;Lua 模拟继承、重写;Lua 只是面向过程语言,所有 OO 均为模拟实现

一、元表基础概念

1 概念

元表 (metatable) 可以改变普通 table 的行为 ,给 table 附加各种元方法(双下划线__开头);使用setmetatable()给目标表设置元表,getmetatable()获取表的元表;重复 setmetatable 会直接覆盖旧元表。

2 特性 & 规则

  1. setmetatable(目标表, 元表):给目标表绑定元表;

  2. getmetatable(目标表):获取该表的元表,没有返回 nil;

  3. 元表本身也是普通 table,里面存放各类__xxx元方法;

  4. 一张表只能拥有一个元表,多次 setmetatable 直接覆盖。

3 语法 & 代码片段

复制代码
local t1 = {}
local t2 = {}
setmetatable(t1, t2)  -- t1的元表设置为t2
local mt = getmetatable(t1)

4 相关知识点对比

无元表 table VS 绑定元表 table

普通 table:访问不存在 key 直接返回 nil,运算符不能直接对 table 运算;

绑定元表:可以拦截查找、赋值、运算、打印等行为,自定义处理逻辑。

5 模块拓展

元表是 Lua 模拟继承、多态的底层基础。

二、__index 查找元方法【最重要】

1 概念

当读取表[key]key 在本表中找不到 时触发**index 元方法;**index 可以是函数,也可以是一张表。

2 特性 & 规则

查找完整流程:

  1. 在当前目标表内查找 key,找到直接返回值;

  2. 当前表找不到 key → 判断是否存在元表;没有元表返回 nil;

  3. 有元表,判断元表是否存在__index;没有__index 返回 nil;

  4. 如果__index函数:执行该函数,返回函数返回值;

  5. 如果__index一张表:到这张表里面继续查找 key;

可以多层套元表,形成链式查找,用来模拟继承:自己找不到,去父类表查找

3 语法 & 代码片段

复制代码
local child = {}
local parent = {t=123}
local mt = {
    __index = parent
}
setmetatable(child, mt)
print(child.t) -- child没有t,触发__index,去parent找到,输出123

4 相关知识点对比

直接访问 nil VS __index 拦截查找

无元表:不存在 key 直接 nil;

__index 指向表:自动到另外一张表查找,模拟继承父类成员。

5 模块拓展

多层元表链式 = 类的多级继承。

三、__newindex 赋值元方法

1 概念

给表不存在的 key 做赋值 的时候触发__newindex;key 已经存在于本表,不会触发__newindex,直接修改本表值。

2 特性 & 规则

  1. 给表一个全新 key 赋值;

  2. 检测有没有元表,存在且有__newindex

  3. 不会往原表写入,而是交给__newindex 处理;

  4. __newindex 可以是函数,也可以指向另一张表,把新键值写入别的表。

⚠️注意:只有新 key 才触发;已经存在的 key 直接修改本表,不走 newindex

3 语法 & 代码片段

复制代码
local t6 = {}
local t4 = {}
local mt5 = {
    __newindex = t4
}
setmetatable(t6, mt5)
t6.t2 = 999 -- t6没有t2,触发newindex,写入t4,t6仍然是空表
print(t6.t2) -- nil
print(t4.t2) -- 999

4 相关知识点对比

普通赋值 VS __newindex 拦截赋值

普通:直接在当前表新增 key;

__newindex 拦截:可以把赋值重定向到别的表,做只读表、数据代理。

5 模块拓展

利用__newindex 实现只读表,禁止外部修改表。

四、运算符元方法(模拟运算符重载)

1 概念

元表中定义__add / __sub / __mul / __div / __mod / __concat等元方法,实现 table 之间+ - * / % ..运算;Lua 没有真正运算符重载,靠元方法模拟。

2 特性 & 规则

  1. __add(a,b)+加法;运算以左边操作数的元表__add 为准

  2. __sub减法、__mul乘法、__div除法、__mod取模;

  3. __concat对应..字符串拼接;

  4. 比较元方法__eq等于;

  5. 运算:优先取左边对象元方法执行;左边没有才看右边。

3 语法 & 代码片段

复制代码
local t7 = {1,2,3}
local t8 = {4,5,6}
local mtAdd = {
    __add = function(a,b)
        local res = {}
        for i=1,#a do
            res[i] = a[i]+b[i]
        end
        return res
    end
}
setmetatable(t7, mtAdd)
local ret = t7 + t8

4 相关知识点对比

原生 table 不支持加减 VS __add 元方法

默认 table 直接t1+t2直接报错;

设置__add 元方法后,自定义两表相加逻辑。

5 模块拓展

自定义向量、矩形 table 的加减运算。

五、__tostring 元方法

1 概念

print 打印 table 的时候会调用__tostring元方法;自定义 table 转为字符串输出行为,默认 print (table 输出内存地址)。

2 特性 & 规则

  1. print (table) 自动触发元表__tostring

  2. 元方法必须返回字符串;

  3. 不写元方法直接输出 table 内存地址。

3 语法 & 代码片段

复制代码
local tb = {10,20,30}
setmetatable(tb,{
    __tostring = function(t)
        return "表元素总和:"..(t[1]+t[2]+t[3])
    end
})
print(tb)

4 相关知识点对比

默认打印地址 VS __tostring 自定义输出

默认 print (t):输出table: 0xxxxxx内存地址;

__tostring:输出自定义业务字符串。

5 模块拓展

打印配表调试,美化 table 输出。

六、点号 . 与冒号 : 调用,self 关键字

1 概念

Lua 表内函数两种调用语法:.点号、:冒号;冒号会隐式传入 self 参数,self 等价 C#/C++ 的 this,代表调用者自身对象。

2 特性 & 规则

  1. 点号 .:不会自动传 self,所有参数必须手动写全;

  2. 冒号 : :定义和调用时,第一个参数隐式为self(谁调用 self 就是谁);

  3. 可以交叉混用:冒号定义,点号调用;点号定义,冒号调用,需要手动处理 self 参数。

3 语法 & 代码片段

复制代码
local obj = {a = 3}
--冒号定义,自带self
function obj:SetA(val)
    self.a = val
end
--冒号调用,自动把obj作为self传入
obj:SetA(10)
print(obj.a)

4 相关知识点对比

.点号 VS :冒号

.:无隐式 self,参数全部手动传入;

::隐式 self,self 就是调用这个函数的表实例,写成员函数首选冒号。

5 模块拓展

Lua 模拟面向对象全部依靠:+self + metatable __index。

七、Lua 模拟面向对象:继承与方法重写

1 概念

⚠️重点:Lua 本身是面向过程语言,没有 class 关键字,封装、继承、多态全部依靠 table+metatable 元方法做模拟实现

封装:模块 + local 私有变量;

继承:依靠__index元方法链式向上查找父类表;

重写:子类表写同名函数,优先在子类找到,不再往上父类查找;

new:自己封装 new 函数,返回实例 table。

2 特性 & 规则

  1. 父类一张 table,存放成员、成员函数;

  2. new 函数创建空实例表,给实例设置元表,元表__index = 父类

  3. 子类 new,实例元表__index 指向子类;子类找不到成员,继续通过元表链去父类查找;

  4. 子类写同名函数,优先子类执行,实现方法重写。

3 语法 & 代码片段(模拟继承)

复制代码
--基类Shape
local Shape = {}
Shape.__index = Shape
function Shape:new(t)
    local ins = t or {}
    setmetatable(ins, self)
    return ins
end
function Shape:GetArea()
    return 0
end
​
--子类Square,继承Shape
local Square = Shape:new()
Square.__index = Square
function Square:new(t,size)
    local ins = Shape:new(t or {})
    ins.size = size
    setmetatable(ins, Square)
    return ins
end
--重写父类方法
function Square:GetArea()
    return self.size * self.size
end
​
--创建实例
local sq = Square:new(nil,10)
print(sq:GetArea()) --输出100

4 相关知识点对比

原生 C# class VS Lua 模拟 OO

C#:class、public/private、override 原生语法支持;

Lua:全部 table+metatable+__index 模拟;没有真正类、没有虚函数。

5 模块拓展

XLua 框架大量使用这套模拟面向对象写法,C# 和 Lua 交互。

综合拓展

1 完整知识流程 setmetatable 设置元表 → **index 查找元方法模拟继承 →**newindex 赋值重定向 → 运算符元方法模拟重载 → __tostring 自定义打印 → 点号 / 冒号 self 语法 → new 函数模拟实例、子类重写父类方法。 2 高频易错清单

  1. index 只有 key 不存在才触发,本表已有 key 不会走index;

  2. __newindex 只有 ** 全新 key 赋值才触发,已存在 key 直接修改本表,不走 newindex;

  3. 运算符+优先取左边对象的元表__add;

  4. 冒号:自动隐式 self,点号.不会自动传 self;

  5. Lua 没有 class 关键字,继承全部是 metatable 模拟,不是原生面向对象。

3 核心考点 setmetatable/getmetatable;index 完整查找流程; newindex 触发条件;运算符元方法;**tostring;.:区别、self 含义;Lua 如何模拟继承(**index 元表链);Lua 不是面向对象语言。

4 进阶拓展学习方向 XLua C# 与 Lua 交互,CS 调用 Lua、Lua 调用 C#;userdata 类型;热更新实战。

相关推荐
维天说1 小时前
Agent会听人话,反而更难管
java·开发语言·人工智能
会飞的拖把1 小时前
Python 面向对象编程详解:从类与对象到动态属性方法
开发语言·python
大牧师2 小时前
Nest.js 微服务入门教程
开发语言·javascript·后端·微服务·node.js·nest.js·nest
格林威2 小时前
C# 图像异步落盘存储:基于Channel 配合 ArrayPool 实现异步落盘
开发语言·人工智能·数码相机·机器学习·计算机视觉·c#·视觉检测
软件黑马王子2 小时前
11.缓存池优化:窗口布局
开发语言·前端框架·c#
深圳市益普科技有限公司2 小时前
半导体MES的数字孪生:虚拟调试如何把上线风险提前清零
java·开发语言
低代码布道师2 小时前
外包项目管理平台全栈实战课02搭建数据底座:Next.js + PostgreSQL + Prisma 7
开发语言·javascript·postgresql·全栈开发
格林威2 小时前
C# 图像使用AVX2指令集:使用OpenCvSharp实现字节图像解压缩速度和map_image算子速度提升
开发语言·图像处理·人工智能·计算机视觉·c#·视觉检测·工业相机