Lua 进阶教程


第1节:表 (Table) 进阶

表的内存模型

Lua 中的表是引用类型,赋值只是复制引用:

local a = {1, 2, 3}
local b = a -- b 和 a 指向同一个表
b[1] = 100
print(a[1]) -- 100(a 也被修改)
 
-- 深拷贝需要自己实现

表的遍历陷阱

  • ipairs 从索引1开始,遇到第一个 nil 就停止
  • pairs 遍历所有键值对,顺序不确定
  • 不要在遍历过程中添加新键
local t = {10, 20, nil, 40}
for i, v in ipairs(t) do
 print(i, v) -- 只输出 1=10, 2=20(遇到 nil 停止)
end
 
for k, v in pairs(t) do
 print(k, v) -- 输出 1=10, 2=20, 4=40(跳过 nil)
end

表作为集合

local visited = {}
visited["node_A"] = true
visited["node_B"] = true
 
if visited["node_A"] then
 print("已访问 node_A")
end
 
-- 遍历集合
for k in pairs(visited) do
 print(k)
end

链表实现

list = nil -- 空链表
list = {next = list, value = "first"}
list = {next = list, value = "second"}
list = {next = list, value = "third"}
 
-- 遍历
local l = list
while l do
 print(l.value)
 l = l.next
end
-- 输出: third, second, first

第2节:元表 (Metatable) 与元方法

什么是元表?

元表允许我们重定义表的行为,是实现 Lua 面向对象、运算符重载等高级特性的基础。

local t = {}
print(getmetatable(t)) -- nil(默认没有元表)
 
local mt = {}
setmetatable(t, mt)
print(getmetatable(t)) -- table: 0x...

算数元方法

local Vector2 = {}
Vector2.__index = Vector2
 
function Vector2:new(x, y)
 return setmetatable({x = x or 0, y = y or 0}, self)
end
 
function Vector2:__add(other)
 return Vector2:new(self.x + other.x, self.y + other.y)
end
 
function Vector2:__sub(other)
 return Vector2:new(self.x - other.x, self.y - other.y)
end
 
function Vector2:__mul(scalar)
 return Vector2:new(self.x * scalar, self.y * scalar)
end
 
function Vector2:__tostring()
 return string.format("Vector2(%.2f, %.2f)", self.x, self.y)
end
 
local v1 = Vector2:new(3, 4)
local v2 = Vector2:new(1, 2)
local v3 = v1 + v2 -- 调用 __add
local v4 = v3 * 2 -- 调用 __mul
print(v3) -- Vector2(4.00, 6.00)
print(v4) -- Vector2(8.00, 12.00)

所有可用的元方法

元方法触发操作说明
__add(a, b)a + b加法
__sub(a, b)a - b减法
__mul(a, b)a * b乘法
__div(a, b)a / b除法
__mod(a, b)a % b取模
__pow(a, b)a ^ b幂运算
__unm(a)-a负号
__idiv(a, b)a // b整除
__concat(a, b)a .. b拼接
__len(a)#a长度
__eq(a, b)a == b等于
__lt(a, b)a < b小于
__le(a, b)a <= b小于等于
__index(t, k)t[k]读取不存在键
__newindex(t, k, v)t[k] = v写入不存在键
__call(t, ...)t(...)函数调用
__tostring(a)tostring(a)转字符串
__pairs(t)for k,v in pairs(t)自定义 pairs 行为

__index:读取不存在的键

-- __index 可以是函数
local mt = {
 __index = function(t, k)
 return "默认值: " .. k
 end
}
local t = setmetatable({}, mt)
print(t.foo) -- 默认值: foo
 
-- __index 可以是表(实现继承的关键)
local defaults = {color = "red", size = 10}
local obj = setmetatable({}, {__index = defaults})
print(obj.color) -- red(来自 defaults)
obj.color = "blue"
print(obj.color) -- blue(自己的字段)

__newindex:写入不存在的键

-- 控制属性的只读性
local mt = {
 __newindex = function(t, k, v)
 error("不允许添加新字段: " .. k, 2)
 end
}
local t = setmetatable({name = "test"}, mt)
t.name = "new" -- OK(已存在)
-- t.age = 10 -- ERROR

__call:让表可以像函数一样调用

