Skip to content

Lua 速查表

轻量级、可嵌入的脚本语言。

01

入门

基础与变量

Lua 变量默认是全局的;用 'local' 声明作用域内的局部变量。Lua 支持多重赋值。注释用 --(单行)和 --[[ ]](多行)。

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

注释

单行注释以 -- 开头。多行注释用 --[[ ... ]]。长括号注释支持层级数字如 --[==[ ]==],可在其中包含 ]] 而不会提前结束。

lua
-- 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 会报错。

lua
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,多余的值被静默丢弃。列表会按变量数量调整。

lua
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' 运行脚本,自上而下执行。

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)
-- ababab

nil 与变量删除

nil 是 Lua 唯一的'无值'类型。给表键赋 nil 会删除该键。访问未声明的全局变量会返回 nil 而不是报错,这可能掩盖拼写错误——可用 strict.lua 来捕获。

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
02

数据类型

八种基本类型

Lua 是动态类型;变量没有类型,只有值有。type() 返回标识值类型的小写字符串。8 种类型为 nil、boolean、number、string、table、function、thread、userdata。

lua
-- 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
-- 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)

字符串

单引号和双引号等价。长括号 [[ ]](或 [==[ ]==])创建原始多行字符串,其中转义序列不被处理。# 返回字节长度,而非字符数。

lua
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 不同,是移植代码时常见的坑。

lua
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 开始。一张表可混合序列部分和哈希部分。

lua
-- 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 做同一性比较。

lua
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
03

运算符

算术运算符

Lua 支持 +、-、*、/、%、一元 - 和 ^。注意 ^ 是乘方(返回浮点),不是按位异或。没有自增(++)运算符;用 x = x + 1。

lua
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
-- 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 除外);跨类型的排序比较会报错。字符串按字节序比较。

lua
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 时会失效。

lua
-- 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 比反复连接快得多。

lua
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+)。

lua
-- # 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)
04

控制结构

if-elseif-else

条件用 then ... end,括号可选。没有 switch 语句——改用 elseif 串联。每个 if、while、for 和函数体都必须用 end 闭合。

lua
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 optional

while 循环

while do ... end 运行零次或多次。循环体是一个 do 块,因此其中声明的 local 作用域限于每次迭代。用 break 跳出最内层循环。

lua
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
end

repeat-until

repeat-until 是后测试循环,至少执行一次,条件为真时退出(与 while 相反)。与其他块不同,循环体中声明的 local 对 until 条件可见。

lua
-- 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 在开始时只求值一次。

lua
-- 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。

lua
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)
end

break 与 goto

break 只跳出最内层循环;没有 continue。用 goto 加标签跳过迭代,或改用函数加 return 重构。goto 不能跳入某个 local 的作用域。

lua
-- 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
05

字符串

字符串字面量与转义

双引号和单引号等价。转义序列(换行 \n、制表符 \t)只在引号字符串中生效。长括号 [[ ]] 或 [==[ ]==] 产生原始字符串,反斜杠是字面量——适合放正则类模式和文件路径。

lua
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 由码构造字符串。

lua
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"

长字符串

长括号字符串保留内嵌换行并忽略转义序列。要在其中包含 ]],用相同层级如 [==[ ]==];闭合标记必须有相同数量的 = 号。

lua
-- [[ ]] 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。

lua
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。它是把值嵌入字符串的标准方式。

lua
-- 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 库。

lua
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)
06

创建表

表是关联数组。无键的字面量项进入序列部分(下标从 1 起);name = value 项成为哈希条目。一张表可自由混合二者。

lua
-- 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']——标识符形状的键用点号,含空格/特殊字符/计算的键用方括号。两种形式都可读可写。

lua
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 弹出末尾(或指定下标)并把后续元素左移,返回被移除的值。

lua
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。省略它则默认升序(用 <)。

lua
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 访问所有键(数组和哈希),顺序不定。要确定顺序,先对键排序。

