第 5 章 · 元表与面向对象:行为外挂 章节摘要:元表是 Lua 把"行为如何定义"外挂出去的机制:表的索引失败、运算、比较、打印、垃圾回收,每种行为都有一个可改写的钩子位。宿主不改语言核心就能定制出默认值表、只读配置、继承链——这正是嵌入世界需要元表的原因。本章三节:5.1 讲元表与查找链的机制,5.2 盘点常用元方法,5.3 用"表加元表"造出类、继承与封装,回答"Lua 怎么做面向对象"。 学习目标 阅读完本章,你应当能够: 说清元表是什么(一张挂在表上的"后备行为表")、setmetatable 与 getmetatable 的用法、元表挂表不挂类型的规则; 画出 找不到时的完整查找链,区分 为表与为函数两种形态,掌握 rawget/rawset 绕过元方法的用法;
章节摘要:元表是 Lua 把"行为如何定义"外挂出去的机制:表的索引失败、运算、比较、打印、垃圾回收,每种行为都有一个可改写的钩子位。宿主不改语言核心就能定制出默认值表、只读配置、继承链——这正是嵌入世界需要元表的原因。本章三节:5.1 讲元表与查找链的机制,5.2 盘点常用元方法,5.3 用"表加元表"造出类、继承与封装,回答"Lua 怎么做面向对象"。
阅读完本章,你应当能够:
t.k 找不到时的完整查找链,区分 __index 为表与为函数两种形态,掌握 rawget/rawset 绕过元方法的用法;语言核心管"必然",元表管"或然":把可以商量的行为全部外包,核心就能保持小巧。
元表挂在哪里、什么时候被咨询、查找链怎么走——机制的三个问题。__index 的表形态给出委托与继承的原型,函数形态给出惰性计算与默认值的入口,raw 系列函数是调试与穿透的钥匙。本节是全章的地基。
按"读写、调用、呈现、比较、算术、生命周期"六组盘点元方法,每组配可运行的最小例子:默认值表与只读表(读写)、函数对象(调用)、自描述打印(呈现)、值语义比较(等值与次序)、向量运算(算术家族)、to-be-closed 与弱表(生命周期)。
把前两节的零件组装成面向对象:类即"共享一张元表的方法表",构造器即工厂函数,继承即 __index 链,多继承即函数形态的 __index 逐父查找,私有成员即闭包。末尾讨论"要不要用面向对象"的工程判断——Lua 社区半个流派只用表与函数,另一半写出完整 class 库,两种都合法。
一句话论点:5.1 给机制、5.2 给零件、5.3 给组装——像先学电路、再认元件、最后装整机。
5.1 机制:元表挂表 · 查找链 · raw 穿透 │ ▼ 5.2 零件:六组元方法 · 每组最小例子 │ ▼ 组装 5.3 整机:类 · 继承 · 封装 · 取舍 │ ├──→ 第 6 章 模块即一张表 · 错误对象带元方法 └──→ 第 9 章 userdata 挂元表 · C 对象获得脚本行为
元表挂在谁身上、什么时候被咨询? 挂在值(主要是表与 userdata)身上;只在"常规操作无答案"时咨询——键扑空问 index、写入落空问 newindex、陌生运算问算术族、转字符串问 tostring。常规路径命中时元表完全不参与,这就是 rawget 与普通访问的差别。
__index 的表形态与函数形态分别适合什么? 表形态是委托(继承链、配置回退、方法表),链式上溯成本低;函数形态是计算(默认值工厂、懒加载回填、多继承分发),灵活但每跳一次调用。同一条链里两种形态可以混用。
为什么面向对象不必内建? 因为类(共享元表的方法表)、继承(index 链)、封装(闭包或约定)都能用已有件组装——语言核心不为范式背书,范式以库的形式存在,团队按需取用或不用。
__index 函数形态、私有成员都要用)。__tostring 让日志可读(第 6 章);userdata 配元表让 C 对象长得像本地值(第 9 章);字符串能直呼 string 库的方法,靠的正是字符串的默认元表(本章揭秘)。三句话带走本章:
**第一句:行为是外挂的。**语言核心只规定"常规路径",扑空、陌生运算、自我介绍这些"非常规时刻"交给元表——宿主不改语言就能定制行为,语言不背范式的锅。
第二句:读委托,写自立。__index 只在扑空时咨询、写在本体;继承、默认值、方法表全是这条规则的变奏。分清"自己的值"与"链上来的值",raw 系是验钞机。
**第三句:范式是选择不是信仰。**表加函数能解决的别上类,类能解决的别上继承链,三层链以上就该重新分层——Lua 把选择权全给你,也把选择的判断题全留给你。