local mt = {
 __call = function(t, ...)
 local sum = 0
 for _, v in ipairs({...}) do
 sum = sum + v
 end
 return sum
 end
}
local sum = setmetatable({}, mt)
print(sum(1, 2, 3, 4, 5)) -- 15

第3节:面向对象编程 (OOP)

Lua 没有内置的 class 关键字,但通过元表可以优雅地实现 OOP。

最简单的 OOP

-- 定义类
local Animal = {}
Animal.__index = Animal
 
function Animal:new(name)
 local obj = {name = name}
 setmetatable(obj, self)
 return obj
end
 
function Animal:speak()
 print(self.name .. " makes a sound.")
end
 
-- 使用
local a = Animal:new("猫")
a:speak() -- 猫 makes a sound.

继承

local Dog = {}
Dog.__index = Dog
setmetatable(Dog, {__index = Animal}) -- Dog 继承 Animal
 
function Dog:new(name, breed)
 local obj = Animal.new(self, name) -- 调用父类构造
 obj.breed = breed
 setmetatable(obj, self)
 return obj
end
 
function Dog:speak()
 print(self.name .. " says: Woof!")
end
 
function Dog:fetch()
 print(self.name .. " fetches the ball!")
end
 
local d = Dog:new("旺财", "金毛")
d:speak() -- 旺财 says: Woof!
d:fetch() -- 旺财 fetches the ball!

访问控制(私有成员)

local BankAccount = {}
BankAccount.__index = BankAccount
 
function BankAccount:new(owner, initial_balance)
 local balance = initial_balance or 0 -- 局部变量 = 私有成员
 local obj = {
 owner = owner, -- 公开成员
 }
 setmetatable(obj, self)
 
 -- 定义方法也会捕获局部变量(闭包)
 function obj:getBalance()
 return balance
 end
 function obj:deposit(amount)
 balance = balance + amount
 end
 function obj:withdraw(amount)
 if amount <= balance then
 balance = balance - amount
 return true
 end
 return false
 end
 return obj
end
 
local acc = BankAccount:new("Alice", 1000)
acc:deposit(500)
print(acc:getBalance()) -- 1500
print(acc.balance) -- nil(无法直接访问)

第4节:模块与包

创建模块

-- math_helper.lua
local math_helper = {}
 
function math_helper.add(a, b)
 return a + b
end
 
function math_helper.subtract(a, b)
 return a - b
end
 
-- 私有函数(不导出)
local function internal_calc(x)
 return x * 2
end
 
function math_helper.double(x)
 return internal_calc(x)
end
 
return math_helper

使用模块

local mh = require("math_helper")
print(mh.add(10, 5)) -- 15
print(mh.double(3)) -- 6
-- print(mh.internal_calc(3)) -- 错误:私有函数

require 查找路径

-- 查看当前模块搜索路径
print(package.path)
 
-- 临时添加搜索路径
package.path = package.path .. ";./mylibs/?.lua"
 
-- 或通过环境变量 LUA_PATH
-- export LUA_PATH="$HOME/.luarocks/share/lua/5.4/?.lua;;"

模块化组织示例

project/
├── main.lua
├── lib/
│ ├── utils.lua -- return { trim = ..., split = ... }
│ └── math/
│ └── vector.lua -- return Vector class
└── config.lua -- return { width = 800, height = 600 }
-- main.lua
local utils = require("lib.utils")
local Vector = require("lib.math.vector")
local config = require("config")

第5节:文件 I/O

简单模式

-- 写文件
local file = io.open("test.txt", "w") -- "w" 覆盖, "a" 追加
file:write("Hello, World!\n")
file:write("第二行\n")
file:close()
 
-- 读文件(一次性读全部)
local file = io.open("test.txt", "r")
local content = file:read("*a") -- *a = 读取全部
file:close()
print(content)

读取模式

模式说明
"*a"读取整个文件
"*l"读取下一行(不含换行符)
"*n"读取一个数字
n读取 n 个字符
-- 逐行读取
local file = io.open("test.txt", "r")
for line in file:lines() do
 print(line)
end
file:close()

错误处理

local file, err = io.open("nonexistent.txt", "r")
if not file then
 print("打开文件失败: " .. err)
 return
end
-- ... 使用 file ...
file:close()

io 模块 vs 文件对象

-- io.xxx:操作默认输入/输出文件
io.write("Hello ") -- 写入标准输出
io.write("World\n")
 
