Lua 教程 - 第五章:表的高级应用
第五章:表的高级应用
表(Table)不仅仅是数据容器,它还是 Lua 模块系统、面向对象编程和元编程的基石。在前面的章节中,我们学习了表的基础操作,本章将深入探讨表的更多高级特性。
我们将学习如何操作表、使用元表(Metatable)改变表的行为,以及如何利用表来实现面向对象编程(OOP)。
5.1 表操作
Lua 的 table 库提供了许多实用的函数来操作数组类型的表。
插入与删除
-
table.insert(list, [pos,] value): 在指定位置插入元素。默认插入末尾。 -
table.remove(list, [pos]): 删除指定位置的元素,并返回该元素。默认删除最后一个。
local t = {10, 20, 30}
table.insert(t, 40) -- {10, 20, 30, 40}
table.insert(t, 2, 15) -- {10, 15, 20, 30, 40}
print(table.remove(t, 1)) -- 10, t 变为 {15, 20, 30, 40}
表连接
table.concat(list, [sep, [i, [j]]]): 将数组中的元素连接成字符串。
-
sep: 分隔符(默认为空字符串)。 -
i,j: 起始和结束索引。
local t = {"Lua", "is", "great"}
print(table.concat(t, " ")) -- "Lua is great"
表排序
table.sort(list, [comp]): 对数组进行排序。comp 是可选的比较函数。
local t = {3, 1, 4, 1, 5}
table.sort(t) -- {1, 1, 3, 4, 5}
-- 自定义降序排序
table.sort(t, function(a, b) return a > b end) -- {5, 4, 3, 1, 1}
5.2 元表 (Metatable)
元表是 Lua 中最强大的特性之一。它允许我们修改表的行为,例如定义两个表相加的操作、定义如何访问不存在的键等。
设置和获取元表
-
setmetatable(table, metatable): 设置表的元表。 -
getmetatable(table): 获取表的元表。
元方法 (Metamethods)
元表中包含的特殊键称为元方法。常见的元方法有:
-
__add:+操作 -
__sub:-操作 -
__mul:*操作 -
__div:/操作 -
__tostring: 用于print或tostring转换时的输出 -
__index: 当访问不存在的索引时调用 -
__newindex: 当给不存在的索引赋值时调用 -
__call: 当把表当作函数调用时触发
示例:重载加法运算符
local Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in ipairs(l) do set[v] = true end
return set
end
function Set.union(a, b)
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
function Set.tostring(set)
local l = {}
for e in pairs(set) do l[#l + 1] = tostring(e) end
return "{" .. table.concat(l, ", ") .. "}"
end
mt.__add = Set.union
mt.__tostring = Set.tostring
local s1 = Set.new{10, 20, 30}
local s2 = Set.new{30, 1}
print(s1) -- 调用 __tostring
print(s1 + s2) -- 调用 __add, 输出 {1, 10, 20, 30} (顺序不一定)
__index 元方法
这是实现面向对象编程的关键。当访问表 t 中不存在的字段 k 时,如果 t 有元表且元表有 __index 字段:
-
如果
__index是一个表,则在这个表中查找k。 -
如果
__index是一个函数,则调用该函数__index(t, k)。
local proto = {x = 0, y = 0}
local mt = {__index = proto}
local o = {x = 10}
setmetatable(o, mt)
print(o.x) -- 10 (存在)
print(o.y) -- 0 (不存在,去 proto 中找)
5.3 面向对象编程 (OOP)
Lua 没有内置的类(Class)概念,但我们可以使用表和元表来模拟。
类的实现
通常,我们创建一个表作为“类”,并设置 __index 指向它自己。
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account:withdraw(v)
if v > self.balance then error"insufficient funds" end
self.balance = self.balance - v
end
-- 创建对象
a = Account:new{balance = 0}
a:deposit(100.00)
print(a.balance) -- 100.0
冒号语法
a:deposit(100) 是 a.deposit(a, 100) 的语法糖。定义函数时使用 function Account:deposit(v) 会自动添加一个隐藏参数 self。
继承
我们可以基于现有类创建新类。
SpecialAccount = Account:new()
function SpecialAccount:withdraw(v)
if v - self.balance >= self.limit then
error"insufficient funds"
end
self.balance = self.balance - v
end
s = SpecialAccount:new{limit = 1000.00}
s:deposit(100.00)
s:withdraw(200.00)
print(s.balance) -- -100.0
5.4 模块与包
Lua 5.1 引入了模块系统。
编写模块
通常一个文件就是一个模块,模块最后返回一个包含导出函数的表。
-- mymodule.lua
local M = {}
function M.say_hello()
print("Hello from module!")
end
return M
使用模块
使用 require 函数加载模块。
local m = require("mymodule")
m.say_hello()
Lua 会在 package.path 指定的路径中搜索模块。
练习题
-
编写一个函数
Set.intersection(a, b)计算两个集合的交集,并将其绑定到元方法__mul(*) 上。 -
实现一个
Stack(栈)类,包含push,pop,peek方法。 -
尝试实现一个只读表:利用
__index和__newindex元方法,使得访问存在的键正常,修改任何键都报错。 -
创建一个模块
complex,用于处理复数运算,并支持+,-运算符重载。
下一章预告:我们将深入研究 Lua 强大的字符串模式匹配功能,它类似于正则表达式但更加轻量高效。

