lua中Table 与 Metatable

Table 与 Metatable

在 Lua 中,Table 就是对象。它可以存储数据(属性),也可以存储函数(方法)。

但仅有 Table 是不够的,我们需要 Metatable(元表) 来实现"继承"和"方法查找"。

  • 元表:就像是一个对象的"原型"或"父类"。
  • __index 元方法:这是 Lua OOP 的灵魂。当你访问一个表中不存在的键(比如调用一个方法)时,Lua 会去查找它的元表。如果元表里有这个方法,就执行它。

🛠️ 1. 类的实现(封装)

在 Lua 中,我们通常把一个 Table 当作"类",并通过 new 函数来实例化对象。

关键步骤:
  1. 定义一个表作为类。
  2. 设置 self.__index = self,这样实例找不到方法时能找到类。
  3. 编写构造函数 new,使用 setmetatable 将实例与类关联。
代码示例:
复制代码
-- 1. 定义类
Person = { name = "Unknown", age = 0 }
Person.__index = Person -- 关键:让实例在找不到方法时回退查找 Person

-- 2. 构造函数
function Person:new(name, age)
    local instance = {} -- 创建新对象
    instance.name = name
    instance.age = age
    setmetatable(instance, self) -- 设置元表,让 instance 继承 Person 的方法
    return instance
end

-- 3. 定义方法 (注意使用冒号语法)
function Person:sayHello()
    print("你好,我是 " .. self.name .. ",今年 " .. self.age .. " 岁。")
end

-- 4. 实例化与调用
local p1 = Person:new("Alice", 30)
p1:sayHello() -- 输出: 你好,我是 Alice,今年 30 岁。

🧬 2. 继承的实现

Lua 的继承也是基于元表。子类需要将自己的元表设置为父类,或者设置一个 __index 指向父类。

实现逻辑:
  1. 创建一个新表作为子类。

  2. 设置子类的元表为父类(setmetatable(Child, {__index = Parent}))。

  3. 重写 new 方法,确保实例的元表指向子类自己。

    -- 继承自 Person
    Student = {}
    -- 设置 Student 的元表,使其能访问 Person 的方法(实现继承)
    setmetatable(Student, { __index = Person })
    Student.__index = Student -- 让 Student 的实例能访问 Student 的方法

    -- 子类构造函数
    function Student:new(name, age, school)
    -- 调用父类构造函数创建一个基础对象
    local instance = Person:new(name, age)
    -- 重新设置元表为 Student(这一步很重要,否则找不到 Student 的特有方法)
    setmetatable(instance, Student)
    instance.school = school
    return instance
    end

    -- 子类特有方法
    function Student:study()
    print(self.name .. " 在 " .. self.school .. " 学习。")
    end

    local s1 = Student:new("Bob", 20, "清华大学")
    s1:sayHello() -- 继承自父类的方法
    s1:study() -- 子类特有方法

🎯 3. 语法糖:冒号 (:) 与点号 (.)

你可能注意到了代码中混用了 :.,这在 Lua OOP 中非常重要:

表格

语法 含义 自动传参 适用场景
冒号 : obj:func(arg) 自动 传递 self 实例方法调用、定义
点号 . obj.func(obj, arg) 自动传递 self 静态方法、工具函数、或显式传参
  • 定义时function Person:say() 等价于 function Person.say(self)
  • 调用时p1:say() 等价于 p1.say(p1)

🔒 4. 私有性与封装

Lua 的 Table 默认是公开的,任何人都可以访问内部数据。要实现"私有变量"(如 private balance),通常利用 闭包(Closure)局部变量 来实现。

方法:利用闭包隐藏数据

我们不把数据放在 self 里,而是放在函数的局部变量中,只暴露操作接口。

复制代码
function createAccount(initialBalance)
    -- 私有变量(在闭包作用域内,外部无法直接访问)
    local balance = initialBalance or 0

    -- 返回公共接口
    return {
        deposit = function(amount)
            balance = balance + amount
            print("存款成功,余额: " .. balance)
        end,
        withdraw = function(amount)
            if amount <= balance then
                balance = balance - amount
                print("取款成功,余额: " .. balance)
            else
                print("余额不足")
            end
        end
    }