-- 文件对象:操作具体文件
local f = io.open("data.txt", "w")
f:write("data")
f:close()

常用文件操作

-- 文件系统操作
os.rename("old.txt", "new.txt") -- 重命名
os.remove("temp.txt") -- 删除
 
-- 获取文件信息
local info = io.open("test.txt", "r")
if info then
 print(info:seek("end")) -- 文件大小
 info:close()
end

第6节:错误处理

error() 和 assert()

-- 主动抛出错误
function divide(a, b)
 if b == 0 then
 error("除数不能为零", 2) -- 第二个参数指定错误报告层级
 end
 return a / b
end
 
-- assert:如果条件为假则报错
function safeSqrt(x)
 assert(x >= 0, "负数不能开平方根, 传入: " .. tostring(x))
 return math.sqrt(x)
end

pcall:受保护调用

-- pcall 在保护模式下调用函数,返回状态和结果
local ok, result = pcall(function()
 return 10 / 0 -- 不会导致错误
end)
 
if ok then
 print("结果: " .. result)
else
 print("错误: " .. result) -- result 是错误消息
end
 
-- pcall 传参
local ok, result = pcall(divide, 10, 0)
print(ok, result) -- false 除数为零

xpcall:带错误处理器的保护调用

local function traceback(err)
 print("=== 错误追踪 ===")
 print(debug.traceback("错误: " .. tostring(err), 2))
 print("=================")
end
 
local function riskyOperation()
 error("出问题了!")
end
 
xpcall(riskyOperation, traceback)

自定义错误系统

-- result.lua — 类似 Rust 的 Result 类型
local Result = {}
 
function Result.ok(value)
 return {is_ok = true, value = value}
end
 
function Result.err(message)
 return {is_ok = false, error = message}
end
 
-- 使用
local function safeDivide(a, b)
 if b == 0 then
 return Result.err("division by zero")
 end
 return Result.ok(a / b)
end
 
local r = safeDivide(10, 2)
if r.is_ok then
 print("Result: " .. r.value)
else
 print("Error: " .. r.error)
end

第7节:环境与全局变量

_ENV(Lua 5.2+)

-- _ENV 是一个特殊的局部变量,控制全局变量的可见范围
-- 每个 Lua 块编译时都被包装成:
-- function(...) local _ENV = _ENV ... end
 
print(_ENV) -- 全局环境的引用
print(_G) -- 5.1 兼容方式
 
-- 创建隔离环境
local sandbox = {}
sandbox.print = print -- 允许 print
sandbox.math = math -- 允许 math 模块
 
local code = [[
 print(math.sqrt(16))
 -- os.execute('rm -rf /') -- 错误: os 不存在
]]
local func = load(code, nil, "t", sandbox)
func() -- 输出: 4.0

沙盒执行

local sandbox = {
 print = print,
 math = math,
 string = string,
 table = table,
 pairs = pairs,
 ipairs = ipairs,
}
 
local function sandboxRun(code)
 local result = {}
 sandbox.print = function(...)
 for i, v in ipairs({...}) do
 table.insert(result, tostring(v))
 end
 table.insert(result, "\n")
 end
 
 local f, err = load(code, "sandbox", "t", sandbox)
 if not f then
 return nil, err
 end
 local ok, msg = pcall(f)
 if not ok then
 return nil, msg
 end
 return table.concat(result)
end
 
local output, err = sandboxRun("print('Hello') print(1+2)")
print(output) -- Hello 3

第8节:协程 (Coroutine)

Lua 协程是协作式多线程,在同一时刻只有一个协程在运行,由程序主动让出控制权。

创建与使用

-- 创建协程
local co = coroutine.create(function()
 print("协程开始")
 coroutine.yield("第一次暂停")
 print("协程继续")
 coroutine.yield("第二次暂停")
 print("协程结束")
 return "完成"
end)
 
-- 查看状态
print(coroutine.status(co)) -- suspended
 
-- 启动/恢复协程
local ok, value = coroutine.resume(co)
print(value) -- 第一次暂停
 
print(coroutine.status(co)) -- suspended
 
local ok, value = coroutine.resume(co)
print(value) -- 第二次暂停
 
local ok, value = coroutine.resume(co)
print(value) -- 完成
 
print(coroutine.status(co)) -- dead

生产者-消费者模式

