Lua程序可以使用Lua编写的函数,也可以使用C语言编写的函数,调用时没区别。函数需要将所有参数放在一个圆括号内。但是如果函数只有一个参数,且参数为字面字符串或table构造式,圆括号可以省略。关于面向对象部分,Lua也提供冒号操作符,例如func.f(func, x),可以写作:func:f(x),此时,func隐式地作为函数的第一个参数。
函数以function定义,一个函数定义中包括名称,参数表和函数体,举书上的例子说明:
function add(a)
local sum = 0
for i,v in ipairs(a) do
sum = sum + v
end
return sum
end
例子中,add是函数名称,a是参数(此处只有一个参数),end之前的部分为函数体。
关于函数的参数概念,这里还是要说一下。如同C,有形参和实参的概念。形参,即形式参数,也就是函数定义的时候参数表的部分,例如上面的例子,a就是形参。实参就是实际参数,是调用函数时传入的,例如上面的例子,我要调用这个add函数,那么调用时候赋予的参数(此处为table)就是实际参数,这里省略该例。如果实参和形参的数量不同,Lua会做自动调整。原则是:实参多于形参,舍弃多余的实参;实参少于形参,多余的形参初始化为nil。我们来看例子:
function f(a,b)
return a or b
end
f(3)
f(3,4)
f(3,4,5)
--[[
f(3)的调用,实参小于形参,此时a = 3, b = nil
f(3,4)d的调用,实参和形参相等,此时a = 3, b = 4
f(3,4,5)的调用,实参大于形参,此时a = 3, b = 4, 多于的实参5被丢弃
--]]
下面,我们来看几个函数的特征:
1.多重返回值
Lua允许函数返回多个结果,这个很好理解,我们来看书上列出的例子即可理解:
function maximum(a)
local mi = 1
local m = a[mi]
for i,val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end
print(maximum({8,10,23,12,5}))
-- 返回结果:23 3
例子中我们定义了一个找最大值的函数maximum,传入实参为一个数值集组成的table,形参a接受了这个table,进入函数体的泛型for循环,记录下最大值以及最大值所在的位置。之后return返回这两个参数,因此,print接收到的是两个值,分别为最大值及其位置,输出结果。这里要注意的是,如果将函数作为表达式的一部分来调用,Lua只能保留第一个返回值,只有当一个函数调用是一系列表达式中的最后一个元素(或仅有一个元素),才能获得所有返回值。
以下代码段较长,介绍上述特点:
--定义三个函数用于做例子
function f0()
end
function f1()
return "a"
end
function f2()
return "a", "b"
end
--1.多重赋值情况
x,y = f2()
x = f2()
x,y,z = 10, f2()
x,y = f0()
x,y = f1()
x,y,z = f2()
x,y = f2(), 20
x,y = f0(), 20, 30
--上例中,若对所有结果print,可以得到:
--[[
第一个x = "a", y = "b"
第二个x = "a", b无匹配的赋值,舍弃
第三个x = 10, y = "a", z = "b"
这一组是因为f2的调用是一系列表达式中的最后一个元素,lua则会保留尽可能多的返回
--]]
--[[
第四个x = nil, y = nil
第五个x = "a", y = nil
第六个x = "a", y = "b", z = nil
这一组是因为f0没有返回值,f1有一个返回值,f2有两个返回值,都不能满足等号左边被赋值的数量,因此,lua使用nil替补不足
--]]
--[[
第七个x = "a", y = 20
第八个x = nil, y = 20, 30无匹配的赋值,舍弃
这一组由于f2不是一系列表达式中的最后一个元素,那么只能返回一个值
--]]
--2.函数调用时传入的实参列表
print(f0())
print(f1())
print(f2())
print(f2(), 1)
print(f2() .. "x")
--[[
输出结果:
第一个为空,因为f0什么也没有
第二个为a,因为f1只返回一个a,并且它是print调用时传入的实参列表中的最后一个元素,所有返回值都传入
第三个为a b,因为f2只返回一个a,并且它是print调用时传入的实参列表中的最后一个元素,所有返回值都传入
第四个为a 1,因为f2不是print调用时传入的实参列表中的最后一个元素,因此自传回一个参数a
第五个为ax,因为f2作为表达式的一部分来使用,lua只保留第一个返回值
--]]
--3.table的构造式
t0 = {f0()}
t1 = {f1()}
t2 = {f2()}
t = {f0(), f2(), 4}
--[[
t0={},为空表,因为f0为空的
t1={"a"},因为f1返回了a,并且它是table构造式的最后一个元素,因此返回所有值
t2={"a", "b"},因为f2返回了a和b,并且它是table构造式的最后一个元素,因此返回所有值
t={nil,"a",4},因为f0返回空,因此赋予nil值,f2返回a和b,但不是table构造式的最后一个元素,因此返回第一个结果a
--]]
--4.return语句
function f(i)
if i == 0 then
return f0()
elseif i == 1 then
return f1()
elseif i == 2 then
return f2()
end
end
print(f(1))
print(f(2))
print(f(0))
print(f(3))
--[[
因为f0,f1,f2都是return语句的最后一个元素,因此,打印出他们各自的返回值,由于不存在f3,因此无返回
--]]
下面来看第二个特征:
2.变长参数
即函数可以接受不同长度的实参,在函数中体现为参数表用三个点表示(…),来看例子:
function add(...)
local s = 0
for i,v in ipairs{...} do
s = s + v
end
return s
end
print(add(3,4,10,25,12))
print(add(7,19,33))
print(add(2,4,11,18,20,22,27))
--输出结果:54,59和104
上例中使用了变长参数,add在print中第一次调用,传入的5个参数都被接纳,并通过泛型for做累加运算,此时,ipairs后的三个点成了表达式,在函数体中要运用传入的参数,就需要则三个点的表达式,最后返回累加结果。add的第二次和第三次分别传入了三个参数和七个参数,结果依然计算准确。因此lua的这个特性使得函数的使用和编写非常灵活。我们应该掌握,提高效率。
这里要注意的是也可以将固定参数和变长参数混合使用,但是固定参数要在变长参数之前,来看格式化文本和输出文本组成的例子:
function fwrite(fmt, ...)
return io.write(string.format(fmt, ...))
end
fwrite()
fwrite("a")
fwrite("%d%d", 4, 5)
--[[
第一次调用,fmt = nil, 无变长参数
第二次调用,fmt = "a", 无变长参数
第三次调用,fmt = "%d%d", 变长参数为4和5
--]]
Tips:使用select遍历函数的所有变长参数(当变长参数中含有故意传入的nil时,应该使用此方式)
for i = 1, select('#', ...)
local arg = select(i, ...)
--body
end
上例中select(‘#’, …)返回所有变长参数总和(包括nil),select(i, …)返回第i个变长实参。select的调用要注意的是必须有一个固定参数(数字或#)以及变长参数。
3.具名实参
Lua中,调用一个函数时,实参通过其在参数表中的位置与形参相匹配,因此具有位置性。具名实参的意思是通过名称来指定实参,可以不在乎其位置。
看一个书上的例子,一个用于创建窗口的函数拥有大量的参数,而有些是可选的,使用具名实参。
--新建窗口所必需的参数检查函数Window
function Window(options)
if type(options.title) ~= "string" then
error("no title") --检查标题是否存在
elseif type(options.width) ~= "number" then
error("no width") --检查宽度是否存在
elseif type(options.height) ~= "number" then
error("no height") --检查高度是否存在
end
--函数_Window用于创建新窗口,位于检查函数Window中,上述检查通过后给予相应赋值
_Window(options.title, options.x or 0, options.y or 0, options.width,
options.height, options.background or "white", options.border)
end
w = Window{x = 0, y = 0, width = 300, height = 200,
title = "Lua", background = "blue",
border = true}
--经过w的调用,Window检查通过,将所有参数按参数名称赋予_Window,完成窗口创建,而不需要关注参数的顺序
该例子为展示用例,单独运行是不通过的,因为没有初始化_Window,这里只是做一个介绍。给参数命名是一种良好的习惯,便于调用,应该要掌握这个技巧。
以上就是函数部分的学习,这部分笔记到此结束,下次我们学习更深层次的内容。