核心知识点 :getmetatable/setmetatable;**index 查找元方法;**newindex 赋值元方法;运算符元方法;__tostring;点号与冒号调用区别、self 关键字;Lua 模拟继承、重写;Lua 只是面向过程语言,所有 OO 均为模拟实现
一、元表基础概念
1 概念
元表 (metatable) 可以改变普通 table 的行为 ,给 table 附加各种元方法(双下划线__开头);使用setmetatable()给目标表设置元表,getmetatable()获取表的元表;重复 setmetatable 会直接覆盖旧元表。
2 特性 & 规则
-
setmetatable(目标表, 元表):给目标表绑定元表; -
getmetatable(目标表):获取该表的元表,没有返回 nil; -
元表本身也是普通 table,里面存放各类
__xxx元方法; -
一张表只能拥有一个元表,多次 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 特性 & 规则
查找完整流程:
-
在当前目标表内查找 key,找到直接返回值;
-
当前表找不到 key → 判断是否存在元表;没有元表返回 nil;
-
有元表,判断元表是否存在
__index;没有__index 返回 nil; -
如果
__index是函数:执行该函数,返回函数返回值; -
如果
__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 特性 & 规则
-
给表一个全新 key 赋值;
-
检测有没有元表,存在且有
__newindex; -
不会往原表写入,而是交给__newindex 处理;
-
__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 特性 & 规则
-
__add(a,b):+加法;运算以左边操作数的元表__add 为准; -
__sub减法、__mul乘法、__div除法、__mod取模; -
__concat对应..字符串拼接; -
比较元方法
__eq等于; -
运算:优先取左边对象元方法执行;左边没有才看右边。
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 特性 & 规则
-
print (table) 自动触发元表
__tostring; -
元方法必须返回字符串;
-
不写元方法直接输出 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 特性 & 规则
-
点号
.:不会自动传 self,所有参数必须手动写全; -
冒号
::定义和调用时,第一个参数隐式为self(谁调用 self 就是谁); -
可以交叉混用:冒号定义,点号调用;点号定义,冒号调用,需要手动处理 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 特性 & 规则
-
父类一张 table,存放成员、成员函数;
-
new 函数创建空实例表,给实例设置元表,元表
__index = 父类; -
子类 new,实例元表__index 指向子类;子类找不到成员,继续通过元表链去父类查找;
-
子类写同名函数,优先子类执行,实现方法重写。
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 高频易错清单
-
index 只有 key 不存在才触发,本表已有 key 不会走index;
-
__newindex 只有 ** 全新 key 赋值才触发,已存在 key 直接修改本表,不走 newindex;
-
运算符
+优先取左边对象的元表__add; -
冒号
:自动隐式 self,点号.不会自动传 self; -
Lua 没有 class 关键字,继承全部是 metatable 模拟,不是原生面向对象。
3 核心考点 setmetatable/getmetatable;index 完整查找流程; newindex 触发条件;运算符元方法;**tostring;.和:区别、self 含义;Lua 如何模拟继承(**index 元表链);Lua 不是面向对象语言。
4 进阶拓展学习方向 XLua C# 与 Lua 交互,CS 调用 Lua、Lua 调用 C#;userdata 类型;热更新实战。