第四章:函数深入

函数是 Lua 程序的重要组成部分。在 Lua 中,函数是第一类值(First-Class Value),这意味着函数可以存储在变量中,可以作为参数传递给其他函数,也可以作为返回值。

本章将深入讲解函数的定义、参数传递、多返回值、可变参数以及闭包等高级特性。

4.1 函数基础

函数定义

使用 function 关键字定义函数。

-- 定义一个计算阶乘的函数
function factorial(n)
    if n == 0 then
        return 1
    else
        return n * factorial(n - 1)
    end
end

print(factorial(5)) -- 120

这实际上是以下形式的语法糖:

factorial = function(n)
    -- ...
end

函数调用

调用函数时,如果有参数,必须加上圆括号 ()。哪怕没有参数,也必须加上 ()
特例:如果函数只有一个参数,且该参数是字符串字面量表构造器,则可以省略括号。

print "Hello"      -- 等同于 print("Hello")
print {1, 2, 3}    -- 等同于 print({1, 2, 3})

参数传递

Lua 的参数传递机制类似于赋值:实参的值被赋给形参。

  • 多余的参数会被忽略。

  • 缺少的参数会被初始化为 nil

function f(a, b)
    print(a, b)
end

f(1)        -- 1   nil
f(1, 2)     -- 1   2
f(1, 2, 3)  -- 1   2

多返回值

Lua 函数非常独特的一点是支持返回多个值。只需在 return 语句后列出所有返回值即可。

function swap(x, y)
    return y, x
end

a, b = swap(10, 20)
print(a, b) -- 20  10

如果函数调用不是表达式的最后一个元素,只返回第一个值。

print(swap(10, 20), "end") -- 20  end
print(swap(10, 20))        -- 20  10

变长参数

使用 ... 表示变长参数。在函数体内,... 表现为一个包含所有额外参数的表达式。

function sum(...)
    local s = 0
    for i, v in ipairs{...} do
        s = s + v
    end
    return s
end

print(sum(1, 2, 3, 4)) -- 10

在 Lua 5.2+ 中,还可以使用 table.pack(...) 将参数打包成一个表,这对于包含 nil 的参数列表更安全(因为它包含一个 n 字段表示参数个数)。

4.2 高级函数特性

闭包 (Closure)

闭包是指一个函数加上该函数所需访问的非局部变量(Upvalue)。简单来说,闭包可以让函数访问并操作其定义时所在作用域的变量,即使该作用域已经结束。

function newCounter()
    local count = 0
    return function() -- 匿名函数
        count = count + 1
        return count
    end
end

c1 = newCounter()
print(c1()) -- 1
print(c1()) -- 2

c2 = newCounter() -- 新的闭包,拥有独立的 count
print(c2()) -- 1

在上面的例子中,newCounter 返回了一个匿名函数。虽然 newCounter 执行完毕后局部变量 count 本该销毁,但因为返回的匿名函数引用了它,所以 count 会一直存在于闭包中。

尾调用 (Tail Call)

尾调用是指函数的最后一个动作是调用另一个函数。Lua 支持尾调用优化(Tail Call Optimization),这意味着尾调用不会消耗栈空间。这使得我们可以编写无限递归函数而不会导致栈溢出。

function foo(n)
    if n > 0 then
        return foo(n - 1) -- 尾调用
    end
end

以下情况不是尾调用:

  • return foo(n) + 1 (调用后还有加法操作)

  • return (foo(n)) (被括号包裹,强制返回一个值)

函数作为参数(高阶函数)

函数可以作为参数传递给另一个函数。例如 table.sort 就接受一个比较函数。

students = {
    {name = "Alice", score = 90},
    {name = "Bob", score = 85},
    {name = "Charlie", score = 95}
}

table.sort(students, function(a, b)
    return a.score > b.score -- 按分数降序排列
end)

for _, s in ipairs(students) do
    print(s.name, s.score)
end

4.3 函数式编程初探

Lua 的灵活性使其非常适合函数式编程风格。

  • map: 对集合中的每个元素应用函数。

  • filter: 筛选符合条件的元素。

  • reduce: 将集合归约为单个值。

虽然 Lua 标准库没有直接提供这些函数,但我们可以轻松实现它们。

function map(func, t)
    local new_t = {}
    for i, v in ipairs(t) do
        new_t[i] = func(v)
    end
    return new_t
end

t = {1, 2, 3}
t2 = map(function(x) return x * 2 end, t)
-- t2 is {2, 4, 6}

练习题

  1. 编写一个函数 average(...),计算任意数量参数的平均值。

  2. 编写一个函数 power(x, n),使用递归计算 xn 次幂。

  3. 利用闭包实现一个简单的“状态机”,每次调用返回 “STATE_A”, “STATE_B”, “STATE_C” 循环切换。

  4. 重写 4.1 节的 sum 函数,使其支持直接传入一个数组作为参数(提示:检查第一个参数类型)。


下一章预告:表(Table)是 Lua 最强大的数据结构,也是唯一的复合数据结构。下一章我们将深入研究表的操作、元表(Metatable)以及如何实现面向对象编程。