lua
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 高效地用分隔符连接字符串(和数字)序列。

lua
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
07

函数

函数定义

优先用 'local function' 以免污染全局。函数是值,可赋值、传递、存入表。'local function name' 还能安全递归,而 'local name = function' 不行。

lua
-- 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。

lua
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。

lua
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)是内联使用的值。它们是函数式编程的核心:回调、比较器、闭包。在表字面量中用分号/逗号分隔。

lua
-- 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 可追加到序列末尾。

lua
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
-- 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
08

闭包

闭包基础

闭包是函数加上它从外层作用域捕获的局部变量(upvalue)。每次调用 make_counter 都创建新的 count,所以计数器彼此独立、状态私有。

lua
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 中的私有字段。

lua
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

用闭包实现生成器

每次调用都修改捕获状态的闭包可充当生成器。每次调用从上次停下的地方继续。对于无需挂起的有状态迭代器,这比协程更简单。

lua
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 也表示'未缓存'。

lua
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 块无需控制结构即可创建局部作用域,适合限制变量寿命。

lua
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
09

元表与元方法

设置元表

元表是一个普通表,其特殊键(如 __index、__add)为另一个表定义行为。setmetatable 附加元表;getmetatable 取回。每张表最多一个元表。

lua
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 以表和键为参数调用它。

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 是绕开元方法的等价物。

lua
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 在任一操作数上查找元方法。

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 要求两个值类型相同。

lua
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() 如何渲染该表。两者都极大提升自定义类型的易用性。

lua
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
10

面向对象编程

类模拟

Lua 没有类;对象是表,其元表的 __index 指回类表。方法通过 __index 查找。'Animal.new' 是工厂;实例以 Animal 为元表。

lua
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 惯用法。

lua
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 指向接收者。

lua
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 指向父类,因此缺失的方法会落到父类。实例以子类为元表,形成查找链。

lua
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, ...)。

lua
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 等)的基础。

lua
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
11

协程

创建协程

coroutine.create 把函数包成 thread 对象(初始为挂起态)。函数体在 resume 之前不会运行。协程是协作式的——只在 yield/resume 处切换控制权。

lua
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))  -- suspended

resume 与 yield

resume 运行协程直到下一个 yield(或 return),返回 true 及任何 yield 的值。函数返回后协程死亡,resume 返回 true 及返回值。

lua
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() 标识当前线程。

lua
-- 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 出来。这可实现生成器和协作式流水线。

lua
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),没有内置错误标志。

lua
-- 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
12

模块

定义模块

标准模式:构建局部表 M,附加函数和字段,然后返回它。返回的表就是模块。让 M 保持局部可避免把名字泄漏到全局命名空间。

lua
-- 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 扩展和模块的搜索器。

lua
-- 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 指向你的库目录。

lua
-- 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 重新运行代码块,便于开发时热重载。对旧模块表的现有引用仍是过时的,需要在用到处重新获取模块。

lua
-- 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 干净。

lua
-- 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
13

错误处理

error()

error() 抛出错误,通常带字符串消息。可选的 level 控制错误指向:1(默认)是 error() 调用处;2 归咎于调用者——在库内部很有用。允许非字符串错误,对类型化异常有用。

lua
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,它是安全错误处理的主要工具。

lua
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  4

xpcall 与 traceback

xpcall 类似 pcall,但接受一个错误处理函数,可在栈展开前用 debug.traceback 构建完整回溯。从 Lua 5.3 起可在处理函数后传参。

lua
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  9

assert

assert 在第一参数非 nil/非 false 时返回它,否则以给定(或默认)消息抛错。它是检查 io.open 等失败时返回 nil 的函数结果的惯用法。

lua
-- 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 完全省略文件/行信息。

lua
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()
14

文件 I/O

简单 I/O

