第5课: 函数基础
函数概述
函数是Lua中的第一类值(first-class values),这意味着函数可以像其他值一样被赋值、传递、返回。理解函数的内部实现和特性对于编写高效、模块化的代码至关重要。
Lua函数的设计哲学:
- 第一类值:函数是值,可以赋值、传递、返回
- 词法作用域:函数可以访问定义时的外部变量(闭包)
- 多返回值:支持返回多个值,简化代码
- 尾调用优化:支持尾递归优化,避免栈溢出
- 灵活参数:支持可变参数和默认参数
函数定义与调用
Lua提供了多种定义函数的方式,理解这些方式的区别和适用场景很重要。
基本函数定义
函数定义的多种方式
函数定义方式对比:
- function name() end:全局函数定义
- local function name() end:局部函数定义(推荐)
- local name = function() end:函数表达式赋值
函数调用的深入理解
函数调用的内部机制:
函数调用的执行流程:
- 创建新的调用栈帧
- 将参数压入栈中
- 跳转到函数体执行
- 执行函数体代码
- 将返回值压入栈中
- 销毁调用栈帧
- 返回到调用点
多返回值
Lua函数可以返回多个值,这是Lua的特色功能之一。理解多返回值的规则和使用场景非常重要。
基本多返回值
多返回值的规则
多返回值的处理规则:
- 赋值:接收变量多于返回值时,多余的为nil
- 赋值:接收变量少于返回值时,多余的返回值被忽略
- 表达式:在表达式中只使用第一个返回值
- 函数参数:作为参数时,只传递第一个返回值
- 表构造:在表构造中可以使用所有返回值
- return:return语句可以返回多个值
多返回值的实际应用
可变参数
Lua支持可变参数函数,允许函数接受任意数量的参数。理解可变参数的处理方式对于编写灵活的函数很重要。
基本可变参数
可变参数的深入理解
可变参数的处理方式:
- 表达式...:可变参数表达式,可以传递给其他函数
- {...}:将可变参数打包成表
- select("#", ...):获取可变参数的个数
- select(n, ...):获取第n个参数及其后的所有参数
可变参数的实际应用
maxValue then
maxValue = v
end
end
return maxValue
end
print(max(1, 5, 3, 9, 2)) -- 9
-- 函数组合
local function compose(f, g)
return function(...)
return f(g(...))
end
end
local function double(x) return x * 2 end
local function addOne(x) return x + 1 end
local doubleThenAddOne = compose(addOne, double)
local addOneThenDouble = compose(double, addOne)
print(doubleThenAddOne(5)) -- 11 (5*2+1)
print(addOneThenDouble(5)) -- 12 ((5+1)*2)
匿名函数
匿名函数是没有名称的函数,通常作为值传递给其他函数或赋值给变量。理解匿名函数的使用场景对于编写函数式风格的代码很重要。
基本匿名函数
匿名函数的实际应用
闭包
闭包是函数式编程的重要概念,指函数可以访问定义时的外部变量。理解闭包的原理和应用对于编写模块化、安全的代码至关重要。
基本闭包
闭包的深入理解
闭包的内部机制:
闭包由两部分组成:
- 函数代码:函数的可执行代码
- 环境:函数定义时的外部变量环境
当闭包被创建时,它会捕获定义时的外部变量。即使外部函数已经返回,闭包仍然可以访问这些变量。
闭包的实际应用
局部函数
局部函数的作用域限制在定义的块内,避免污染全局命名空间。理解局部函数的作用域和递归调用很重要。
基本局部函数
局部函数的作用域
局部函数的作用域规则:
- 作用域:局部函数只在定义的块内可见
- 递归:局部函数可以递归调用自身
- 互调:需要前向声明才能互相调用
- 性能:局部函数访问速度比全局函数快
尾调用优化
尾调用优化是Lua的重要特性,可以避免递归导致的栈溢出。理解尾调用的条件和优化机制对于编写高效的递归函数至关重要。
基本尾调用
尾调用的条件
尾调用的条件:
要成为尾调用,必须满足以下条件:
- 位置:必须是函数的最后一个动作
- 形式:必须是函数调用,不能有其他操作
- 返回:必须直接返回调用结果
尾调用优化会重用当前栈帧,而不是创建新的栈帧。
综合示例
重要提示:
- Lua函数是第一类值,可以赋值、传递、返回
- 函数可以返回多个值,这是Lua的特色功能
- 闭包可以访问外部函数的局部变量
- 尾调用优化可以避免栈溢出
- 优先使用local function定义局部函数
- 可变参数使用...接收,用select处理
- 匿名函数常用于回调和高阶函数
练习题:
- 编写函数计算圆的面积和周长(返回两个值)
- 实现一个可变参数函数,计算所有参数的平均值
- 使用闭包实现一个简单的计数器
- 编写递归函数计算斐波那契数列(使用尾调用优化)
- 实现一个高阶函数,接受函数和列表,返回新列表
- 使用闭包实现一个简单的缓存机制
- 编写函数解析URL,返回协议、域名、路径等
- 实现一个简单的表达式求值器,支持加减乘除
- 使用匿名函数实现表的自定义排序
- 编写函数工厂,创建不同配置的函数