Lua学习笔记(4)——函数

Lua

本文介绍了Lua的函数,包括多重返回值、变长参数、具名实参,以及比较高级的主题如闭包、递归的尾调用等等。

1. 基础知识

调用函数都需要写圆括号,即使没有参数,但有一种特殊例外:函数若只有一个参数且参数是字面字符串或table构造式,则圆括号可有可无,如dofile 'a.lua'f{x=10, y=20}

Lua为面向对象式的调用提供冒号操作符的特殊语法,如o.foo(o, x)等价于o:foo(x)。和Javascript类似,调用函数时提供的实参数量可以与形参数量不同,若实参多了则舍弃,不足则多余的形参初始化为nil。

1.1 多重返回值

Lua允许函数返回多个结果,函数返回如return max, index,接收如s, e = string.find("hello Lua world", "Lua")。如果一个函数调用不是一系列表达式的最后一个元素,则只产生一个值:

function foo() return "a", "b" end
x, y = foo(), 20    -- x="a", y=20(foo的第二个返回值被丢弃)
print(foo() .. "x")    -- 输出ax,这是因为当函数出现在一个表达式中时,Lua会将其返回值数量调整为1

另外,只有当一个函数调用作为最后一个元素时,返回值才不会被调整,在其他位置都会被调整为1个,如t = {foo2()}则t={“a”, “b”},t = {foo2(), 4}则t={“a”, 4}。

特殊函数unpack接受一个数组作为参数,并从下标1开始返回该数组的所有元素,如a, b = unpack({10, 20, 30}),则30被丢弃。unpack的一项重要用途体现在“泛型调用”机制中。

1.2 变长参数

函数参数表中3个点(…)表示该函数可接受不同数量的实参。在Lua 5.0中,没有提供“…”表达式,如果要遍历变长参数,可以访问函数内隐含的局部变量arg如果还有固定参数,则必须放在变长参数之前。

2. 高级主题

2.1 closure闭合函数

和Javascript的闭包基本是一个东西,此处不再赘述。从技术上说,Lua中只有closure,而不存在“函数”,因为函数本身就是一种特殊的closure。closure的应用很广泛,如用于高阶函数的参数、为GUI工具包创建回调、重定义函数并在新实现中调用旧实现、创建“沙盒”安全运行环境等等。

2.2 非全局的函数

大部分Lua库都采用了将函数存储在table中的机制(如io.read,math.sin),例如下面采用了三种方式来定义table的成员函数:

MathLib = {
    plus = function(x, y) return x + y end
}
MathLib.minus = function(x, y) return x - y end
function MathLib.multiply(x, y) return x * y end

局部函数的定义:

local f = function(<参数>) <函数体> end
local function f(<参数>) <函数体> end  -- Lua提供的语法糖

**注意如果定义递归函数,不能使用上面第一种定义方式(因为在函数体调用f时,f尚未定义完毕),使用第二种“语法糖”则没问题;或者使用“前向声明”,先local ff = function ...这样定义。

2.3 正确的尾调用

当一个函数调用时另一个函数的最后一个动作时,该调用算是一条“尾调用”,例如function f(x) return g(x) end。由于在尾调用后程序不要保存任何关于该函数的栈信息,所以递归调用不会耗费栈空间,可以递归调用无数次。有一些看似是“尾调用”的代码,其实都违背了这条准则:

function f(x) g(x) end    -- 调用g后,f没有立即返回,还需要丢弃g返回的临时结果
function f(x) return g(x) + 1    -- 还要做一次加法
function f(x) return x or g(x)    -- 必须调整为一个返回值

所以,只有形如return <func>(<args>)这样的调用形式才算是尾调用。

参考文献:电子工业出版社《Lua程序设计(第2版)》第5-6章

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器