Lua 中的函数

发布时间 2023-10-17 15:00:54作者: ストッキング

# Lua 中的函数

image

基础形式

function Func (arg1, arg2)
	-- TODO
end
  • 不需要在定义的时候标注形参数据类型
  • 使用 end 作为结束
  • function 前可以使用 local 修饰,表示局部函数
  • function 作为公民可以被赋值给变量或当作参数传递
  • 一个 function 可以返回多个值

使用的时候也与其他语言没什么不同,但是有两个语法糖:

  1. 当参数只有一个且为 string 或 table 则可以省略圆括号
  2. 使用 :: 语法省略第一个参数
foo "Hello" -- 等价 foo ("Hello")
foo {name="ant", age=13} -- 等价 foo ({name="ant", age=13})

-- 下面两种方式等价,但只有在调用者本身作为第一个参数被传入的时候有效
obj.start (obj, port)
obj::start (port)

多值返回的接收

既然可以返回多个值,那么就可以接收多个值。但是 Lua 中对函数的多返回值比其他语言要灵活——并不需要全部接收。Lua 会自动适配,并接收对应的值。先看一个最简单的。

return 接收函数的所有返回值

local function f1()
  return 1, 2, 3
end

local function f2()
  return f1()
end

此处 f2 return 的是 f1 的执行结果,所以 return 会接收 f1 所有返回值(3 个)——其实就是简单的把其他函数的返回值当作自己的返回值返回。

多重赋值时候的自适应

如果一个函数的执行语句出现在赋值语句当中,那么他会像 Rust 的模式匹配一样自适应。让我看个例子来说明:

local function f3()
  return 1, 2, 3
end

local a = f3()               							-- 1 a = 1
local a, b = f3()            							-- 2 a = 1 b = 2
local a, b, c = f3()         							-- 3 a = 2 b = 2 c = 3
local a, b = f3(), 10        							-- 4 a = 1 b = 10
local a, b, c = f3(), 10     							-- 5 a = 1 b = 2 c = nil
local a, b, c = 10, f3()     							-- 6 a = 10 b = 1 c = 2
local a, b, c = 10, f3(), 20 							-- 7 a = 10 b = 1 c = 20
local a, b, c, d = 10, f3(), 20           -- 8 a = 10 b = 1 c = 20 d = nil

前三个挺好理解,f3 有 3 个返回值,a, b, c 从左到右去接收就是,多出来的就被抛弃。但是从第 4 个还是就有些魔幻了,我们一个一个来看。

起始第 4 个只需要猜分为 a = f3(), b = 10 两部分来看自然就清楚了,前一个式子就变成了第 1 个式子。第 7 个同理。那么第 5、6 呢?同样能够拆开,第 5 个拆为 a = f3()b = 10c = nil 。第 6 个拆为 a = 10b, c = f3() 这样来看是不是清楚了呢?

如果还有疑惑也不要慌。我们说过,这个自适应模式与 Rust 的模式匹配很相似。第 6 个式子左边有 3 个变量用于接收值,右边却只有两个式子提供了值,再一看 f3() 可以展开,所以就让 b ,c 和 f3 展开后的式子匹配。但是第 5 个应该也是相似的情况啊?因为单个的值具有优先选择权,他会限制 f3() 的展开,所以这个地方 f3()只能匹配到a,而无法匹配 a, b

再次强调,只是相似,不是相同。也就是说这是一种理解方式,而非底层原理。

这时候再来看下面两个语句是否立刻就理解了为何第一个式子不需要使用 _ 占位符了吧?

for key in pairs(t) do end
for key, value in pairs(t) do end

作为 table 元素

local t = { 10, f2() }       -- 10 1 2
local t = { f2(), 30 }       -- 1 30
local t = { f2(), 10, f2() } -- 1 10 1 2

在 table 中,也有且只有其作为最后一个元素的时候才会获得其所有值,其余情况,函数返回值列表并不会被展开,而是仅仅获得第一个。因此我们不难知道可以使用一个 table 去接收函数的所有返回值:

local result = {f3()}

因为在空 table 中 f3() 必定会成为最后一个元素。

table.unpack

在较早的 Lua 版本中,unpack 存存放在 _G 这个全局变量中,所以不需要使用 table. 就可以直接使用。

table.unpack 函数的作用是将一个列表的值逐一返回(nil 也会返回,不同于 pairs 和 ipairs,不过并不能用于 for 循环)。

print (table.unpack ({1, 2, 3, 4, 5, 6, 7}))
a, b, c = table.unpack ({1, 2, 3})
a, b, c = table.unpack ({f3()})

次数细看的话,就会发现其实还是在用 {} 去接收 f3 的返回值,而不是直接用的 table.unpack

小总结

起始上面所说的四点总结起来就是:只有当函数执行语句处于 return 语句,多重赋值的最后一个值、table最后一个元素的时候才能获得其返回的所有值。

变长参数

变长参数使用 ... 表示,且必须作为函数形参的最后一个参数。在使用的使用可以使用 pairs,ipairs,select 三种不同的方式。

-- 如果使用 ipairs 就会在遇到 nil 的时候终止,不再遍历后续元素
local function show1(...)
  for key, value in ipairs({ ... }) do
    print(value)
  end
end

-- 如果使用 pairs 则会跳过 nil 值
local function show2(...)
  for key, value in pairs({ ... }) do
    print(value)
  end
end

-- 如果 nil 作为有效值需要使用 select 进行遍历
local function show3(...)
  local len = select("#", ...)   -- select("#", ...) 可以获取 ... 的长度,此处不需要使用 {...}
  for i = 1, len, 1 do
    local value = select(i, ...) -- select(n, ...) 可以获取 ... 的第 n 个元素
    print(value)
  end
end

show1("hello", nil, "world") -- 输出: hello
show2("hello", nil, "world") -- 输出: hello world
show3("hello", nil, "world") -- 输出: hello nil world

具名参数

Lua 中并不存在如 C++ 或 Python 那样真正的具名参数,但是可以借助 table 来实现。

local function sayHello(man)
  print(man.name .. " say hello to " .. man.sbd)
end

-- 参数传递的时候使用 table 传递
sayHello({ name = "ant", sbd = "bnt" })

就是把参数”数组“变成参数”字典“——有没有让你想起 neovim 的配置文件?是的,这常常被用于处理一些可选参数/属性。

这与其说是具名参数,说是对 table 做参数传递的应用更加贴切。

把函数放入 table 的几种方式

函数作为一等公民,自然也可以像其他类型一样轻而易举的放入到 table 当中,想当然的方式自然就有下面几种方式

lib     = {
  f1 = function() end -- 方式一
}

lib.f2  = function() end -- 方式二

f3      = function() end
lib.f3  = f3 -- 方式三

但是 Lua 也提供了一种简便的写法:

function lib.f4() end