local function producer()
 for i = 1, 5 do
 coroutine.yield(i)
 end
end
 
local function consumer()
 local co = coroutine.create(producer)
 while coroutine.status(co) ~= "dead" do
 local _, value = coroutine.resume(co)
 if value then
 print("消费: " .. value)
 end
 end
end
 
consumer()
-- 输出: 消费: 1 ... 消费: 5

coroutine.wrap(更简洁的包装)

local function count(n)
 for i = 1, n do
 coroutine.yield(i)
 end
end
 
local counter = coroutine.wrap(function() count(3) end)
print(counter()) -- 1
print(counter()) -- 2
print(counter()) -- 3
print(counter()) -- nil(结束)

主从间传值

local co = coroutine.create(function(a, b)
 print("收到: " .. a .. ", " .. b)
 local c, d = coroutine.yield(a + b)
 print("再次收到: " .. c .. ", " .. d)
 return c * d
end)
 
coroutine.resume(co, 10, 20) -- 输出: 收到: 10, 20;返回 30
coroutine.resume(co, 3, 4) -- 输出: 再次收到: 3, 4;返回 12

实际应用:异步任务调度器

-- 简易异步调度器
local Scheduler = {}
Scheduler.__index = Scheduler
 
function Scheduler:new()
 return setmetatable({queue = {}}, self)
end
 
function Scheduler:spawn(func)
 table.insert(self.queue, coroutine.create(func))
end
 
function Scheduler:tick()
 for i = #self.queue, 1, -1 do
 local co = self.queue[i]
 if coroutine.status(co) == "dead" then
 table.remove(self.queue, i)
 else
 coroutine.resume(co)
 end
 end
end
 
function Scheduler:wait(seconds)
 local start = os.clock()
 while os.clock() - start < seconds do
 coroutine.yield()
 end
end
 
-- 使用
local sched = Scheduler:new()
 
sched:spawn(function()
 for i = 1, 3 do
 print("任务1: " .. i)
 sched:wait(0.5)
 end
end)
 
sched:spawn(function()
 for i = 1, 3 do
 print("任务2: " .. i)
 sched:wait(0.3)
 end
end)
 
for _ = 1, 10 do
 sched:tick()
end

第9节:垃圾回收

基本原理

Lua 使用增量标记-清除(Incremental Mark & Sweep)GC 算法。

-- 手动触发 GC
collectgarbage("collect") -- 执行完整垃圾回收周期
collectgarbage("stop") -- 暂停自动 GC
collectgarbage("restart") -- 重启自动 GC
 
-- 查看内存使用
print(collectgarbage("count")) -- 当前使用的内存(KB)
 
-- 设置 GC 参数(Lua 5.3+: pause, step multiplier)
collectgarbage("setpause", 200) -- 标记暂停比例(越大GC越不频繁)
collectgarbage("setstepmul", 200) -- 步进倍率(越大GC越快)

__gc:终结器

local mt = {
 __gc = function(t)
 print("对象被回收: " .. t.name)
 end
}
 
-- 注意: 创建对象时就必须设置好元表(不能事后设置)
local obj = setmetatable({name = "Resource"}, mt)
obj = nil -- 引用置空
collectgarbage() -- 强制回收 → 输出: 对象被回收: Resource

弱引用表

-- 弱引用表:键或值不会被 GC 计算在内
-- 模式: "k" (弱键), "v" (弱值), "kv" (弱键值)
 
local cache = setmetatable({}, {__mode = "v"})
 
do
 local huge_data = string.rep("x", 1000000)
 cache[1] = huge_data
end
-- huge_data 出了作用域,可以被 GC 回收
-- 如果 cache 不是弱值,huge_data 会一直占用内存
 
collectgarbage()
print(cache[1]) -- nil(已被回收)

第10节:调试与反射

debug 库概览

函数说明
debug.traceback([msg])获取调用栈字符串
debug.getinfo(func[, what])获取函数信息
debug.getlocal(level, n)获取局部变量
debug.setlocal(level, n, value)设置局部变量
debug.getupvalue(func, n)获取上值(闭包捕获的变量)
debug.setupvalue(func, n, value)设置上值
debug.sethook(hook, mask[, count])设置钩子
-- 查看调用栈
function a() b() end
function b() c() end
function c()
 print(debug.traceback("追踪: "))
end
 
