目录
注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
二. 调用函数时使用的参数可以与定义函数时使用的参数个数不一致
3.1.2 当函数被作为表达式(如,加法的操作数)调用时,只保留函数的第一个返回值
3.1.3 只有当函数调用是一系列表达式中最后一个表达式(或是唯一一个表达式)时,其所有的返回值才能被获取到
4.1.1. 可以通过变长参数来模拟Lua语言中的普通的参数传递机制:
4.1.4 使用函数table.pack 检测参数中是否有nil
注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
在Lua语言中,函数(function)是对语句和表达式进行抽象的主要方式。
函数既可以完成某种特定任务,又能进行计算并返回结果。
函数调用时都需要使用一对圆括号把参数列表括起来。即使被调用的函数不需要参数,也需要一对空括号()。唯一例外就是,当函数只有一个参数且该参数是字符串常量或表构造器时,括号是可选的:
print "Hello World" <--> print("Hello World")
dofile 'a.lua' <--> dofile('a.lua')
print [[a multi-line message]] <--> print([[a multi-line message]])
f{x=10, y=20} <--> f({x=10, y=20})
type{} <--> type({})
一. 函数定义语法:
--求序列'a'中元素的求和
function add(a)
local sum = 0
for i = 1, #a do
sum = sum + a[i]
end
return sum
end
在这个语法中,一个函数定义具有一个函数名、一个参数组成的列表和由一组语句组成的函数体
二. 调用函数时使用的参数可以与定义函数时使用的参数个数不一致
Lua语言会通过抛弃多余参数和将不足的参数设为nil的方式调整参数个数
function f(a, b) print(a, b) end
--其行为如下
f() --> nil nil
f(3) --> 3 nil
f(3, 4) --> 3 4
f(3, 4, 5) --> 3 4 (5被舍弃)
三. 多返回值
Lua语言中一种与众不同且又非常有用的特性是允许一个函数返回多个结果。接触过的函数 string.find()
s, e = string.find("hello lua users", "lua")
print(s, e) --> 7 9
Lua语言编写的函数同样可以返回多个结果,在需在 return 关键字后列出所有要返回的值即可。
-- 用于查找序列中最大元素的函数可以同时返回最大值及该元素的位置
function maximun(a)
local mi = 1 --最大值的索引
local m = a[mi] -- 最大值
for i = 1, #a do
if a[i] > m then
mi = i
m = a[i]
end
end
return m, mi -- 返回最大值及其索引
end
3.1 Lua语言根据函数的被调用情况调整返回值的数量。
3.1.1 当函数被单独调用时,其所有返回值都会被丢弃
3.1.2 当函数被作为表达式(如,加法的操作数)调用时,只保留函数的第一个返回值
3.1.3 只有当函数调用是一系列表达式中最后一个表达式(或是唯一一个表达式)时,其所有的返回值才能被获取到
所谓“一系列表达式” 在Lua中表现为4种情况;
Ⅰ.多重赋值
Ⅱ.函数调用时传入的实参列表
Ⅲ. 表构造器
Ⅳ. return
分别针对这四类情况举例说明
先定义几个函数
function foo0 () end -- 不返回结果
function f001() return "a" end -- 返回1个结果
function f002() return "a", "b" end -- 返回2个结果
多重赋值:如果一个函数调用是一系列表达式中的最后(或者是唯一)一个表达式,则该函数调用将产生尽可能多的返回值以匹配待赋值变量:
-- 多重赋值给变量
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 ('b'被丢弃)
x, y = foo0(), 20, 30 -- x = nil, y = 20 (30被丢弃)
当一个函数作为另一个函数调用的最后一个(或是唯一)实参调用时,第一个函数所有返回值都会被作为实参传给第二个函数。例如, print(g())会打印出g返回的所有结果。
print(foo0()) -->(没有结果)
print(foo1()) --> a
print(foo2()) --> a b
print(foo2(), 1) -- 不满足最后一个时,只返回一个 --> a 1
print(foo2() .. "x") --> ax
表构造器会完整地接收函数调用地所有返回值,不会调整返回值的个数:
t = {foo0()} -- t= {} (一个空表)
t = {foo1()} -- t = {"a"}
t = {foo2()} -- t = {"a", "b"}
t = {foo0(), foo2(), 4} -- t[1] = nil, t[2] = "a", t[3] = 4
return f() 的语句会返回 f 返回的所有结果:
function foo(i)
if i == 0 then return foo0()
elseif i == 1 then return foo1()
elseif i == 2 then return foo2()
end
end
print(foo(1)) --> a
print(foo(2)) --> a b
print(foo(0)) --> (无结果)
print(foo(3)) --> (无结果)
-- 将函数调用用一对圆括号括起来可以强制其只返回一个结果:
print((foo0())) -- > nil
print((foo1())) -- > a
print((foo2())) -- > a
四. 可变长参数函数
function add(...)
local s = 0
for _,v in ipairs{...} do
s = s + v
end
return s
end
参数列表中的三个点(...) 表示该函数的参数是可变长的。我们将三个点组成的表达式称为可变长参数表达式,类似于一个具有多个返回值的函数,返回的是当前函数的所有可变长参数。
4.1 可变长参数的几种用法:
4.1.1. 可以通过变长参数来模拟Lua语言中的普通的参数传递机制:
function foo (a, b, c)
<==>
function foo (...)
local a, b, c = ...
4.1.2 当跟踪对某个特定的函数调用时:
function foo1(...)
print("calling foo:", ...)
return foo(...)
end
4.1.3 封装输出文本函数
function fwrite(fmt, ...)
return io.write(string.format(fmt, ...))
end
4.1.4 使用函数table.pack 检测参数中是否有nil
table.pack,保存所有参数,将其放在一个表中返回,但是这个表还有一个保存了参数个数的额外字段"n"
function nonils (...)
local arg = table.pack(...)
for i = 1, arg.n do
if arg[i] == nil then return false end
end
return true
end
print(nonils(2, 3, nil)) --> false
print(nonils(2, 3)) --> true
print(nonils()) --> true
print(nonils(nil)) --> false
4.1.5 使用函数select 遍历可变长参数。
select 总是具有一个固定的参数 selector , 以及数量可变的参数。如果selector 是数值n, 那么函数select 则返回第n个参数后的所有参数;否则,selector 应该是字符串“#”,以便函数select 返回额外参数的总数。
print(select(1, "a", "b", "c")) --> a b c
print(select(2, "a", "b", "c")) --> b c
print(select(3, "a", "b", "c")) --> c
print(select("#", "a", "b", "c")) --> 3
通常,我们在需要把返回值个数调整为1的地方使用函数select(在表达式中调用函数select), 因此可以把select(n, ...)认为是返回第n个额外参数的表达式
function add(...)
local s = 0
for i = 1, select('#', ...) do
-- 会依次打印出
-- i等于1时,select(i, ...) 返回 1, 2, 3 返回值 保留1
-- 等于2时, select(i, ...) 返回 2, 3 返回值 保留2
-- 等于3时, select(i, ...) 返回 3 返回值 保留3
-- 由于 s = s + select(i, ...) 是 函数作为表达式调用时,函数只返回第一个返回值,故只保留(1,2,3)
s = s + select(i, ...)
end
return s
end
对于参数较少的情况,第 二个版本的 add 更快,因为该版本避免了每次调用时创建一个新表。不过,对于参数较多的情况,多次带有很多参数调用函数目le ct 会超过 建表的开销,
因此第一个版本会更好(特别地,由于迭代的次数和每次迭代时传人参数的个数会随着参数的个数增长 ,因 第二个版本的时间开销是二次代价 的)
五. 函数table.unpack
该函数的参数是一个数组。返回值为数组内的所有元素::
print(table.unpack{10, 20, 30}) --> 10 20 30
a,b = table.unpack{10, 20, 30} -- a = 10, b= 20, 30被丢弃
顾名思义,函数table.unpack 与 函数table.pack 的功能相反。pack 把参数列表转换成Lua语言中一个真实的列表(一个表)。而unpack则把Lua语言中真实的列表(一个表)转换成一组返回值,进而可以被另一个函数的参数被使用。
unpack函数的重要用途之一体现在泛型调用机制上。
如果我们想通过数组a传入可变的参数来调用函数f, 那么可以写成
f(table.unpack(a))
unpack 会返回a 中所有的元素,而这些元素又被用作f的参数。
print(string.find("hello", "ll"))
可以使用如下代码动态地构造一个等价的调用:
f = string.find()
a = {"hello", "ll"}
print(f(table.unpack(a)))
通常,函数table.unpack 使用长度操作符获取返回值的个数,因而该函数只能用于序列。不过,如果有需要,也可以显式地限制返回元素的范围:
print(table.unpack({"Sun, "Mon", "Tue", "Wed"}, 2, 3))
--> Mon Tue
虽然预定义的函数unpack是用C语言编写的,但是也可以利用递归在Lua语言中实现:
function unpack(t, i, n)
i = i or 1
n = n or #t
if i <= n then
return t[i], unpack(t, i+1, n)
end
end
在第一次调用该函数时,只传入一个参数,此时i为1,n为序列长度;然后,函数返回 t[1] 及 unpack(t, 2, n) 返回的所有结果,而unpack(t, 2, n)又会返回t[2] 及 unpack(t, 3, n) 返回的所有结果,依次类推,知道处理完N 个元素为止
六. 正确的尾调用
尾调用:是被当作函数调用使用的跳转。当一个函数的最后一个动作是调用另一个函数而没有再进行其他工作时,就形成了尾调用。
function f(x) x = x + 1; return g(x) end
当函数f 调用完函数g 之后, f 不再需要进行其他的工作。这样,当被调用的函数执行结束后,程序就不再需要返回最初的调用者。因此,在尾调用之后,程序也就不许需要在调用栈中保存有关调用函数的任何信息。例如Lua语言解释器,就利用了这个特点,使得在进行尾调用时不使用任何额外的栈空间。我们就把这种实现称为 尾调用消除。
由于尾调用不会使用栈空间,所以一个程序中能够嵌套的尾调用的数量是无限的。例如,下列函数支持任意的数字作为参数:
function foo(n)
if n > 0 then return foo(n - 1) end
end
该函数永远不会发生栈溢出
如何判断一个调用是尾调用。
-- 这个是不是尾调用呢?
function f (x) g(x) end
-- 不是,问题在于,调用完g 后,f 在返回前还不得不 丢弃g 返回的所有结果,类似的,这些都不是尾调用
return g(x) + 1 -- 必须进行加法
return x or g(x) -- 必须把返回值限制为1个
return (g(x)) -- 必须把返回值限制为1个
只有,形如 return fun(args)的调用才是尾调用