入门
基础与变量
Lua 变量默认是全局的;用 'local' 声明作用域内的局部变量。Lua 支持多重赋值。注释用 --(单行)和 --[[ ]](多行)。
-- comments start with --
print("Hello, World!")
-- variables (global by default)
x = 10
name = "Alice"
-- local variables
local y = 20
local z = x + y
-- multiple assignment
local a, b, c = 1, 2, 3
print(a, b, c) -- 1 2 3注释
单行注释以 -- 开头。多行注释用 --[[ ... ]]。长括号注释支持层级数字如 --[==[ ]==],可在其中包含 ]] 而不会提前结束。
-- single line comment
--[[ multi-line
comment block ]]
print("hi")
-- long-bracket comments can have = signs for nesting
--[==[ this is a comment with ]] inside ]==]
print("still runs")打印与输出
print() 会加换行并用制表符分隔参数;io.write() 写入原始文本,无分隔符也无换行。print() 会把 nil 显示为 'nil',而大多数操作遇到 nil 会报错。
print("Hello") -- prints with a trailing newline
io.write("no newline") -- writes without a newline
-- print separates multiple args with tabs
print("name", "age") -- name age
-- print converts all types (nil included)
print(42, true, nil) -- 42 true nil
-- io.stdout / io.stderr are file handles
io.stderr:write("warning\n")多重赋值
所有右值在赋值前先求值,因此能干净地交换变量。缺少的值变为 nil,多余的值被静默丢弃。列表会按变量数量调整。
local a, b = 1, 2
print(a, b) -- 1 2
a, b = b, a -- swap without a temp variable
print(a, b) -- 2 1
local x, y, z = 10 -- y, z become nil
local m, n = 1, 2, 3 -- 3 is discarded
print(m, n) -- 1 2交互模式
lua -i 启动交互式解释器。在 REPL 中给表达式加前缀 '=' 会求值并打印,类似 Python 的 REPL。用 'lua file.lua' 运行脚本,自上而下执行。
-- run the REPL: lua -i
-- run a file: lua script.lua
-- show the version: lua -v
-- inside the REPL:
-- > print("hi")
-- hi
-- > = 2 + 3 -- '=' prints an expression value
-- 5
-- > = string.rep("ab", 3)
-- abababnil 与变量删除
nil 是 Lua 唯一的'无值'类型。给表键赋 nil 会删除该键。访问未声明的全局变量会返回 nil 而不是报错,这可能掩盖拼写错误——可用 strict.lua 来捕获。
local x = 10
print(x) -- 10
x = nil -- delete the value
print(x) -- nil
if x == nil then
print("x is nil")
end
-- reading an undeclared name returns nil (no error)
print(undefined_var) -- nil
-- assign nil to let the GC collect a table entry
local t = {a = 1}
t.a = nil数据类型
八种基本类型
Lua 是动态类型;变量没有类型,只有值有。type() 返回标识值类型的小写字符串。8 种类型为 nil、boolean、number、string、table、function、thread、userdata。
-- Lua has 8 basic types:
print(type(nil)) -- nil
print(type(true)) -- boolean
print(type(42)) -- number
print(type("hi")) -- string
print(type({})) -- table
print(type(print)) -- function
print(type(coroutine.create(function() end))) -- thread
print(type(io.stdin)) -- userdata
-- type() always returns a string
print(type(type(42))) -- string数字
整数和浮点数都属于 'number' 类型;math.type() 可区分(Lua 5.3+)。'/' 始终做真除法(浮点),而 '//' 做向下取整除法,保留操作数类型。
-- Lua 5.3+ distinguishes integers and floats
local i = 10 -- integer
local f = 3.14 -- float
print(type(i), math.type(i)) -- number integer
print(type(f), math.type(f)) -- number float
local hex = 0xFF -- 255
local exp = 1e3 -- 1000.0 (float)
print(hex, exp)
-- integer / integer with no fraction stays integer
print(7 / 2) -- 3.5 (true division, always float)
print(7 // 2) -- 3 (floor division)字符串
单引号和双引号等价。长括号 [[ ]](或 [==[ ]==])创建原始多行字符串,其中转义序列不被处理。# 返回字节长度,而非字符数。
local s1 = "double quoted"
local s2 = 'single quoted' -- identical to double
local s3 = [[
a multi-line
string, escapes ignored]]
print(#s1) -- 14 (# is the length operator)
print(s1:upper()) -- DOUBLE QUOTED
-- strings are immutable: operations return new strings布尔值与真值
Lua 只有两个假值:nil 和 false。其他一切(包括 0 和空串)都为真。这与 C/Python 不同,是移植代码时常见的坑。
local flag = true
local done = false
-- ONLY nil and false are falsy
-- 0, "", and {} are all truthy (unlike many languages)
if 0 then print("0 is truthy") end
if "" then print("empty string is truthy") end
-- logical operators short-circuit and return a value
local x = nil or "default" -- "default"
local y = true and "yes" -- "yes"
print(x, y)表
表是关联数组,是 Lua 唯一的复合数据结构,兼作数组、记录、对象和模块。数组下标按约定从 1 开始。一张表可混合序列部分和哈希部分。
-- the table is Lua's only composite data structure
-- it serves as array, dictionary, object, and module
local arr = {10, 20, 30} -- array (1-indexed)
local dict = {name = "Lua", ver = 5.4}
local mixed = {1, 2, x = 3}
print(arr[1]) -- 10 (indices start at 1, not 0)
print(dict.name) -- Lua
print(mixed.x) -- 3
print(#arr) -- 3类型检查
type() 是按值类型分支判断的标准方式。它对 nil 返回 'nil' 而非报错。更细致的判断可用 math.type(整数 vs 浮点)或 rawequal 做同一性比较。
local function describe(v)
local t = type(v)
if t == "table" then
return "a table"
elseif t == "string" then
return "a string: " .. v
elseif t == "nil" then
return "nothing"
end
return "a " .. t
end
print(describe({})) -- a table
print(describe("hi")) -- a string: hi
print(describe(nil)) -- nothing运算符
算术运算符
Lua 支持 +、-、*、/、%、一元 - 和 ^。注意 ^ 是乘方(返 回浮点),不是按位异或。没有自增(++)运算符;用 x = x + 1。
print(10 + 3) -- 13
print(10 - 3) -- 7
print(10 * 3) -- 30
print(10 / 3) -- 3.3333... (true division)
print(10 % 3) -- 1 (modulo)
print(2 ^ 10) -- 1024.0 (^ is exponent, NOT xor)
print(-5) -- -5 (unary minus)向下取整除法与取模
//(Lua 5.3+)向负无穷取整,不同于 C 的截断除法。% 运算定义为 a - floor(a/b)*b,因此结果总取除数的符号——适合做角度和下标的回绕。
-- Lua 5.3+ integer floor division
print(10 // 3) -- 3 (integer)
print(-10 // 3) -- -4 (floors toward -inf)
print(10.0 // 3) -- 3.0 (float floor div)
-- modulo result takes the sign of the divisor
print(10 % 3) -- 1
print(-10 % 3) -- 2
print(10 % -3) -- -2比较运算符
不等用 ~=(不是 !=)。不同类型的值用 == 或 ~= 比较总返回 false/true(nil == nil 除外);跨类型的排序比较会报错。字符串按字节序比较。
print(3 == 3) -- true (equality)
print(3 ~= 4) -- true (inequality: ~=, not !=)
print(3 < 4) -- true
print(3 > 4) -- false
print(3 <= 3) -- true
print(3 >= 4) -- false
print("abc" < "abd") -- true (lexicographic)
print(1 == "1") -- false (different types never equal)逻辑运算符
and 在第一操作数为假时返回它,否则返回第二操作数;or 在第一操作数为真时返回它,否则返回第二操作数。它们返回操作数,而非布尔值。(c) and x or y 这一惯用法在 x 为 false 或 nil 时会失效。
-- and / or / not short-circuit and return a value (not a bool)
print(true and 10) -- 10
print(false or "x") -- "x"
print(nil or "default") -- "default"
print(not nil) -- true
-- idiom for defaults
local name = nil
local display = name or "anonymous"
-- ternary-like (beware falsy 'false' values!)
local v = (cond) and "yes" or "no"字符串连接
.. 是唯一的连接运算符;它总是新建字符串(字符串不可变)。数字会自动转为字符串。拼接大字符串时,把片段收集到表里用 table.concat 比反复连接快得多。
local name = "Lua"
local v = 5.4
print("Hello " .. name .. " " .. v) -- Hello Lua 5.4
-- .. always creates a new string
local s = "a" .. "b" .. "c"
print(s) -- abc
-- numbers are auto-converted when concatenated
print("n=" .. 42) -- n=42
-- repeat a string with string.rep
print(string.rep("ab", 3)) -- ababab长度运算符
# 返回字符串的字节长度或表序列部分的长度。仅对无空洞(nil 间隙)的表才有明确定义。要按 UTF-8 字符计数可用 lua-utf8 或 utf8 库(Lua 5.3+)。
-- # works on strings and the sequence part of tables
print(#"hello") -- 5
print(#{10, 20, 30}) -- 3
-- caution: # only counts the contiguous sequence part
-- the result is UNDEFINED if the table has holes
local t = {1, 2, nil, 4}
print(#t) -- 2 or 4 (implementation-defined)
-- # on a string returns its byte length, not codepoints
print(#"cafe") -- 4
print(#"café") -- 5 (é is 2 bytes in UTF-8)控制结构
if-elseif-else
条件用 then ... end,括号可选。没有 switch 语句——改用 elseif 串联。每个 if、while、for 和函数体都必须用 end 闭合。
local score = 85
if score >= 90 then
print("A")
elseif score >= 80 then
print("B")
elseif score >= 70 then
print("C")
else
print("F")
end
-- keywords: if / then / elseif / else / end
-- parentheses around the condition are optionalwhile 循环
while do ... end 运行零次或多次。循环体是一个 do 块,因此其中声明的 local 作用域限于每次迭代。用 break 跳出最内层循环。
local i = 1
while i <= 5 do
print(i)
i = i + 1 -- Lua has no ++ operator
end
-- the condition is checked before each iteration
local n = 0
while n < 3 do
print("loop", n)
n = n + 1
endrepeat-until
repeat-until 是后测试循环,至少执行一次,条件为真时退出(与 while 相反)。与其他块不同,循环体中声明的 local 对 until 条件可见。
-- repeat-until runs the body at least once (like do-while)
-- the loop STOPS when the condition becomes true
local i = 1
repeat
print(i)
i = i + 1
until i > 5
-- the condition can see locals declared in the body
local x
repeat
x = math.random(1, 10)
until x == 7数值 for
数值 for 让 i 从 start 到 stop(含),每次加 step(默认 1)。循环变量在每次迭代都是全新的局部变量——对闭包安全。limit 和 step 在开始时只求值一次。
-- for start, stop[, step]
for i = 1, 5 do
print(i) -- 1 2 3 4 5
end
for i = 10, 1, -2 do
print(i) -- 10 8 6 4 2
end
-- step defaults to 1; the loop variable is local to the loop
for i = 1, 3 do print(i) end
-- print(i) -- i is out of scope here泛型 for
ipairs 遍历序列部分,遇第一个 nil 即止;pairs 遍历每个键/值对,顺序不定。任何迭代器函数(返回下一个值或 nil 停止)都可驱动泛型 for。
local arr = {"a", "b", "c"}
-- ipairs iterates the array (sequence) part, 1..n
for i, v in ipairs(arr) do
print(i, v) -- 1 a / 2 b / 3 c
end
local dict = {x = 1, y = 2}
-- pairs iterates ALL key/value pairs (order not guaranteed)
for k, v in pairs(dict) do
print(k, v)
endbreak 与 goto
break 只跳出最内层循环;没有 continue。用 goto 加标签跳过迭代,或改用函数加 return 重构。goto 不能跳入某个 local 的作用域。
-- break exits the innermost loop only
for i = 1, 10 do
if i == 5 then break end
print(i) -- 1 2 3 4
end
-- Lua has NO continue; emulate it with goto
for i = 1, 5 do
if i % 2 == 0 then goto continue end
print(i) -- 1 3 5
::continue::
end
-- goto jumps to a label ::name:: within the same scope字符串
字符串字面量与转义
双引号和单引号等价。转义序列(换行 \n、制表符 \t)只在引号字符串中生效。长括号 [[ ]] 或 [==[ ]==] 产生原始字符串,反斜杠是字面量——适合放正则类模式和文件路径。
local s1 = "double 'quoted'"
local s2 = 'single "quoted"'
local s3 = "line1\nline2\ttabbed"
local s4 = "percent: %d %%"
-- long brackets: escapes are NOT processed
local s5 = [[raw \n string, \t kept literal]]
print(s3, s5)长度与索引
# 返回字节长度。string.sub 用 1 起始下标;负数下标从末尾算。string.byte 返回数字码,string.char 由码构造字符串。
local s = "hello"
print(#s) -- 5 (byte length)
-- the string library is 1-indexed
print(s:sub(1, 3)) -- "hel"
print(s:sub(-2)) -- "lo" (negative = from end)
print(s:byte(1)) -- 104 (ASCII of 'h')
print(string.char(104, 105)) -- "hi"长字符串
长括号字符串保留内嵌换行并忽略转义序列。要在其中包含 ]],用相同层级如 [==[ ]==];闭合标记必须有相同数量的 = 号。
-- [[ ]] preserves newlines exactly
local text = [[Line 1
Line 2
Line 3]]
print(text)
-- match bracket level to allow ]] inside
local code = [==[
if a then ]] -- this ]] is literal
end ]==]
print(code)字符串转换
tonumber 失败时返回 nil(不报错),可接受进制(2-36)。tostring 会调用 __tostring 元方法(若存在)。用 tonumber 的结果当数字前务必检查是否为 nil。
local n = tonumber("42") -- 42 (number)
local f = tonumber("3.14") -- 3.14
local bad = tonumber("abc") -- nil (no error raised)
local s = tostring(42) -- "42"
local b = tostring(true) -- "true"
local nil_s = tostring(nil) -- "nil"
print(tonumber("0x1F")) -- 31 (hex)
print(tonumber("11", 2)) -- 3 (base 2)字符串格式化
string.format 对应 C 的 printf:%d 整数、%f 浮点、%s 字符串、%x 十六进制、%o 八进制、%% 字面百分号。宽度和精度(.N)同 C。它是把值嵌入字符串的标准方式。
-- string.format uses C-style format strings
print(string.format("%d + %d = %d", 2, 3, 5)) -- 2 + 3 = 5
print(string.format("%.2f", 3.14159)) -- 3.14
print(string.format("%5d", 42)) -- " 42"
print(string.format("%-10s|", "hi")) -- "hi |"
print(string.format("%x %o", 255, 8)) -- ff 10字符串方法(方法语法)
任何字符串都能通过冒号调用 string 库函数:s:upper() 是 string.upper(s) 的简写。方法语法之所以可行,是因为字符串有元表把它们链接到 string 库。
local s = "Hello, World"
-- s:method(x) is sugar for string.method(s, x)
print(s:upper()) -- HELLO, WORLD
print(s:lower()) -- hello, world
print(s:rep(2)) -- Hello, WorldHello, World
print(s:reverse()) -- dlroW ,olleH
print(s:sub(1, 5)) -- Hello
print(s:find("World")) -- 8 13 (start and end indices)表
创建表
表是关联数组。无键的字面量项进入序列部分(下标从 1 起);name = value 项成为哈希条目。一张表可自由混合二者。
-- array (sequence)
local arr = {10, 20, 30, 40}
-- dictionary (hash)
local p = {name = "Lua", version = 5.4}
-- mixed: both sequence and hash entries
local m = {1, 2, 3, lang = "Lua"}
print(arr[1]) -- 10 (1-indexed!)
print(p.name) -- Lua
print(p["version"]) -- 5.4
print(#arr) -- 4访问元素
t.name 就是 t['name']——标识符形状的键用点号,含空格/特殊字符/计算的键用方括号。两种形式都可读可写。
local t = {name = "Lua", ["full name"] = "Lua 5.4"}
-- dot notation works only for valid identifiers
print(t.name) -- Lua
-- bracket notation works for any string key
print(t["full name"]) -- Lua 5.4
-- bracket form accepts variables
local key = "name"
print(t[key]) -- Lua
t.name = "LuaLang"
t["new key"] = 99数组操作
table.insert 传一个值时追加;传下标时把元素右移后插入。table.remove 弹出末尾(或指定下标)并把后续元素左移,返回被移除的值。
local t = {10, 20, 30}
table.insert(t, 40) -- append: {10,20,30,40}
table.insert(t, 1, 5) -- insert at index 1: {5,10,20,30,40}
print(#t) -- 5
local removed = table.remove(t) -- pop last: 40
local first = table.remove(t, 1) -- remove index 1: 5
print(removed, first, #t) -- 40 5 3
-- table.move(src, f, e, t[, dst]) (Lua 5.3+)
local src = {1, 2, 3, 4, 5}
table.move(src, 2, 4, 1) -- copy elements 2..4 to position 1排序
table.sort 原地对序列部分排序;它不是稳定排序。可选比较函数在第一个参数应排前面时返回 true。省略它则默认升序(用 <)。
local nums = {5, 2, 8, 1, 9}
table.sort(nums)
print(table.concat(nums, ",")) -- 1,2,5,8,9
-- custom comparator (return true if a should come before b)
local people = {
{name = "Bob", age = 30},
{name = "Ann", age = 25},
}
table.sort(people, function(a, b) return a.age < b.age end)
for _, p in ipairs(people) do print(p.name, p.age) end
-- Ann 25 / Bob 30迭代
ipairs 走 1, 2, ... 下标直到遇到 nil,所以只看到数组部分。pairs 访问所有键(数组和哈希),顺序不定。要确定顺序,先对键排序。
local t = {10, 20, 30, name = "Lua"}
-- ipairs: sequence part, stops at the first nil
for i, v in ipairs(t) do
print(i, v) -- 1 10 / 2 20 / 3 30
end
-- pairs: every key/value pair (order not guaranteed)
for k, v in pairs(t) do
print(k, v) -- includes name Lua (order varies)
end
-- numeric loop over the sequence
for i = 1, #t do print(t[i]) end表长度与嵌套表
表可以自然嵌套——matrix[r][c] 先索引行再列。对嵌套表用 # 得到该行的长度。table.concat 高效地用分隔符连接字符串(和数字)序列。
local matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
print(matrix[2][3]) -- 6 (row 2, col 3)
print(#matrix) -- 3 (number of rows)
print(#matrix[1]) -- 3 (number of cols)
-- table.concat joins an array of strings/numbers
print(table.concat({"a", "b", "c"}, "-")) -- a-b-c
print(table.concat({1, 2, 3}, "")) -- 123函数
函数定义
优先用 'local function' 以免污染全局。函数是值,可赋值、传递、存入表。'local function name' 还能安全递归,而 'local name = function' 不行。
-- named (global) function
function greet(name)
return "Hello, " .. name
end
-- local function (preferred)
local function add(a, b)
return a + b
end
print(greet("Lua")) -- Hello, Lua
print(add(2, 3)) -- 5
-- functions are first-class values
local f = add
print(f(10, 20)) -- 30多返回值
函数可返回多个值。它们按上下文调整:列表中只有最后一个表达式会展开为多值;多余的丢弃,缺少的补 nil。
local function minmax(arr)
local lo, hi = arr[1], arr[1]
for _, v in ipairs(arr) do
if v < lo then lo = v end
if v > hi then hi = v end
end
return lo, hi
end
local mn, mx = minmax({3, 1, 4, 1, 5})
print(mn, mx) -- 1 5
-- values are adjusted: extras dropped, missing become nil
local a = minmax({2, 7}) -- a = 2 (mx discarded)可变参数函数
'...' 收集所有额外参数。用 {...} 包成表来遍历,但小心 nil 空洞——用 select('#', ...) 取真实数量,用 table.pack(5.2+)保留 n。
local function sum(...)
local total = 0
for _, v in ipairs({...}) do
total = total + v
end
return total
end
print(sum(1, 2, 3, 4)) -- 10
-- select('#', ...) = count; select(n, ...) = args from n
local function info(...)
print(select("#", ...)) -- number of args
print(select(2, ...)) -- args from position 2 onward
end匿名函数
匿名函数(function ... end)是内联使用的值。它们是函数式编程的核心:回调、比较器、闭包。在表字面量中用分号/逗号分隔。
-- functions are first-class: assign to a variable
local double = function(x) return x * 2 end
print(double(21)) -- 42
-- as table fields
local ops = {
add = function(a, b) return a + b end,
mul = function(a, b) return a * b end,
}
print(ops.add(2, 3), ops.mul(2, 3)) -- 5 6
-- passed inline to higher-order functions
table.sort({3, 1, 2}, function(a, b) return a > b end)函数作为参数
接受其他函数的函数(map、filter、sort)是高阶函数。Lua 没有内置 map/filter,但写起来很简单。返回 #out+1 可追加到序列末尾。
local function map(arr, fn)
local result = {}
for i, v in ipairs(arr) do
result[i] = fn(v)
end
return result
end
local doubled = map({1, 2, 3}, function(x) return x * 2 end)
print(table.concat(doubled, ",")) -- 2,4,6
local function filter(arr, pred)
local out = {}
for _, v in ipairs(arr) do
if pred(v) then out[#out + 1] = v end
end
return out
end用表实现命名参数
传一张表来模拟命名/可选参数。调用语法 f{...}(省略括号)读起来像命名 参数。默认值来自 'opts.key or default'。
-- Lua has no native named args; pass a table instead
local function create(opts)
opts = opts or {}
local name = opts.name or "anonymous"
local age = opts.age or 0
return {name = name, age = age}
end
-- call f{...} is sugar for f({...})
local p = create{name = "Alice", age = 30}
print(p.name, p.age) -- Alice 30闭包
闭包基础
闭包是函数加上它从外层作用域捕获的局部变量(upvalue)。每次调用 make_counter 都创建新的 count,所以计数器彼此独立、状态私有。
local function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local c = make_counter()
print(c()) -- 1
print(c()) -- 2
print(c()) -- 3
-- the inner function captures 'count' (an upvalue)状态封装
闭包无需元表即可提供私有状态。balance 是方法共享的 upvalue,但外部无法访问,封装效果类似 OOP 中的私有字段。
local function make_account(balance)
return {
deposit = function(n) balance = balance + n end,
withdraw = function(n) balance = balance - n end,
get = function() return balance end,
}
end
local acc = make_account(100)
acc.deposit(50)
acc.withdraw(30)
print(acc.get()) -- 120
-- 'balance' is private: only the methods can touch it用闭包实现生成器
每次调用都修改捕获状态的闭包可充当生成器。每次调用从上次停下的地方继续。对于无需挂起的有状态迭代器,这比协程更简单。
local function fibgen()
local a, b = 0, 1
return function()
a, b = b, a + b
return a
end
end
local next_fib = fibgen()
for i = 1, 8 do
io.write(next_fib(), " ") -- 1 1 2 3 5 8 13 21
end
print()记忆化
memoize 用一个表作为缓存(存为 upvalue)包装函数。相同参数的重复调用返回缓存结果。注意:nil 结果需要哨兵值,因为 cache[x]==nil 也表示'未缓存'。
local function memoize(fn)
local cache = {}
return function(x)
if cache[x] == nil then
cache[x] = fn(x)
end
return cache[x]
end
end
local function slow_square(n)
-- pretend this is expensive
return n * n
end
local fast_square = memoize(slow_square)
print(fast_square(5)) -- 25 (computed)
print(fast_square(5)) -- 25 (cached)Upvalue 与作用域
upvalue 是被嵌套函数捕获的局部变量。每次调用 adder 都产生一个独立的新 x。do-end 块无需控制结构即可创建局部作用域,适合限制变量寿命。
local function adder(x)
-- 'x' is an upvalue for the returned function
return function(y) return x + y end
end
local add5 = adder(5)
local add10 = adder(10)
print(add5(3)) -- 8
print(add10(3)) -- 13
-- each call to adder creates a separate upvalue
-- block scope with do-end
do
local tmp = "hidden"
end
-- print(tmp) -- error: tmp is out of scope元表与元方法
设置元表
元表是一个普通表,其特殊键(如 __index、__add)为另一个表定义行为。setmetatable 附加元表;getmetatable 取回。每张表最多一个元表。
local t = {}
local mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt) -- true
-- setmetatable returns the table, so chain inline
local t2 = setmetatable({key = 1}, {
__tostring = function(self) return "MyTable" end,
})
print(tostring(t2)) -- MyTable__index 查找
当表里找不到键时触发 __index。若它是表,Lua 就在其中查找(实现继承/默认值)。若它是函数,Lua 以表和键为参数调用它。
local defaults = {color = "red", size = 10}
local obj = {color = "blue"}
setmetatable(obj, {__index = defaults})
print(obj.color) -- blue (own key wins)
print(obj.size) -- 10 (falls back to defaults)
print(obj.shape) -- nil (not found anywhere)
-- __index can also be a function: __index = function(t, k) ...__newindex 赋值
给尚不存在的键赋值时触发 __newindex。在其中用 rawset 存值可避免再次触发 __newindex(否则会递归)。rawget/rawset 是绕开元方法的等价物。
local log = {}
local t = setmetatable({}, {
__newindex = function(t, k, v)
log[k] = v -- record the assignment
rawset(t, k, v) -- actually store it (avoid recursion)
end,
})
t.x = 5 -- triggers __newindex
print(log.x, t.x) -- 5 5
-- rawset/rawget bypass metamethods算术元方法
算术元方法(__add、__sub、__mul、__div、__mod、__pow、__unm、__idiv、__concat)让表参与运算符。当运算原生未定义时,Lua 在任一操作数上查找元方法。
local Vec = {}
Vec.__index = Vec
Vec.__add = function(a, b) return Vec.new(a.x + b.x, a.y + b.y) end
Vec.__mul = function(a, n) return Vec.new(a.x * n, a.y * n) end
function Vec.new(x, y)
return setmetatable({x = x, y = y}, Vec)
end
local v1 = Vec.new(1, 2)
local v2 = Vec.new(3, 4)
local v3 = v1 + v2 -- uses __add
local v4 = v1 * 10 -- uses __mul
print(v3.x, v3.y) -- 4 6比较元方法
比较元方法是 __eq、__lt、__le(Lua 推导其余)。仅当两个操作数共享同一元方法时才生效,且 __eq 要求两个值类型相同。
local Money = {}
Money.__index = Money
Money.__lt = function(a, b) return a.cents < b.cents end
Money.__le = function(a, b) return a.cents <= b.cents end
Money.__eq = function(a, b) return a.cents == b.cents end
local function money(cents)
return setmetatable({cents = cents}, Money)
end
print(money(100) < money(200)) -- true
print(money(100) == money(100)) -- true__call 与 __tostring
__call 让表能像函数一样被调用(适合函子和工厂)。__tostring 控制 tostring() 和 print() 如何渲染该表。两者都极大提升自定义类型的易用性。
local Greeter = {}
Greeter.__index = Greeter
Greeter.__call = function(self, who)
return "Hi " .. who .. ", I am " .. self.name
end
Greeter.__tostring = function(self)
return "Greeter(" .. self.name .. ")"
end
local g = setmetatable({name = "Bot"}, Greeter)
print(g("Alice")) -- Hi Alice, I am Bot
print(tostring(g)) -- Greeter(Bot)
-- __call lets a table be invoked like a function面向对象编程
类模拟
Lua 没有类;对象是表,其元表的 __index 指回类表。方法通过 __index 查找。'Animal.new' 是工厂;实例以 Animal 为元表。
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:speak()
return self.name .. " makes a sound"
end
local a = Animal.new("Cat")
print(a:speak()) -- Cat makes a sound对象创建与 self
冒号语法定义和调用方法时带有隐式 self。'function C:m() ... end' 等价于 'function C.m(self) ... end','obj:m()' 等价于 'C.m(obj)'。这是 Lua 的 OOP 惯用法。
local Counter = {}
Counter.__index = Counter
function Counter.new()
return setmetatable({count = 0}, Counter)
end
-- the colon ':' adds an implicit 'self' parameter
function Counter:inc()
self.count = self.count + 1
end
local c = Counter.new()
c:inc() -- sugar for Counter.inc(c)
c:inc()
print(c.count) -- 2构造函数
构造函数按约定命名为 'new',返回新建的实例。'x or 0' 提供命名默认值。方法调用用冒号,使 self 指向接收者。
local Point = {}
Point.__index = Point
function Point.new(x, y)
local obj = setmetatable({}, Point)
obj.x = x or 0
obj.y = y or 0
return obj
end
function Point:distance(other)
local dx, dy = self.x - other.x, self.y - other.y
return math.sqrt(dx*dx + dy*dy)
end
local p1 = Point.new(0, 0)
local p2 = Point.new(3, 4)
print(p1:distance(p2)) -- 5通过元表实现继承
子类是这样一张表:其元表的 __index 指向父类,因此缺失的方法会落到父类。实例以子类为元表,形成查找链。
local Animal = {}
Animal.__index = Animal
function Animal.new(name) return setmetatable({name = name}, Animal) end
function Animal:speak() return self.name .. " speaks" end
local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog
function Dog.new(name)
return setmetatable(Animal.new(name), Dog)
end
function Dog:bark() return self.name .. " barks!" end
local d = Dog.new("Rex")
print(d:speak()) -- Rex speaks (inherited from Animal)
print(d:bark()) -- Rex barks! (Dog's own method)方法重写
子类用同名方法定义即覆盖;子类版本遮蔽父类版本。要显式调用父类实现,用 Parent.method(self, ...)。
local Shape = {}
Shape.__index = Shape
function Shape.new() return setmetatable({}, Shape) end
function Shape:area() return 0 end
local Circle = setmetatable({}, {__index = Shape})
Circle.__index = Circle
function Circle.new(r) return setmetatable({r = r}, Circle) end
function Circle:area() return math.pi * self.r * self.r end -- override
local c = Circle.new(2)
print(c:area()) -- 12.566...
-- call the parent method explicitly: Shape.area(c)基 于类的继承模式
一个小的 Class() 辅助函数统一了创建与继承:new() 构建实例并在存在 init 时调用它。这个模式是许多 Lua OOP 库(middleclass、classic 等)的基础。
local function Class(base)
local cls = {}
cls.__index = cls
if base then setmetatable(cls, {__index = base}) end
cls.new = function(...)
local obj = setmetatable({}, cls)
if obj.init then obj:init(...) end
return obj
end
return cls
end
local Vehicle = Class()
function Vehicle:init(wheels) self.wheels = wheels end
function Vehicle:desc() return self.wheels .. " wheels" end
local Bike = Class(Vehicle)
function Bike:init() Vehicle.init(self, 2) end
print(Bike.new():desc()) -- 2 wheels协程
创建协程
coroutine.create 把函数包成 thread 对象(初始为挂起态)。函数体在 resume 之前不会运行。协程是协作式的——只在 yield/resume 处切换控制权。
local co = coroutine.create(function(a, b)
print("start", a, b)
local c = coroutine.yield(a + b)
print("resumed with", c)
return "done"
end)
-- coroutine.create returns a thread
print(type(co)) -- thread
print(coroutine.status(co)) -- suspendedresume 与 yield
resume 运行协程直到下一个 yield(或 return),返回 true 及任何 yield 的值。函数返回后协程死亡,resume 返回 true 及返回值。
local co = coroutine.create(function()
for i = 1, 3 do
coroutine.yield(i)
end
end)
print(coroutine.resume(co)) -- true 1
print(coroutine.resume(co)) -- true 2
print(coroutine.resume(co)) -- true 3
print(coroutine.resume(co)) -- true (no more values)
print(coroutine.status(co)) -- dead协程状态
协程在 yield 处挂起时为 suspended,运行时为 running,它 resume 的另一协程在运行时为 normal,函数返回后为 dead。coroutine.running() 标识当前线程。
-- states: suspended, running, normal, dead
local co = coroutine.create(function()
coroutine.yield()
end)
print(coroutine.status(co)) -- suspended
coroutine.resume(co)
print(coroutine.status(co)) -- suspended (yielded)
coroutine.resume(co)
print(coroutine.status(co)) -- dead
-- coroutine.running() returns the current coroutine (nil in main)
print(coroutine.running()) -- nil (main thread)双向值传递
值双向流动:第一次 resume 的参数进入函数形参,后续 resume 的参数由 yield 返回,yield 的参数从 resume 出来。这可实现生成器和协作式流水线。
local co = coroutine.create(function(x)
local y = coroutine.yield(x * 2) -- sends x*2, receives y
local z = coroutine.yield(y * 3) -- sends y*3, receives z
return z
end)
print(coroutine.resume(co, 10)) -- true 20 (x=10, yields 20)
print(coroutine.resume(co, 5)) -- true 15 (y=5, yields 15)
print(coroutine.resume(co, 7)) -- true 7 (z=7, returns 7)coroutine.wrap
coroutine.wrap 返回一个普通函数,每次调用都 resume 协程——比 create+resume 更方便,适合迭代器。代价是错误会抛出而非返回(ok, err),没有内置错误标志。
-- wrap returns a function (not a thread); it auto-resumes
-- and propagates errors instead of returning ok
local gen = coroutine.wrap(function()
for i = 1, 5 do
coroutine.yield(i * i)
end
end)
print(gen()) -- 1
print(gen()) -- 4
print(gen()) -- 9
-- ideal for iterator-style use
for v in coroutine.wrap(function()
coroutine.yield("a"); coroutine.yield("b")
end) do
print(v) -- a, then b
break
end模块
定义模块
标准模式:构建局部表 M,附加函数和字段,然后返回它。返回的表就是模块。让 M 保持局部可避免把名字泄漏到全局命名空间。
-- file: mymath.lua
local M = {}
function M.square(n) return n * n end
function M.cube(n) return n * n * n end
M.pi = 3.14159
return M
-- a module is just a table returned by the chunk使用 require
require 加载模块一次并缓存到 package.loaded,因此代码块只运行一次。点分模块名映射到嵌套路径。require 还处理 C 扩展和模块的搜索器。
-- require loads the module and caches it in package.loaded
local mymath = require("mymath")
print(mymath.square(5)) -- 25
print(mymath.cube(3)) -- 27
print(mymath.pi) -- 3.14159
-- subsequent requires return the cached table
local m2 = require("mymath")
print(m2 == mymath) -- true
-- dotted names map to paths: a.b.c -> a/b/c.lua模块搜索路径
package.path 是以分号分隔的模板列表,? 代表模块名。require 按顺序尝试每个模板。在启动时调整 package.path 指向你的库目录。
-- package.path controls where require looks for Lua modules
print(package.path)
-- patterns like ./?.lua;./?/init.lua;? is the module name
-- add a custom directory
package.path = "./libs/?.lua;" .. package.path
-- require("foo") then tries ./libs/foo.lua
-- package.cpath is the equivalent for compiled C modules
-- (?.so on Unix, ?.dll on Windows)重新加载模块
把 package.loaded[name] 设为 nil 会强制下一次 require 重新运行代码块,便于开发时热重载。对旧模块表的现有引用仍是过时的,需要在用到处重新获取模块。
-- require caches in package.loaded; clear it to reload
package.loaded["mymath"] = nil
local mymath = require("mymath") -- re-runs the chunk
-- a hot-reload helper
local function reload(name)
package.loaded[name] = nil
return require(name)
end
-- warning: existing references to the old table
-- are NOT updated after a reload模块模式
现代惯用法是构建局部表并返回它。避免遗留的 module() 函数(它会污染全局,5.2+ 已移除)。只导出选定名字可保持公共 API 干净。
-- pattern 1: return a table (most common)
local M = {}
function M.fn() end
return M
-- pattern 2: keep helpers local, export a clean table
local function helper() end
local function main_fn() helper() end
return { main = main_fn }
-- pattern 3: module() (legacy, Lua 5.1) -- deprecated
-- module("foo"); function _M.fn() end错误处理
error()
error() 抛出错误,通常带字符串消息。可选的 level 控制错误指向:1(默认)是 error() 调用处;2 归咎于调用者——在库内部很有用。允许非字符串错误,对类型化异常有用。
local function divide(a, b)
if b == 0 then
error("division by zero") -- raises an error
end
return a / b
end
-- the level argument controls the reported position
error("msg", 0) -- no position info added
error("msg", 1) -- points to the caller of error (default)
error("msg", 2) -- points to the caller's caller
-- error() can throw non-string objects too
error({code = 42, msg = "x"})pcall(保护调用)
pcall 以保护模式运行函数,成功返回 true 加结果,失败返回 false 加错误。由于 Lua 没有 try/catch,它是安全错误处理的主要工具。
local function risky(x)
if x < 0 then error("negative") end
return math.sqrt(x)
end
local ok, result = pcall(risky, -1)
if ok then
print("result:", result)
else
print("error:", result) -- error: negative
end
local ok2, r2 = pcall(risky, 16)
print(ok2, r2) -- true 4xpcall 与 traceback
xpcall 类似 pcall,但接受一个错误处理函数,可在栈展开前用 debug.traceback 构建完整回溯。从 Lua 5.3 起可在处理函数后传参。
local function handler(err)
return debug.traceback("Error: " .. tostring(err), 2)
end
local function f() error("boom") end
-- xpcall takes a handler; Lua 5.3+ also accepts arguments
local ok, err = xpcall(f, handler)
print(ok, err) -- false Error: boom\nstack traceback:...
-- xpcall with arguments (Lua 5.3+)
local function risky(x) if x < 0 then error("x") end return x end
local ok2, r2 = xpcall(risky, handler, 9)
print(ok2, r2) -- true 9assert
assert 在第一参数非 nil/非 false 时返回它,否则以给定(或默认)消息抛错。它是检查 io.open 等失败时返回 nil 的函数结果的惯用法。
-- assert(v, msg) raises if v is nil or false
local function load_config(path)
local f = assert(io.open(path, "r")) -- raises if file missing
return f:read("*a")
end
local n = assert(tonumber("42"), "not a number") -- 42
-- assert(nil, "bad input") -- raises: bad input
-- common idiom: assert on results that return nil on failure
local line = assert(f:read("*L"))错误级别
错误级别告诉 Lua 报告哪个调用帧。库应使用 level 2,让用户看到自己的调用处,而非内部 error() 行。level 0 完全省略文件/行信息。
local function check(x)
if type(x) ~= "number" then
-- level 2 blames the caller of check, not check itself
error("expected number, got " .. type(x), 2)
end
return x * 2
end
-- level 0: add no position information
-- level 1: point at the error() call (default)
-- level 2: point at who called the function containing error()文件 I/O
简单 I/O
简单 I/O 模型作用于默认流,适合一次性读写。io.read 的模式控制返回内容:'*l' 一行、'*a' 全部、'*n' 数字,或一个数字表示读取那么多字节。
-- the simple model uses default input/output streams
io.write("Enter your name: ")
local name = io.read() -- read a line
print("Hi " .. name)
-- io.read modes:
-- "*n" a number
-- "*a" the entire remaining input
-- "*l" a line (default, no newline)
-- "*L" a line including the newline
local n = io.read("*n")
local all = io.read("*a")写入文件
io.output 设置默认输出文件;后续 io.write 写入那里。追加或读写模式要用 io.open 取句柄。务必关闭文件以刷新缓冲并释放资源。
io.output("out.txt") -- set the default output file
io.write("line one\n")
io.write("line two\n")
io.close() -- close the default output
-- append mode needs io.open
local f = io.open("out.txt", "a")
f:write("appended\n")
f:close()文件句柄
io.open 失败时返回 nil 加错误消息(而非异常),所以检查第一个返回值。句柄的方法与 io 函数对应,但作用于特定文件:read、write、lines、seek、close。
-- io.open returns a file handle (or nil + error message)
local f, err = io.open("data.txt", "r")
if not f then
error("could not open: " .. err)
end
local content = f:read("*a") -- read the entire file
print(content)
f:close() -- always close handles
-- handle methods: f:read, f:write, f:lines, f:seek, f:close逐行读取
f:lines() 返回句柄各行的迭代器——你必须自己关闭 f。io.lines(filename) 是个便利函数,自动打开、迭代并关闭文件,适合一次性逐行处理。
local f = assert(io.open("data.txt", "r"))
for line in f:lines() do
print(line)
end
f:close() -- f:lines does NOT close the handle
-- io.lines(path) opens, iterates, and closes automatically
for line in io.lines("data.txt") do
print(line)
end文件模式
模式对应 C 的 fopen:'r'、'w'、'a' 分别为读/写/追加,可选 '+' 表示读写、'b' 表示二进制。多数系统上 'b' 无效,因为 Lua 默认按二进制处理文件。
-- io.open(path, mode) modes:
-- "r" read (default); file must exist
-- "w" write; truncate or create
-- "a" append; create if missing
-- "r+" read/write; file must exist
-- "w+" read/write; truncate or create
-- "a+" read/append; create if missing
-- append "b" for binary on some systems: "rb", "wb"
local f = io.open("log.txt", "a")
f:write(os.date(), " started\n")
f:close()文件定位
f:seek 移动读写位置。'set' 从头绝对定位,'cur' 相对当前位置,'end' 从末尾。seek('end') 不带偏移返回文件大小。
local f = assert(io.open("data.txt", "r"))
-- f:seek(whence, offset)
-- "set": from the start, "cur": from current, "end": from end
print(f:seek("end")) -- file size (position at end)
f:seek("set", 0) -- rewind to the start
local first_line = f:read("*l")
print(first_line)
f:close()模式匹配
基础模式(Lua 模式,非正则)
Lua 模式像正则但更简单,用 % 做转义和字符类(不是 \)。它没有交替(|)和除 '-' 外的非贪婪量词,但支持捕获,对大多数解析任务够用。
-- Lua patterns are NOT regular expressions; they use % not \
local s = "hello world"
print(s:find("wor")) -- 7 9 (start and end indices)
print(s:find("xyz")) -- nil
-- . matches any single character
print(("abc"):match(".")) -- a
-- string.find / gmatch / gsub / match all use patterns
print(("x = 42"):match("%d+")) -- 42字符类
预定义类以 % 开头:%a、%d、%s、%w、%p、%l、%u、%c、%x 及其大写否定形式。自定义集合用 [...],类似正则。% 也是魔法字符的转义:%.、%%、%$。
-- %a letters, %d digits, %s whitespace, %w alnum
-- %p punctuation, %l lower, %u upper, %c control
-- uppercase = negation: %A non-letters, %D non-digits
print(("a1 b2"):match("%a%d")) -- a1
print(("x = 42"):match("%d+")) -- 42
-- custom sets: [abc], [a-z], [^0-9]
print(("a-b"):match("[a-z]")) -- a
print(("phone 555-1234"):match("%d+-%d+")) -- 555-1234捕获
模式中的括号捕获匹配文本;match 按顺序返回它们,gmatch 每次迭代 yield 它们。空捕获 () 返回匹配在字符串中的数值位置。
-- parentheses () create captures
local s = "name=Alice age=30"
local k, v = s:match("(%w+)=(%w+)")
print(k, v) -- name Alice
-- gmatch iterates over all matches
for key, val in ("a=1 b=2 c=3"):gmatch("(%w+)=(%w+)") do
print(key, val) -- a 1 / b 2 / c 3
end
-- an empty capture () returns the current position
print(("abc"):match("()b")) -- 2gsub 替换
gsub 返回新字符串和替换次数。替换可以是字符串(用 %0/%1.. 引用捕获)、表(按捕获查找)或函数(每次匹配调用)。第四个参数限制次数。
local s = "hello world"
print(s:gsub("o", "0")) -- hell0 w0rld 2 (string, count)
-- replacements can reference captures %1, %2
print(("2024-01-15"):gsub("(%d+)-(%d+)-(%d+)", "%3/%2/%1")) -- 15/01/2024 1
-- a function receives captures and returns the replacement
print(("1 2 3"):gsub("%d", function(d) return d * 2 end)) -- 2 4 6 3
-- a 4th argument limits the number of replacements
print(("aaaa"):gsub("a", "b", 2)) -- bbaa 2锚点与模式项
锚点 ^ 和 $ 把匹配绑定到字符串首尾。'*' 和 '+' 贪婪,'-' 是懒惰(非贪婪)变体,'?' 使前一项可选。用 '-' 匹配尽可能短的文本。
-- ^ anchors to the start, $ anchors to the end
print(("hello"):match("^he")) -- he
print(("hello"):match("lo$")) -- lo
print(("hello"):match("^hello$")) -- hello
-- pattern items:
-- * zero or more (greedy)
-- + one or more (greedy)
-- - zero or more (lazy)
-- ? zero or one
print(("aXXXb"):match("a(%a+)b")) -- XXX (greedy)
print(("<!--c-->"):match("<!--(.-)-->")) -- c (lazy)标准库
string 库
string 库涵盖查找(find/match/gmatch/gsub)、格式化(format)和操作(sub/rep/upper/lower/reverse/byte/char)。Lua 5.3+ 还增加了 string.pack/unpack 处理二进制数据及 utf8 库。
local s = "Lua"
print(s:upper(), s:lower(), s:len()) -- LUA lua 3
print(s:rep(3)) -- LuaLuaLua
print(s:reverse()) -- auL
print(s:sub(1, 2)) -- Lu
print(s:byte(1), string.char(76)) -- 76 L
print(string.format("%s %d", "v", 5)) -- Lua v 5
-- full set: find, gmatch, gsub, match, format,
-- rep, sub, byte, char, len, upper, lower, pack, unpacktable 库
table 库操作序列部分:insert、remove、sort、concat 及(5.3+)move 。table.pack 把可变参数包成带 n 字段的表(保留 nil);table.unpack 反过来,把表展开成列表。
local t = {1, 2, 3}
table.insert(t, 4) -- {1,2,3,4}
table.insert(t, 1, 0) -- {0,1,2,3,4}
table.remove(t, 1) -- removes 0, returns it
table.sort(t) -- sort in place
print(table.concat(t, ",")) -- 1,2,3,4
-- Lua 5.2+: table.pack / table.unpack
local args = table.pack(1, 2, 3) -- {1,2,3, n=3}
print(table.unpack(args)) -- 1 2 3
-- Lua 5.3+: table.move(src, f, e, t[, dst])math 库
math 提供常量(pi、huge)、取整(floor/ceil)、弧度三角函数、随机数(用 randomseed 播种)及 max/min/abs/sqrt。math.type 在 Lua 5.3+ 区分整数与浮点。
print(math.pi, math.huge) -- 3.1415926535898 inf
print(math.max(1, 5, 3)) -- 5
print(math.floor(3.7), math.ceil(3.2)) -- 3 4
print(math.abs(-5)) -- 5
print(math.sqrt(16)) -- 4.0
print(math.sin(math.pi / 2)) -- 1.0 (radians)
print(math.random(1, 100)) -- random int in [1,100]
math.randomseed(os.time()) -- seed the generator
print(math.type(3), math.type(3.0)) -- integer floatio 库
io 提供简单模型(默认流上的 io.read/io.write)和完整模型(io.open 句柄上的 f:read/f:write/f:lines/f:seek)。io.stdin、io.stdout、io.stderr 是预定义句柄;io.tmpfile 创建临时文件。
-- default input/output streams
io.write("to stdout\n")
local line = io.read("*l") -- read a line
-- file handles
local f = io.open("x.txt", "w")
f:write("data\n")
f:close()
-- io.stdin, io.stdout, io.stderr are handles
io.stderr:write("an error\n")
-- io.tmpfile() returns a handle to an auto-deleted temp file
local tmp = io.tmpfile()
tmp:write("scratch")
tmp:seek("set", 0)os 库
os 涵盖时间/日期(time、date、difftime、clock)、环境(getenv)和进程控制(execute、exit、tmpname、rename、remove)。io.popen 运行命令并返回读取其输出的句柄。
print(os.time()) -- current timestamp (seconds)
print(os.date("%Y-%m-%d %H:%M")) -- formatted date
local t = os.date("*t") -- table: year, month, day, hour...
print(t.year, t.month, t.day)
print(os.getenv("PATH")) -- an environment variable (or nil)
os.execute("ls") -- run a shell command
local h = io.popen("date") -- capture command output
print(h:read("*a"))
h:close()debug 库
debug 库提供内省:traceback 用于栈,getinfo 用于函数/帧元数据,getlocal/setlocal 用于变量,setmetatable/getmetatable 可绕过元方法限制。谨慎使用——它会破坏不变量。
-- debug.traceback returns a call-stack string
local function f() error("x") end
print(xpcall(f, function(e) return debug.traceback(e) end))
-- debug.getinfo returns info about a function or stack frame
local info = debug.getinfo(print)
print(info.what, info.name) -- C print
-- debug.getlocal / setlocal inspect stack locals
local function locals()
local a = 1
local name, value = debug.getlocal(1, 1)
print(name, value) -- a 1
end
locals()Lua 与 C 交互
C API 概览
在 C 中嵌入 Lua:用 luaL_newstate 创建 lua_State,打开标准库,再用 luaL_dostring/do_file 运行代码。每个 state 独立,必须 close 以释放内存。
// Lua is embedded via a C library; the core object is lua_State
// Headers: lua.h, lauxlib.h, lualib.h
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
int main(void) {
lua_State *L = luaL_newstate(); // create a state
luaL_openlibs(L); // open the standard libs
luaL_dostring(L, "print('hi')"); // run Lua source
lua_close(L); // free the state
return 0;
}基于栈的 API
C API 基于栈:压入值传入,从栈读取结果,pop 清理。下标从底部 1 起,或从顶部负数起(-1 是栈顶)。lua_gettop 返回栈大小。
// all communication with Lua goes through a stack
lua_State *L = luaL_newstate();
// push values
lua_pushinteger(L, 42);
lua_pushstring(L, "hello");
// stack now: bottom[42]["hello"]top
// inspect (1-based from bottom, negative from top)
int top = lua_gettop(L); // 2
lua_Integer n = lua_tointeger(L, 1); // 42
const char *s = lua_tostring(L, 2); // "hello"
lua_pop(L, 2); // pop 2 values从 C 调用 Lua
从 C 调用 Lua 函数:用 lua_getglobal 压入它,压入参数,再用 lua_pcall 调用并指定参数和结果数。lua_pcall 返回状态;出错时消息在栈顶。
// script.lua: function add(a, b) return a + b end
luaL_dofile(L, "script.lua");
lua_getglobal(L, "add"); // push the function 'add'
lua_pushinteger(L, 3); // arg 1
lua_pushinteger(L, 4); // arg 2
// lua_pcall(L, nargs, nresults, msgh)
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
} else {
lua_Integer sum = lua_tointeger(L, -1); // 7
lua_pop(L, 1); // pop the result
}向 Lua 暴露 C 函数
可被 Lua 调用的 C 函数从栈读参数(用 luaL_check* 做类型检查),压入结果,并返回结果数量。通过 lua_pushcfunction + lua_setglobal 注册。
// a Lua-callable C function has signature: int fn(lua_State *L)
static int l_square(lua_State *L) {
lua_Integer n = luaL_checkinteger(L, 1); // read arg 1, type-checked
lua_pushinteger(L, n * n); // push the result
return 1; // number of results
}
// register it so Lua can call it
lua_pushcfunction(L, l_square);
lua_setglobal(L, "square");
// now in Lua: print(square(5)) -> 25模块注册
C 模块是导出 luaopen_<模块名> 的共享库。luaL_newlib 从 luaL_Reg 数组构建表。Lua 的 require 通过 package.cpath 和 luaopen_ 入口找到它,然后返回该表作为模块。
// register a table of C functions as a Lua module
static const luaL_Reg mylib[] = {
{"square", l_square},
{"cube", l_cube},
{NULL, NULL} // sentinel marks the end
};
int luaopen_mylib(lua_State *L) {
luaL_newlib(L, mylib); // create a table and register the funcs
return 1; // return that table (the module)
}
// in Lua: local mylib = require("mylib"); print(mylib.square(5))
// compiled as a shared lib (.so/.dll) found via package.cpathLuaJIT 与 FFI
LuaJIT 概览与 jit.status
LuaJIT 是 Lua 5.1 的追踪式 JIT,可快 10-100 倍。全局 'jit' 表仅存在于 LuaJIT 下,因此 'if jit then' 可干净地检测。jit.version、jit.status、jit.os、jit.arch 描述构建。
-- LuaJIT is a Just-In-Time compiler for Lua 5.1, much faster
-- detect LuaJIT at runtime
if jit then
print(jit.version) -- e.g. LuaJIT 2.1.0-beta3
print(jit.status()) -- true if JIT compilation is on
print(jit.os, jit.arch) -- e.g. Linux x64
end
-- 'jit' is nil on standard PUC Lua, so this guard is safe
if not jit then
print("running plain Lua")
endJIT 控制
jit.on/off/flush 全局控制编译;传一个函数则只针对它。对某条热路径禁用 JIT 有助于诊断错误编译或测量仅解释器执行的性能。
jit.on() -- enable JIT compilation globally
jit.off() -- disable it (fall back to the interpreter)
jit.flush() -- flush the compiled-code cache
-- control compilation per-function
local function hot()
local s = 0
for i = 1, 1e6 do s = s + i end
return s
end
jit.off(hot) -- never compile this function
-- jit.on(hot) to re-enable
-- useful for debugging or working around compiler bugsFFI:加载 C 库
FFI 库让 LuaJIT 无需绑定代码即可调用 C 函数。ffi.C 访问默认 C 命名空间(通常是 libc);ffi.load 加载具名共享库。调用会被 JIT 编译为原生代码,非常快。
local ffi = require("ffi")
-- ffi.C is the default C namespace (libc on most systems)
-- ffi.load("name") loads a shared library explicitly
local C = ffi.C
local libc = ffi.load("c")
-- many standard C functions are available right away
print(ffi.C.time(nil)) -- current Unix timestamp
-- ffi.load("m") for libm, ffi.load("ssl") for OpenSSL, etc.FFI:类型声明(cdef)
ffi.cdef 声明 C 类型、结构和函数 原型(类似头文件)。ffi.new 分配 C 对象;ffi.C(或已加载库)调用声明的函数。C 结构可作为 Lua 值使用,速度接近 C。
local ffi = require("ffi")
-- declare C types and prototypes with ffi.cdef
ffi.cdef[[
typedef struct { int x, y; } Point;
int printf(const char *fmt, ...);
]]
-- create and use a C struct directly
local p = ffi.new("Point", {x = 1, y = 2})
print(p.x, p.y) -- 1 2
ffi.C.printf("p = (%d, %d)\n", p.x, p.y) -- calls libc printfFFI:调用 C 函数
cdef 声明原型后,ffi.C.name 调用 C 函数。C 数字映射到 Lua 数字;C 字符串(char*)必须用 ffi.string 转成 Lua 字符串。在 JIT 下这些调用开销极小。
local ffi = require("ffi")
ffi.cdef[[
double sqrt(double x);
int abs(int x);
const char *getenv(const char *name);
]]
-- call C's math functions directly (very fast under JIT)
local root = ffi.C.sqrt(16.0) -- 4.0
local pos = ffi.C.abs(-7) -- 7
print(root, pos)
-- ffi.string converts a C string (const char *) to a Lua string
local path = ffi.string(ffi.C.getenv("PATH"))
print(#path)FFI:从 C 回调到 Lua
ffi.cast 把 Lua 函数转成可用作回调的 C 函数指针。C 数组从 0 开始下标。回调开销大,且不再使用时必须用 :free() 释放(它们会钉住 Lua 对象)。
local ffi = require("ffi")
ffi.cdef[[
typedef int (*cmp_fn)(const void *, const void *);
void qsort(void *base, size_t n, size_t sz, cmp_fn cmp);
]]
-- create a C callback that calls back into Lua
local arr = ffi.new("int[?]", 5, {5, 3, 1, 4, 2})
local cmp = ffi.cast("cmp_fn", function(a, b)
return ffi.cast("int*", a)[0] - ffi.cast("int*", b)[0]
end)
ffi.C.qsort(arr, 5, ffi.sizeof("int"), cmp)
for i = 0, 4 do io.write(arr[i], " ") end -- 1 2 3 4 5
cmp:free() -- free the callback when doneLua 在 Redis 与 Nginx 中
Redis EVAL
Redis 原子地执行 Lua 脚本——脚本运行期间没有其他命令执行。KEYS 和 ARGV 传入数据;返回值转为 Redis 回复。redis.call 用于硬失败,redis.pcall 在 Lua 中处理错误。
-- Redis runs Lua scripts via EVAL; keys are KEYS, args are ARGV
-- the script returns a single value to Redis
-- run in redis-cli:
-- EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey hello
local key = KEYS[1]
local val = ARGV[1]
redis.call('SET', key, val)
return redis.call('GET', key) -- "hello"
-- redis.call: errors abort the script
-- redis.pcall: errors are returned as a table