简单 I/O 模型作用于默认流,适合一次性读写。io.read 的模式控制返回内容:'*l' 一行、'*a' 全部、'*n' 数字,或一个数字表示读取那么多字节。

lua
-- 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 取句柄。务必关闭文件以刷新缓冲并释放资源。

lua
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。

lua
-- 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) 是个便利函数,自动打开、迭代并关闭文件,适合一次性逐行处理。

lua
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 默认按二进制处理文件。

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') 不带偏移返回文件大小。

lua
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()
15

模式匹配

基础模式(Lua 模式,非正则)

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 及其大写否定形式。自定义集合用 [...],类似正则。% 也是魔法字符的转义:%.、%%、%$。

lua
-- %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 它们。空捕获 () 返回匹配在字符串中的数值位置。

lua
-- 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"))   -- 2

gsub 替换

gsub 返回新字符串和替换次数。替换可以是字符串(用 %0/%1.. 引用捕获)、表(按捕获查找)或函数(每次匹配调用)。第四个参数限制次数。

lua
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

锚点与模式项

锚点 ^ 和 $ 把匹配绑定到字符串首尾。'*' 和 '+' 贪婪,'-' 是懒惰(非贪婪)变体,'?' 使前一项可选。用 '-' 匹配尽可能短的文本。

lua
-- ^ 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)
16

标准库

string 库

string 库涵盖查找(find/match/gmatch/gsub)、格式化(format)和操作(sub/rep/upper/lower/reverse/byte/char)。Lua 5.3+ 还增加了 string.pack/unpack 处理二进制数据及 utf8 库。

lua
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, unpack

table 库

table 库操作序列部分:insert、remove、sort、concat 及(5.3+)move。table.pack 把可变参数包成带 n 字段的表(保留 nil);table.unpack 反过来,把表展开成列表。

lua
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+ 区分整数与浮点。

lua
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  float

io 库

io 提供简单模型(默认流上的 io.read/io.write)和完整模型(io.open 句柄上的 f:read/f:write/f:lines/f:seek)。io.stdin、io.stdout、io.stderr 是预定义句柄;io.tmpfile 创建临时文件。

lua
-- 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 运行命令并返回读取其输出的句柄。

lua
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 可绕过元方法限制。谨慎使用——它会破坏不变量。

lua
-- 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()
17

Lua 与 C 交互

C API 概览

在 C 中嵌入 Lua:用 luaL_newstate 创建 lua_State,打开标准库,再用 luaL_dostring/do_file 运行代码。每个 state 独立,必须 close 以释放内存。

lua
// 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 返回栈大小。

lua
// 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 返回状态;出错时消息在栈顶。

lua
// 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 注册。

lua
// 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_ 入口找到它,然后返回该表作为模块。

lua
// 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.cpath
18

LuaJIT 与 FFI

LuaJIT 概览与 jit.status

LuaJIT 是 Lua 5.1 的追踪式 JIT,可快 10-100 倍。全局 'jit' 表仅存在于 LuaJIT 下,因此 'if jit then' 可干净地检测。jit.version、jit.status、jit.os、jit.arch 描述构建。

lua
-- 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")
end

JIT 控制

jit.on/off/flush 全局控制编译;传一个函数则只针对它。对某条热路径禁用 JIT 有助于诊断错误编译或测量仅解释器执行的性能。

lua
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 bugs

FFI:加载 C 库

FFI 库让 LuaJIT 无需绑定代码即可调用 C 函数。ffi.C 访问默认 C 命名空间(通常是 libc);ffi.load 加载具名共享库。调用会被 JIT 编译为原生代码,非常快。

lua
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。

lua
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 printf

FFI:调用 C 函数

cdef 声明原型后,ffi.C.name 调用 C 函数。C 数字映射到 Lua 数字;C 字符串(char*)必须用 ffi.string 转成 Lua 字符串。在 JIT 下这些调用开销极小。

lua
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 对象)。

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 done
19

