第 2 章 · 函数:可移植的行为单元 章节摘要:函数是 Lua 里唯一可移植的"行为单元"——它既是脚本组织逻辑的方式,也是宿主与脚本互相调用的全部载体。本章两节:先讲函数的定义、调用、多返回值与可变参数,这是宿主注册回调、脚本挂载钩子的语法基础;再讲闭包与尾调用,理解"函数连着它出生的环境一起走"以及"递归不爆栈"的机制,为后面的迭代器、协程与热更新做铺垫。 学习目标 阅读完本章,你应当能够: 用 function 语句与函数表达式两种形态定义函数,说清 与 在递归上的差别; 熟练使用多返回值与"多退少补"规则,掌握 table.pack、table.unpack 与 select 三个处理可变参数的工具; 用表参数实现命名参数与缺省值,写出宿主回调风格的 API;
章节摘要:函数是 Lua 里唯一可移植的"行为单元"——它既是脚本组织逻辑的方式,也是宿主与脚本互相调用的全部载体。本章两节:先讲函数的定义、调用、多返回值与可变参数,这是宿主注册回调、脚本挂载钩子的语法基础;再讲闭包与尾调用,理解"函数连着它出生的环境一起走"以及"递归不爆栈"的机制,为后面的迭代器、协程与热更新做铺垫。
阅读完本章,你应当能够:
local function f 与 local f = function 在递归上的差别;宿主给脚本递的不是数据而是函数:注册一个回调,就等于把一段未来要发生的行为交到了房东手里。
从两种定义形态讲起,重点是调用处的三条规则:参数多退少补、返回值截断与展开、括号强制单值。可变参数 ... 与三个配套工具是脚本适配各种宿主签名的瑞士军刀。末尾以"宿主注册回调"的完整例子收束,展示一等公民函数在寄宿现场的日常用法。
函数是值,所以可以进表、当参数、做返回值;函数加环境就是闭包,upvalue 让计数器、迭代器、按请求隔离的上下文成为可能。尾调用则把"函数调函数"的栈帧复用变成语言保证,状态机写法的合法性由此而来。这两件事放一节讲,因为它们共同回答了"行为为什么能像数据一样流转"。
一句话论点:2.1 讲函数的"形"(怎么定义怎么调),2.2 讲函数的"神"(函数带着环境走、调用不积累栈),形神合起来才是一个完整的行为单元。
2.1 形:定义 · 调用 · 参数与返回值的调整规则 │ └── 回调注册:函数作为值交给宿主 ──┐ ├──→ 2.2 神:闭包带环境 · 尾调用复用栈帧 └── 可变参数:适配宿主签名 ─────────┘ │ ├──→ 第 3 章迭代器 · 第 7 章协程 └──→ 第 9 章热更新按函数替换
进入正文前,先自问三个问题,它们是本章的地基,答不上来说明第 1 章的相应小节需要回炉:
函数在 Lua 的八种类型里是哪一种,值语义还是引用语义? 是 function 类型、引用语义的重值——赋值与传参传的是同一个函数对象,这也是"注册回调"在物理上成立的原因(宿主存下这个引用,日后调用)。
多变量赋值的"多退少补"规则你还记得吗? 右边多则丢、左边多则 nil。这条规则在本章直接升级成参数与返回值的调整规则——函数调用不过是"自动化的多变量赋值"。
local 的作用域边界到哪? 所在块(chunk、do、控制结构体、函数体)结束。闭包会让"块结束了但值还活着",这正是 2.2 节要讲透的现象。
函数是宿主与脚本之间的行为货币。宿主把 C 函数当货物发进脚本世界(9.2 节的注册),脚本把 Lua 函数当回执交还给宿主(事件回调、定时器、排序器)——两种货币在同一座栈上兑换(9.1 节)。多返回值让"一次调用交回一批结果"成为惯例(Redis 的脚本返回、pcall 的 ok 加结果签名全是它);可变参数让脚本能适配任何宿主的调用签名;闭包让"行为连同它的上下文"一起过界(OpenResty 每请求的上下文捕获);尾调用让深递归与状态机住得进有限的栈——第 9 章热更新的状态机写法,语法基础全在本章。