函数教程 |
|
函数使用function
关键字创建,如下所示
function
(
args )
body end
以下示例展示了一个简单的函数,它接收一个参数并返回其值的兩倍
> foo = function (n) return n*2 end > = foo(7) 14
参数(也称为参数)在( )
部分中指定,使用return
关键字从函数返回的值。如果没有return
,函数将不返回值。
请注意,在上面的示例中,我们实际上并没有“命名”函数,我们只是将其分配给了一个变量。这是因为在 Lua 中,函数是普通值(如数字、字符串、表等),您可以对它们执行任何对其他值的操作。这与您可能知道的许多其他语言(如 C)非常不同,在这些语言中,函数具有在编译时固定的永久名称,并且不能像值一样被操作。
function
块是一个表达式(与“1 + 2”是表达式一样),它计算为一个新的函数值。函数值可以通过使用( )
运算符来调用,该运算符运行函数中的代码。( )
对位于函数表达式之后,并可选地包含一个用逗号分隔的参数列表。
这意味着 Lua 函数被认为是匿名(没有预设名称)和一等(与其他值没有区别)。
要记住的另一件事是,与表一样,函数是按引用传递的。例如,当您将包含函数的变量分配给另一个变量时,您只是创建了对同一个函数的新“句柄”。
函数可以接受 0 个或多个参数。这些是在调用函数时传递给函数的值,函数中存储的代码可以使用这些值。在函数内部,参数看起来像变量,只是它们只存在于函数内部。
一个演示参数如何工作以及如何将它们传递给函数的示例
> f = function (op, a, b) >> if op == 'add' then >> return a + b >> elseif op == 'sub' then >> return a - b >> end >> error("invalid operation") >> end > g = function (value) >> print(value) >> end > = f('add', 1, 2) -- args are given inside (), separated by commas. 3 > = f('add', 1, 2, 123) -- extra args are ignored 3 > = f('add', 1) -- missing args aren't an error, instead they will be filled with nil, which might cause an error in the function's code stdin:1: attempt to perform arithmetic on local 'b' (a nil value) > = g() -- to call a function with no args, use () nil > = g "example" -- the () can be omitted if you have one quoted string arg example > = g {} -- same with one table constructor table: 0x820ee0
函数可以使用 `return` 关键字将值返回给调用它们的代码。该值将成为函数调用表达式的值。Lua 的一个独特功能是函数可以返回任意数量的值。在大多数语言中,函数总是返回一个值。要使用此功能,请在 `return` 关键字后放置用逗号分隔的值。
> f = function () >> return "x", "y", "z" -- return 3 values >> end > a, b, c, d = f() -- assign the 3 values to 4 variables. the 4th variable will be filled with nil > = a, b, c, d x y z nil > a, b = (f()) -- wrapping a function call in () discards multiple return values > = a, b x, nil > = "w"..f() -- using a function call as a sub-expression discards multiple returns wx > print(f(), "w") -- same when used as the arg for another function call... x w > print("w", f()) -- ...except when it's the last arg w x y z > print("w", (f())) -- wrapping in () also works here like it does with = w x > t = {f()} -- multiple returns can be stored in a table > = t[1], t[2], t[3] x y z
关于最后一个例子 ( `{f()} `) 需要记住的一点是,如果函数返回 nil,由于表中的 `nil` 被认为是“无值”,`#` 运算符无法可靠地用于获取值的个数,因为它无法确定数组是否包含“空洞”。
如果您习惯于使用返回多个值的语言(如 Python),这些值存储在“元组”类型中,那么 Lua 的工作方式并非如此。Lua 函数实际上返回单独的值,而不是单个容器。
function f(switch) if not switch then --if switch is nil, function f() will not complete anything else below Return return end print("Hello") end f()--doesn't print anything
function f(switch) if not switch then --switch is no longer nil but is instead "1" return end print("Hello") end f(1)--prints "hello"
将函数用作参数或返回值是一个有用的功能,因为它允许您将自己的行为插入现有代码中。一个很好的例子是 `table.sort`,它可以选择接受一个自定义的“小于”函数。
> list = {{3}, {5}, {2}, {-1}} > table.sort(list) attempt to compare two table values stack traceback: [C]: in function 'sort' stdin:1: in main chunk [C]: in ? > table.sort(list, function (a, b) return a[1] < b[1] end) > for i,v in ipairs(list) do print(v[1]) end -1 2 3 5
函数可以在其参数列表的末尾使用 `...`。这将捕获在命名参数之后传递的任何剩余参数。然后您可以在函数体内部使用 `...`,它将评估为多个值(与具有多个返回值的函数调用具有相同的规则)。
例如,一个将它的额外参数不变地传递给另一个函数的函数
> f = function (x, ...) >> x(...) >> end > f(print, "1 2 3") 1 2 3
这也是一个函数将另一个函数作为参数的例子。
要从 `...` 中获取特定项目,请使用 `select` 函数,该函数接受一个数字和可变数量的参数,并返回从该索引开始的参数。它也可以接受 `"#"` 作为索引并返回参数的数量。
> f=function(...) print(select("#", ...)) print(select(3, ...)) end > f(1, 2, 3, 4, 5) 5 3 4 5
`...` 也可以打包到一个表中
> f=function(...) tbl={...} print(tbl[2]) end > f("a", "b", "c") b
具有数组项目的表也可以“解包”到参数列表中
> f=function(...) tbl={...} print(table.unpack(tbl)) end -- it's just "unpack" (without the table.) in 5.1 > f("a", "b", "c") a b c > f("a", nil, "c") -- undefined result, may or may not be what you expect
Lua 5.2 添加了一个table.pack
函数来帮助解决这个问题,它的作用类似于{...}
,但它还添加了一个包含项目数量的“n”字段。
> f=function(...) tbl=table.pack(...) print(tbl.n, table.unpack(tbl, 1, tbl.n)) end > f("a", "b", "c") 3 a b c > f("a", nil, "c") 3 a nil c
table.unpack
的起始和结束索引参数,如果给出这些参数,它将使用这些参数,而不是从 1 开始到#tbl
结束。
虽然 Lua 允许我们像使用任何其他值一样自由地使用函数,但我们通常只想给它们一个名称(通过将它们存储在变量中)并通过该名称使用它们。Lua 有些语法糖可以使将函数存储在变量中看起来更漂亮。
function f(...) end -- is equivalent to: f = function (...) end
这种语法可以在本教程中的示例中使用,但 = 使得函数是值的这一点更加清晰。通常建议在实际脚本中使用快捷语法,除非没有理由给你的函数命名。
此外,还有类似的语法用于将函数存储在表中。
function a.b.f(...) end -- is equivalent to: a.b.f = function (...) end
递归函数是指调用自身的函数。例如
function factorial(x) if x == 1 then return 1 end return x * factorial(x-1) end
还可能存在相互递归函数,例如a
调用b
,b
又调用a
,如此反复。
这类函数的问题在于,每次调用函数时,Lua 都需要记住它是从哪里调用的,以便知道返回到哪里。此信息存储在一个称为调用栈的数据结构中,它在每次调用函数时都会增长,并在函数返回时缩小。因此,当你编写一个可以调用自身数千次的函数时,它会导致堆栈变得非常大。
尾调用是解决这个问题的方法:如果一个函数返回另一个函数调用的完全未修改的结果,Lua 就会知道它不必返回到你的函数,它可以使用当前的堆栈槽,并让调用的函数直接返回到调用你当前函数的函数。
如果以上内容令人困惑,另一种理解方式是,尾调用只是一个跳转,而不是真正的函数调用。
以下是上述函数的尾递归示例
function factorial_helper(i, acc) if i == 0 then return acc end return factorial_helper(i-1, acc*i) end function factorial(x) return factorial_helper(x, 1) end
一些关于什么是尾调用和什么不是尾调用的例子
return f(arg) -- tail call return t.f(a+b, t.x) -- tail call return 1, f() -- not a tail call, the function's results are not the only thing returned return f(), 1 -- not a tail call, the function's results are not the only thing returned return (f()) -- not a tail call, the function's possible multiple return values need to be cut down to 1 after it returns return f() + 5 -- not a tail call, the function's return value needs to be added to 5 after it returns return f().x -- not a tail call, the function's return value needs to be used in a table index expression after it returns
并非所有非尾递归函数都是不好的,如果调用深度不会很深,它通常是最自然的解决方案。但是,如果你预期数百次或更多次的迭代,你可能应该考虑使用尾递归或循环。
最后,尾调用与递归一起引入的唯一原因是,它们在递归中最为有用。但这并不意味着尾调用只在调用同一个函数时才有效,它在调用不同函数时仍然以相同的方式工作。