IT Notes‎ > ‎Programming Language‎ > ‎Lua‎ > ‎

第05章 函数(基本内容)


函数(function)是闭包(closure)的一种特殊形式,Lua closure 的内容将在下章提到(TODO补充链接)。

函数的构成:名称,参数表,函数体。

o.foo(o, x) 可以写成 o:foo(x),这种写法隐式地将 o 作为函数的第一个参数。——这个和 Lua OO 有些联系。将在未来展开 TODO

Lua 可以调用 Lua 编写的函数,也可以调用 C 编写的函数(怎么调用?TODO)

调用参数

函数在使用时,必须把参数放在函数名后的圆括号中,如果参数个数为0,就要写一对空的圆括号。但这个规则有个例外,如果函数只有一个参数,而且这个参数是字面字符串或 table,那么这对圆括号也可以不写。这也就是为什么 HelloWorld 还可以写成 print "hello, world!" 的原因。

参数分为:
  • 形参(形式参数)parameter, 函数定义时参数表中的参数
  • 实参(实际参数)argument, 调用函数时传入的参数

函数调用时,如果发生 paramenter 和 argument 个数不一致的情况,则按如下规则赋值:

  • 如果实参多于形参,则舍弃多于实参,
  • 如实参少于形参,则不足的位置初始化为 nil。
例如,定义一个函数并按上述两种方法使用:
f = function (a, b, c)
    print(tostring(a))
    print(tostring(b))
    print(tostring(c))
end

f(1, 2, 3, 4, 5) -- 实参多于形参
f("hello", "world") -- 实参少于形参
这段代码的输出是:
1
2
3
hello
world
nil
可以看出,第一个调用,把多余的实参4和5丢了,而第二个调用,用 nil 补足了第三个缺少的参数。

多值返回

Lua 中的函数相对其他语言,比较有颠覆性,它能返回多个值,这个特性甚至颠覆了函数的传统数学定义。自定义的一个多值返回函数,求数组中最大的元素的索引和值:
function getMax(t)
    local max_index = 1
    local max_value = t[1]

    for i, v in ipairs(t) do
        if v > max_value then
            max_index = i
            max_value = v
        end
    end
    return max_index, max_value
end

hello = {4543, -11, 9, 0, 34.9}

mi, m = getMax(hello)

print(mi)
print(m)
输出结果:
1
4543
其实,这个例子中还用了 Lua 内建的一个多值函数 ipairs.

返回值数量调整

在使用 Lua 多值函数时,根据调用情况不同,Lua 会调整函数的返回值数量。举例说明如下,
function foo0() end                        -- 无返回值
function foo1() return "a" end             -- 返回1个结果
function foo2() return "a", "b" end        -- 返回2个结果

如果一个函数调用是最后的(或仅有的)一个表达式,那么 Lua 会保留尽可能多的返回值,用于匹配赋值变量
x, y = foo2()                             -- x="a", y="b"
x = foo2()                                -- x="a", "b" 被丢弃
x, y, z = 10, foo2()                      -- x=10, y="a", z="b"

如果一个函数没有返回值或没有足够多的返回值,则用 nil 补齐
x, y = foo0()                            -- x=nil, y=nil
x, y = foo1()                            -- x="a", y=nil
x, y, z = foo2()                         -- x="a", y="b", z=nil

如果一个函数调用不是一系列表达式中最后一个,那么函数只返回第一个值
x, y = foo2(), 20                        -- x="a", y=20
x, y = foo0(), 20, 30                    -- x=nil, y=20, 30被丢弃

当一个函数调用作为另一个函数调用的最后一个(或仅有的)实参时,第一个函数的所有返回值都将作为实参传入第二个函数,否则只返回第一个值
print(foo0())                            --> nil(打印出的结果是空的)
print(foo1())                            --> a
print(foo2())                            --> a    b
print(foo2(), 1)                         --> a    1
print(foo2() .. "x")                     --> ax

可以将一个函数放入一对括号中,迫使它只返回一个值
print((foo2()))

unpack 接受数组作为参数,并从下标1返回该数组的所有元素,可以用 Lua 来实现 unpack
function unpack(t, i)
    i = i or 1
    if t[i] then
        return t[i], unpack(t, i+1)
    end
end

这段代码很巧妙,其中用到了 closure 技术,将在下章讲述。

变长参数

Lua 支持变长参数(variable number of arguments, also called varargs),以三个点来表示变长参数。变长参数和固定参数可混用,但固定参数需放到变长参数之前,这点和 Java 是一样的。下面的求和函数使用了变长参数:

function add(...)
    local s = 0

    for i, v in ipairs{...} do
        s = s+v
    end
    return s
end

多值恒定式函数

多值恒定函数(multi-value identity),是一个利用变长参数写的很特殊的一个函数:

function id(...) return ... end
这个函数的原理对编写调试函数很有用,如:
function foo1(...)
    print("calling foo: ", ...)
    return foo(...)
end

上面这个函数给被调试的函数 foo 在调用前加了一行日志打印。

如果传入的变长参数的实参中,有 nil,则需要 select 函数来访问变长参数。
for i=1, select("#", ...) do
    local arg = select(i, ...) -- 得到第i个参数
    <循环体>
end
另,select("#", ...) 会返回所有变长参数的总数,包括 nil

具名实参(named arguments)

多值返回时,如果值太多,使用上很不方便,此刻可以考虑把返回值封装到一个 table 中,然后返回这个 table 就可以了。如下面的例子:

fetchPersionalInfo = function ()
    local persionInfo = {}
    persionInfo.locale = "Shanghai"
    persionInfo.name = "Iridium"
    persionInfo.sex = "male"
    return persionInfo
end

print(fetchPersionalInfo().name)

如果不封装参数,就会这样子:

fetchPersionalInfo = function ()
    return "Shanghai", "Iridium", "male"
end

local locale, name, sex = fetchPersionalInfo()

print(name)

它们的输出都是:

Iridium

这种技术,其实在其他语言中也早有应用。如 Java Web 开发中,浏览器提交的表单往往有相当多的值,我们一般都封装到一个 DTO 中进行传递。又如在 C 中,如果返回值过多,往往会封装成一个结构体返回。

Comments