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)
endpcall:受保护调用
-- 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 ... 消费: 5coroutine.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 # PostgreSQLSQLite 示例
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++ 应用的完整实战