end

local acc = createAccount(1000)
acc.deposit(500)
-- print(acc.balance) -- 报错或返回 nil,因为 balance 是私有的

__index 作为函数:自定义"读"逻辑

__index 是函数时,每次访问表中不存在的键,都会调用这个函数。

函数签名
复制代码
function __index_func(table, key)
    -- table: 你正在访问的那个表(比如 t)
    -- key: 你试图访问的键(比如 t.name 中的 "name")
    return value -- 返回的值就是 t.name 的结果
end
执行流程
  1. 代码请求 t.key
  2. 发现 t 里没有 key
  3. 发现元表里有 __index 函数。
  4. 调用函数__index_func(t, "key")
  5. 函数返回什么,t.key 就等于什么。
实战:实现"只读"或"默认值"
复制代码
local t = { name = "Alice" }

local mt = {
    __index = function(table, key)
        print("警告:你在读取一个不存在的字段 ->", key)
        return "默认值"
    end
}

setmetatable(t, mt)

print(t.name)    -- Alice (直接存在,不触发 __index)
print(t.money)   
-- 1. t 没有 money
-- 2. 打印 "警告..."
-- 3. 输出 "默认值"

__newindex 作为函数:自定义"写"逻辑

__newindex 是函数时,每次给表中不存在的键赋值,都会调用这个函数。注意:此时赋值操作被拦截了,原表不会自动发生变化。

函数签名
复制代码
function __newindex_func(table, key, value)
    -- table: 你正在赋值的那个表
    -- key: 键
    -- value: 你想赋的值
    -- 在这里你需要手动决定怎么处理这个 value
end
执行流程
  1. 代码执行 t.key = 100
  2. 发现 t 里没有 key
  3. 发现元表里有 __newindex 函数。
  4. 调用函数__newindex_func(t, "key", 100)
  5. 关键点 :如果函数内部什么都不写,t.key 永远不会被赋值 。你需要手动用 rawset 来赋值。
实战:数据校验与私有变量
复制代码
local t = {}
local _private_store = {} -- 用一个隐藏表存真实数据

local mt = {
    __newindex = function(table, key, value)
        if key == "age" then
            if type(value) == "number" and value > 0 then
                -- 校验通过,手动存入隐藏表
                _private_store[key] = value
                print("年龄设置成功")
            else
                error("年龄必须是正数!")
            end
        else
            -- 其他字段直接存入隐藏表
            _private_store[key] = value
        end
    end,
    
    __index = _private_store -- 读取时也去隐藏表读
}

setmetatable(t, mt)

t.age = 20      -- 输出: 年龄设置成功
-- t.age = -5    -- 报错: 年龄必须是正数!

print(t.age)    -- 20 (通过 __index 从 _private_store 读到)

📌 总结

  • __index 函数 :像一个兜底客服。你问它要东西,它没有,但它可以根据你的问题现场算一个给你,或者给你个默认回复。
  • __newindex 函数 :像一个安检员 。你想把东西带进去(赋值),它拦下来检查。如果你不手动把东西放进去(rawset 或存入其他表),东西就会被丢弃。
相关推荐
xingpanvip5 小时前
星盘接口开发文档:组合三限盘接口指南
android·开发语言·前端·python·php·lua
chxii1 天前
lua流程控制语句和table(表)数据结构
开发语言·junit·lua
chxii1 天前
lua 基础语法(上)
开发语言·lua
xingpanvip2 天前
星盘接口开发文档:日运语料接口指南
android·开发语言·前端·css·php·lua
xingpanvip3 天前
星盘接口开发文档:星相日历接口指南
android·开发语言·前端·css·php·lua
咸鱼永不翻身4 天前
Lua脚本事件检查工具
unity·lua·工具
笑虾4 天前
cocos2d-x lua 加载 Cocos Studio 导出的 csb
游戏引擎·lua·cocos2d
xingpanvip4 天前
星盘接口开发文档:日返比接口指南
开发语言·lua
xingpanvip5 天前
星盘接口开发文档:天象盘接口指南
android·开发语言·python·php·lua