a()
-- stack traceback:
-- stdin:3: in function 'c'
-- stdin:2: in function 'b'
-- stdin:1: in function 'a'
-- (... 调用者 ...)
-- 追踪:

函数信息获取

local function testFunc(a, b)
 local x = a + b
 return x * 2
end
 
local info = debug.getinfo(testFunc, "Slnu")
-- S: 源码行信息, l: 当前行, n: 名称, u: 上值数量
print(info.name) -- 函数名
print(info.linedefined) -- 定义在哪一行
print(info.what) -- "Lua" 或 "C"

钩子(Hook)

-- 每执行一行代码就调用钩子函数
debug.sethook(function(event, line)
 if event == "line" then
 print("执行行: " .. line)
 end
end, "l")
 
-- 执行统计
local counts = {}
debug.sethook(function(event)
 local info = debug.getinfo(2, "nS")
 local func = info.name or "anonymous"
 counts[func] = (counts[func] or 0) + 1
end, "call")
 
-- 停止钩子
debug.sethook()

第11节:模式匹配 (Pattern)

Lua 的模式匹配类似简化版的正则表达式。

特殊字符

字符说明
.匹配任意字符
%a字母
%d数字
%l小写字母
%u大写字母
%w字母+数字
%s空白字符
%p标点符号
%A非字母
[abc]a,b,c 任一
[^abc]非 a,b,c
*0或多(贪婪)
+1或多(贪婪)
-0或多(懒惰)
?0或1
local text = "Hello, my email is alice@example.com"
 
-- 提取邮箱
local email = string.match(text, "%w+@[%w%.]+")
print(email) -- alice@example.com
 
-- 匹配日期
local date = "Date: 2024-01-15"
local y, m, d = string.match(date, "(%d+)%-(%d+)%-(%d+)")
print(y, m, d) -- 2024 01 15
 
-- URL 提取
local html = '<a href="https://example.com">link</a>'
local url = string.match(html, 'href="(.-)"')
print(url) -- https://example.com
 
-- 字符串分割
local function split(str, sep)
 local result = {}
 for part in string.gmatch(str, "[^" .. sep .. "]+") do
 table.insert(result, part)
 end
 return result
end
print(table.concat(split("a,b,c,d", ","), " | ")) -- a | b | c | d

第12节:数据库访问 (LuaSQL)

安装 LuaSQL

luarocks install luasql-sqlite3
luarocks install luasql-mysql # MySQL
luarocks install luasql-postgres # PostgreSQL

SQLite 示例

local luasql = require("luasql.sqlite3")
local env = luasql.sqlite3()
 
-- 连接数据库
local conn = env:connect("test.db")
print(conn:execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"))
 
-- 插入数据
conn:execute("INSERT INTO users (name, age) VALUES ('Alice', 25)")
conn:execute("INSERT INTO users (name, age) VALUES ('Bob', 30)")
 
-- 使用参数化查询(防SQL注入)
local stmt = conn:prepare("INSERT INTO users (name, age) VALUES (?, ?)")
stmt:bind("Charlie", 28)
stmt:execute()
stmt:close()
 
-- 查询数据
local cursor = conn:execute("SELECT * FROM users")
local row = {}
while row do
 row = cursor:fetch({}, "a") -- "a" 表示返回关联数组
 if row then
 print(row.id, row.name, row.age)
 end
end
cursor:close()
 
-- 更新/删除
conn:execute("UPDATE users SET age = 26 WHERE name = 'Alice'")
conn:execute("DELETE FROM users WHERE name = 'Charlie'")
 
conn:close()
env:close()

事务处理

local conn = env:connect("test.db")
 
local function transferMoney(from, to, amount)
 local ok, err = pcall(function()
 conn:execute("BEGIN TRANSACTION")
 conn:execute(string.format(
 "UPDATE accounts SET balance = balance - %d WHERE id = %d",
 amount, from
 ))
 conn:execute(string.format(
 "UPDATE accounts SET balance = balance + %d WHERE id = %d",
 amount, to
 ))
 conn:execute("COMMIT")
 end)
 if not ok then
 conn:execute("ROLLBACK")
 error("转账失败,已回滚: " .. tostring(err))
 end
end
 
-- transferMoney(1, 2, 100)
conn:close()

下一步:04-C与C++集成.md — 将 Lua 嵌入 C/C++ 应用的完整实战