🧱 一、表(table)—— Lua 的唯一复合数据结构
✅ 1.1 本质
- Lua 中唯一的内置复合数据结构
- 可以同时作为:
- 数组(array)
- 字典(map / hash / associative array)
- 对象(object)
- 模块(module)
- 结构体(struct)
- 命名空间(namespace)
✅ 1.2 基本语法
local t = {
name = "Alice", -- 字符串键
age = 25, -- 字符串键,等价于 ["age"] = 25
[true] = "yes", -- 任意类型键(除 nil)
[2] = "first", -- 数字键,插入到第一个位置(索引从1开始),此位置向上至2位置的数据,移动到最末尾
sayHello = function(self) -- 方法
print("Hello, I'm " .. self.name)
end
}
✅ 1.3 操作方式
t.name = "Bob" -- 设置字段
print(t.age) -- 读取字段
t:sayHello() -- 调用方法(: 自动传 self)
t["sayHello"](t) -- 等价写法
for k, v in pairs(t) do -- 遍历
print(k, v)
end
✅ 1.4 表的核心作用
存储数据 + 行为(方法) → 成为“对象”的基础
🧩 二、元表(metatable)—— 表的“行为说明书”
✅ 2.1 本质
- 元表本身也是一个表
- 它包含“元方法(metamethods)”,用于定义“当对原表执行某些操作时,应该怎么做”
✅ 2.2 如何设置/获取
setmetatable(table, metatable) -- 设置
getmetatable(table) -- 获取
⚠️ 只有 table、userdata、thread 可以设元表(Lua 5.4+ 支持更多类型,但一般只用于 table)
✅ 2.3 常用元方法
| 元方法 | 触发时机 | 用途 |
|---|---|---|
__index | t[key] 且 key 不存在 | 默认值、继承、代理 |
__newindex | t[key] = value 且 key 不存在 | 权限控制、日志、只读表 |
__add | a + b | 自定义加法 |
__call | f(...) | 让表像函数一样被调用 |
__tostring | print(t) 或 tostring(t) | 自定义字符串表示 |
__len | #t | 自定义长度 |
__eq, __lt, __le | ==, <, <= | 自定义比较 |
✅ 2.4 示例:__index 实现默认值
local defaults = { x = 0, y = 0, color = "white" }
local obj = {}
setmetatable(obj, { __index = defaults })
print(obj.x) -- 0 (从 defaults 获取)
obj.x = 10
print(obj.x) -- 10 (现在 obj 自己有 x)
✅ 2.5 示例:__call 实现可调用对象
local counter = { count = 0 }
local mt = {
__call = function(self, n)
self.count = self.count + (n or 1)
return self.count
end
}
setmetatable(counter, mt)
print(counter()) -- 1
print(counter(5)) -- 6
✅ 2.6 元表的核心作用
控制表的行为 —— 运算符重载、属性代理、函数调用、继承机制等
🧍 三、对象(object)—— 用表模拟的对象
✅ 3.1 什么是对象?
在 Lua 中,对象 = 表 + 方法 +(可选)元表
local obj = {
x = 10,
y = 20,
move = function(self, dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
}
obj:move(5, 5) -- : 语法糖,自动传 self
print(obj.x, obj.y) -- 15 25
✅ 3.2 对象的核心特征
- 状态(数据):
x,y - 行为(方法):
move - 标识(identity):每个表是独立对象
✅ 3.3 如何“构造”对象?
方式 1:直接字面量
local obj = { x = 10, y = 20 }
方式 2:工厂函数
local function newPoint(x, y)
return {
x = x or 0,
y = y or 0,
move = function(self, dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
}
end
local p = newPoint(10, 20)
方式 3:用元表实现“类式”构造(见下文)
🏛 四、类(class)—— 用表 + 元表模拟的“类”
Lua 没有内置类,但可以用“原型继承”模拟:
✅ 4.1 基本“类”结构
-- “类”定义(其实是原型对象)
local Point = {}
-- 定义 __tostring 行为(注意:这是给“实例”用的,不是给类本身)
local mt = {
__tostring = function(self)
return string.format("Point(%d, %d)", self.x, self.y)
end
}
-- 构造函数
function Point:new(x, y)
local obj = {
x = x or 0,
y = y or 0
}
-- 关键:让实例(obj表)的元表要包含 __tostring 和 __index
setmetatable(obj, {
__index = self, -- 初始化 __index 为 Point (继承Point的成员)。
__tostring = mt.__tostring -- 直接设置元方法
})
return obj
end
-- 方法
function Point:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
-- 可选:让类本身(Point表)也支持 tostring(调试用)
setmetatable(Point, {
__tostring = function()
return "Class Point"
end,
__call = Point.new -- 支持 Point(10, 20) 语法
})
✅ 4.2 使用“类”
-- 测试
local p1 = Point:new(10, 20)
print(p1) -- Point(10, 20)
p1:move(5, 5)
print(p1) -- Point(15, 25)
local p2 = Point(30, 40) -- 使用 __call
print(p2) -- Point(30, 40)
print(Point) -- Class Point (可选功能)
✅ 4.3 继承怎么实现?
-- 子类
local ColorPoint = Point:new() -- 创建子类原型
ColorPoint.color = "red"
-- 设置 ColorPoint ,使用 __index 继承 Point
setmetatable(ColorPoint, { __index = Point })
-- 定义 __instance_mt
ColorPoint.__instance_mt = {
__index = function(self, key)
-- 首先在 ColorPoint 中查找,把所有 父类 Point 的函数全部继承过来。
local value = ColorPoint[key]
if value ~= nil then
return value
end
-- 如果没找到,再到 Point 中查找
return Point[key]
end,
__tostring = function(self)
return string.format("ColorPoint(%d, %d, %s)", self.x, self.y, self.color)
end
}
-- 子类构造函数
function ColorPoint:new(x, y, color)
local obj = Point:new(x, y) -- 调用父类构造函数
obj.color = color or self.color
-- 关键:重新设置元表为 ColorPoint 的实例元表
setmetatable(obj, self.__instance_mt)
return obj
end
-- 可选:给 ColorPoint 类本身设置元表
setmetatable(ColorPoint, {
__tostring = function() return "Class ColorPoint" end,
__call = function(self, ...)
return self:new(...)
end -- 修正:使用匿名函数调用 self:new(...)
})
-- 子类测试
local cp = ColorPoint(50, 60, "blue")
print(cp) -- ColorPoint(50, 60, blue) ← 终于正确了!
cp:move(10, 10)
print(cp) -- ColorPoint(60, 70, blue) ← move 继承自 Point!
print(ColorPoint) -- Class ColorPoint
✅ 4.4 “类”的本质
一个“原型表” + 一个元表(用
__index指向原型表)
- 对象创建时,把自己的元表的
__index指向“类” - 当访问对象不存在的字段时,Lua 会去“类”里找 → 实现“方法继承”
📜 五、使用规则 & 最佳实践
✅ 5.1 表的使用规则
- 用作数据容器、对象、模块
- 避免混用数组和字典部分(除非必要)
- 键尽量用字符串或数字(避免用布尔、函数、表作键,除非有特殊需求)
- 使用
:定义和调用方法,自动管理self
✅ 5.2 元表的使用规则
- 只用于定义行为,不用于存储业务数据
- 多个对象可共享同一个元表(节省内存)
__index最常用 —— 实现继承、默认值、代理__newindex用于拦截写操作 —— 实现只读表、日志、验证__call让对象/类可调用 —— 实现工厂、闭包__tostring用于调试和日志 —— 强烈推荐为对象实现
✅ 5.3 对象的使用规则
- 状态(数据)存在对象自身
- 方法存在“类”或原型中(通过
__index继承) - 使用
obj:method()而不是obj.method(obj) - 避免在对象中存储函数(除非是闭包或动态生成),以节省内存
✅ 5.4 类的使用规则
- “类”本身也是一个对象(原型)
- 构造函数通常叫
new或create - 使用
setmetatable(obj, { __index = self })实现继承 - 方法定义在“类”上,对象通过
__index继承 - 支持多层继承(
__index链) - 可选:实现
__call让类像函数一样被调用
✅ 5.5 性能注意事项
- 频繁触发
__index/__newindex有性能开销 → 避免在热路径使用 - 共享元表 → 节省内存
- 方法放在“类”中 → 避免每个对象复制函数
✅ 5.6 调试建议
- 使用
getmetatable(obj)查看对象的元表 - 使用
debug.getmetatable(如果元表被保护) - 为对象实现
__tostring,方便 print 调试 - 避免过度使用元表 —— 会让代码行为“不直观”
🧭 六、关系图谱总结
+----------------------+
| 元表 |
| (metatable) |
| - __index → 类 | ← 定义“继承”行为
| - __call | ← 定义“可调用”行为
| - __tostring | ← 定义“打印”行为
+----------↑----------+
|
| setmetatable(obj, mt)
|
+----------↓----------+
| 表 |
| (table) |
| - x = 10 | ← 数据(状态)
| - y = 20 |
| - move = function | ← 方法(行为)→ 通常放“类”里,通过 __index 继承
+----------↑----------+
|
| 是
|
+----------↓----------+
| 对象 | ← 对象 = 表 + 元表
+----------↑----------+
|
| 从...创建
|
+----------↓----------+
| 类 | ← 类 = 原型表 + 元表(通常 __index 指向自己)
| (其实是原型对象) |
+----------------------+
✅ 七、一句话总结
在 Lua 中:
- 表 是数据和方法的容器;
- 元表 是表的行为控制器;
- 对象 是一个表(带状态) + 可选元表(带行为);
- 类 是一个特殊的表(原型) + 元表(用
__index实现继承),用于创建和管理对象。


发表回复