05 - 函数 / Functions
函数 / Functions
Lua 的函数是一等公民(first-class citizen)——可以存在变量里、作为参数传递、作为返回值返回。这是 Lua 最强大的特性之一。
Lua functions are first-class citizens — stored in variables, passed as arguments, returned from other functions. This is one of Lua’s most powerful features.
🟢 基础 / Basics — 定义和调用函数
1. 函数定义的两种方式
-- 方式一:命名函数(语法糖)/ Named function (syntactic sugar)
local function greet(name)
print("Hello, " .. name)
end
-- 等价于:
local greet
greet = function(name)
print("Hello, " .. name)
end
-- 方式二:匿名函数赋值 / Anonymous function assigned to variable
local add = function(a, b)
return a + b
end
greet("Lua") -- Hello, Lua
print(add(3, 5)) -- 8
2. 参数 / Parameters
-- 参数不足时,缺失的为 nil
local function show(a, b, c)
print(a, b, c)
end
show(1) -- 1 nil nil
show(1, 2) -- 1 2 nil
show(1, 2, 3) -- 1 2 3
show(1, 2, 3, 4) -- 1 2 3 (多余的参数被丢弃)
-- 多返回值 / Multiple return values
local function swap(a, b)
return b, a
end
local x, y = swap(1, 2)
print(x, y) -- 2 1
-- 只接收部分返回值
local x = swap(1, 2) -- x = 2, 第二个返回值被丢弃
3. 冒号语法 / Colon Syntax
-- 冒号语法自动传递 self 参数
-- Colon syntax automatically passes self
local dog = {name = "Buddy"}
function dog:bark() -- 等价于 dog.bark = function(self)
print(self.name .. " says: Woof!")
end
dog:bark() -- Buddy says: Woof!
-- 等价于 dog.bark(dog)
-- 也可以用点号,但需要手动传递 self
function dog.sit(self)
print(self.name .. " is sitting")
end
dog:sit() -- Buddy is sitting
🟡 进阶 / Intermediate — 高级函数特性
1. 可变参数 / Variadic Arguments
-- ... 表示可变参数
local function sum(...)
local result = 0
for _, v in ipairs({...}) do -- 将 ... 转为表
result = result + v
end
return result
end
print(sum(1, 2, 3, 4, 5)) -- 15
-- 更高效的方式:用 select()
local function sum2(...)
local result = 0
for i = 1, select("#", ...) do -- select("#", ...) 返回参数个数
result = result + select(i, ...) -- select(i, ...) 返回第 i 个及之后的参数
end
return result
end
-- 将可变参数固定地赋给变量
local function first3(...)
local a, b, c = ...
return a, b, c
end
print(first3(1, 2, 3, 4, 5)) -- 1 2 3
2. 闭包 / Closures
-- 闭包 = 函数 + 它捕获的外部变量
-- Closure = function + captured external variables
function makeCounter(start)
local count = start or 0 -- 被闭包捕获的"上值"
return function()
count = count + 1 -- 闭包修改了外部变量
return count
end
end
local c1 = makeCounter(0)
local c2 = makeCounter(100)
print(c1()) -- 1
print(c1()) -- 2
print(c1()) -- 3
print(c2()) -- 101
print(c2()) -- 102
-- c1 和 c2 拥有独立的 count 变量!
闭包的实用场景:
-- 场景一:函数工厂
function multiplier(factor)
return function(x)
return x * factor
end
end
local double = multiplier(2)
local triple = multiplier(3)
print(double(5)) -- 10
print(triple(5)) -- 15
-- 场景二:私有变量(模块模式)
function makeBankAccount(initialBalance)
local balance = initialBalance -- 私有变量,外部无法直接访问
return {
deposit = function(amount)
balance = balance + amount
end,
withdraw = function(amount)
if amount > balance then
error("Insufficient funds")
end
balance = balance - amount
end,
getBalance = function()
return balance
end,
}
end
local account = makeBankAccount(1000)
account.deposit(500)
print(account.getBalance()) -- 1500
-- print(balance) -- nil(无法直接访问)
-- 场景三:回调与延迟执行
function defer(fn, delay)
return function(...)
local args = {...}
os.execute("sleep " .. delay)
return fn(table.unpack(args))
end
end
3. 闭包与变量绑定时机 / Closure Variable Binding
-- 陷阱:闭包捕获的是变量的引用,不是值
-- Trap: closures capture variable references, not values
local functions = {}
for i = 1, 5 do
functions[i] = function() return i end
end
-- 所有函数共享同一个 i 变量!
for _, f in ipairs(functions) do
print(f()) -- 全部输出 6!(循环结束后 i = 6)
end
-- 正确做法:用立即调用创建新的作用域
local functions2 = {}
for i = 1, 5 do
functions2[i] = (function(j) -- j 是新的局部变量
return function() return j end
end)(i) -- 立即调用,传入当前的 i 值
end
for _, f in ipairs(functions2) do
print(f()) -- 1, 2, 3, 4, 5
end
-- Lua 5.4 的解决方案:使用 <close> 或 to-be-closed 变量
-- (更常见的做法是在循环内用 do...end 块)
for i = 1, 5 do
local j = i -- 创建新的局部变量
functions[i] = function() return j end
end
4. 尾调用优化 / Tail Call Optimization (TCO)
-- 尾调用:函数的最后一个动作是调用另一个函数
-- Tail call: the last action is calling another function
-- 有尾调用优化(不会栈溢出)
function factorial(n, acc)
acc = acc or 1
if n <= 1 then return acc end
return factorial(n - 1, n * acc) -- 尾调用位置!
end
print(factorial(100000)) -- 不会栈溢出
-- 没有尾调用优化(会栈溢出)
function badFactorial(n)
if n <= 1 then return 1 end
return n * badFactorial(n - 1) -- 不是尾调用!需要在调用后做乘法
end
-- badFactorial(100000) -- 栈溢出!
-- 尾调用的条件 / Conditions for TCO:
-- 1. 调用是函数的最后一个动作
-- 2. 调用的返回值直接被返回
-- 3. 不在保护模式(pcall/xpcall)中
-- ✅ 是尾调用
function f() return g() end
function f(x) if x then return g() else return h() end end
-- ❌ 不是尾调用
function f() return g() + 1 end -- 还要做加法
function f() return (g()) end -- 调整为单值
function f() g() end -- 没有返回
🔴 高级 / Advanced — 闭包与 Upvalue 的内部实现
1. 闭包的内部结构 / Closure Internals
Lua 闭包在内存中的结构:
┌───────────────────────────────────────┐
│ Closure (LClosure) │
├───────────────────────────────────────┤
│ Proto* ──────────► 函数原型/字节码 │
│ nupvalues: 2 │
│ upvals[0] ──────► ┌──────────────┐ │
│ │ Upvalue │ │
│ │ .value = 10 │ │
│ │ .ref = 1 │ │
│ │ .open = false│ │
│ └──────────────┘ │
│ upvals[1] ──────► ┌──────────────┐ │
│ │ Upvalue │ │
│ │ .value = "x"│ │
│ └──────────────┘ │
└───────────────────────────────────────┘
Open Upvalue(未关闭): Closed Upvalue(已关闭):
┌─────────────────────┐ ┌─────────────────────┐
│ Upvalue │ │ Upvalue │
│ .open = true │ │ .open = false │
│ .index ──► 栈上位置 │ │ .value = 实际的值 │
└─────────────────────┘ └─────────────────────┘
2. Upvalue 的共享 / Upvalue Sharing
-- 多个闭包可以共享同一个 upvalue
function makeGetSet()
local value = 0 -- 一个 upvalue,被下面两个函数共享
local function get() return value end
local function set(v) value = v end
return get, set
end
local get, set = makeGetSet()
set(42)
print(get()) -- 42
-- get 和 set 共享同一个 value upvalue
-- 修改通过 set 做的,get 能看到
3. select() 的内部实现原理
-- select(index, ...) 的特殊行为
-- select("#", ...) 返回可变参数个数
-- select(i, ...) 返回第 i 个及之后的所有参数
-- 内部实现非常巧妙:
-- select 不会创建新表,而是直接操作栈
-- 这就是为什么 select 比 {...} 更高效
-- 对比 / Comparison:
-- 方式一:{...} 创建新表,有内存开销
local function bad(...)
local args = {...} -- 分配新表
for i = 1, #args do
process(args[i])
end
end
-- 方式二:select 零开销
local function good(...)
for i = 1, select("#", ...) do
process(select(i, ...))
end
end
4. 函数调用的栈帧 / Function Call Stack Frame
┌─────────────────────────────────────────┐
│ Lua 调用栈 / Call Stack │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Frame 3: function g() │ │
│ │ locals: z = 30 │ │
│ │ PC: 5 │ │
│ ├─────────────────────────────────┤ │
│ │ Frame 2: function f() │ │
│ │ locals: y = 20 │ │
│ │ PC: 8 │ │
│ ├─────────────────────────────────┤ │
│ │ Frame 1: main chunk │ │
│ │ locals: x = 10 │ │
│ │ PC: 3 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
尾调用时(TCO),Frame 2 会被 Frame 3 替代:
┌─────────────────────────────────┐
│ Frame 3: function g() │ ← Frame 2 被回收
│ PC: 1 │
├─────────────────────────────────┤
│ Frame 1: main chunk │
│ PC: 3 │
└─────────────────────────────────┘
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | function 定义、return、多返回值、冒号语法、self |
| 🟡 进阶 | 可变参数(…)、select()、闭包、尾调用优化 |
| 🔴 高级 | Upvalue 内部结构、闭包共享变量、栈帧与 TCO 原理 |
下一章:表 / Tables