Lua 在 Redis 与 Nginx 中

Redis EVAL

Redis 原子地执行 Lua 脚本——脚本运行期间没有其他命令执行。KEYS 和 ARGV 传入数据;返回值转为 Redis 回复。redis.call 用于硬失败,redis.pcall 在 Lua 中处理错误。

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

Redis 的 KEYS 与 ARGV

通过 KEYS 传键名,以便 Redis Cluster 正确路由;非键数据放 ARGV。脚本在服务端缓存,因此用 SCRIPT LOAD 加载一次后用 EVALSHA 重跑可避免重发源码。

lua
-- KEYS[1..N] = keys passed to EVAL (N is EVAL's 2nd argument)
-- ARGV[1..M] = additional arguments
-- EVAL "..." numkeys key1 key2... arg1 arg2...

-- example: sum the values at multiple keys
local total = 0
for i = 1, #KEYS do
  local v = tonumber(redis.call('GET', KEYS[i]))
  if v then total = total + v end
end
return total

-- use SCRIPT LOAD + EVALSHA to cache and reuse scripts

OpenResty / Nginx 概览

OpenResty 把 LuaJIT 和 ngx_lua 模块打包进 Nginx,让你用 Lua 写请求处理器。content_by_lua_block、access_by_lua、rewrite_by_lua 等指令把 Lua 接入 Nginx 的请求阶段。

lua
-- OpenResty embeds LuaJIT in Nginx via ngx_http_lua_module
-- content_by_lua_block runs Lua to generate the response

-- nginx.conf:
-- location /hello {
--   content_by_lua_block {
--     ngx.say("hello from lua")
--   }
-- }

-- ngx.say writes to the response body (with a newline)
-- ngx.print writes without a newline
ngx.status = 200
ngx.say("Hello, ", ngx.var.arg_name)

ngx API

ngx.var 读取 Nginx 变量(uri、args、http_* 头);ngx.req 暴露请求(方法、头、体、uri 参数)。ngx.header 设置响应头。调用 ngx.say/ngx.print 输出体。

lua
-- ngx.var.* accesses Nginx variables
local uri = ngx.var.uri
local ua = ngx.var.http_user_agent

-- ngx.req: the request API
ngx.req.read_body()
local body = ngx.req.get_body_data()

-- request headers (table, case-insensitive keys)
local h = ngx.req.get_headers()
print(h["Content-Type"])

-- set response headers, then write the body
ngx.header["X-Custom"] = "yes"
ngx.say("uri=", uri)

ngx.location.capture

ngx.location.capture 向另一个 Nginx location 发起非阻塞子请求,返回状态、头和体。capture_multi 并行运行多个。子请求不到客户端——是内部组合工具。

lua
-- issue subrequests to other Nginx locations
local res = ngx.location.capture("/api/users")
if res.status == 200 then
  ngx.say(res.body)
end

-- capture_multi: parallel subrequests
local res1, res2 = ngx.location.capture_multi{
  {"/api/a"},
  {"/api/b"},
}
ngx.say(res1.body, res2.body)
-- subrequests are internal; great for composing upstream services

共享字典(ngx.shared)

共享字典(lua_shared_dict)是跨 Nginx worker 进程共享的线程安全内存存储。支持 incr、add 等原子操作,非常适合 OpenResty 应用中的限流、缓存和锁。

lua
-- in nginx.conf:  lua_shared_dict cache 10m;
-- access a dict shared across worker processes
local cache = ngx.shared.cache
cache:set("user:1", "Alice", 60)   -- key, value, ttl in seconds
local name = cache:get("user:1")   -- "Alice"

cache:incr("hits", 1, 0)           -- atomic increment, init 0
cache:delete("old")

-- safe for concurrent access across Nginx workers
-- common uses: rate limiting, caching, distributed locks
local locked = cache:add("lock:job", 1, 30)  -- set only if absent

这篇内容对您有帮助吗?