函数(一)

最后更新于:2022-04-01 20:44:09

在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。下面有3个例子,分别将函数当作一条语句;当作表达式(后面两个是一类)。 ~~~ print(8*9, 9/8) --> 72 1.125 a = math.sin(3) + math.cos(10) --> a = -0.69795152101659 print(os.date()) --> Sat Mar 9 12:14:08 2013 ~~~ 函数如果带参数,那么就要用(arg1, arg2,...)括起来,如果没有参数,就写个空(),说明这是个函数调用。 特例,如果函数只有一个参数,并且参数类型是字符串或者table,那么()可以省略,如下示例: ~~~ print "Hello World" <==> print("Hello World") dofile 'a.lua' <==> dofile('a.lua') print[[a multi-line <==> print([[a multi-line message]] message]]) f{x=10, y=20} <==> f({x=10, y=20}) type{} <==> type({}) ~~~ Lua支持面向对象,操作符为冒号‘:’。o:foo(x) <==> o.foo(o, x),这个在后面会专门写一篇文章。 Lua程序可以调用C语言或者Lua实现的函数。Lua基础库中的所有函数都是用C实现的。调用一个用C实现的函数,和调用一个用Lua实现的函数,二者没有任何区别。 Lua函数的定义语法比较常规,如下示例: ~~~ function add(a) local sum = 0 for i, v in ipairs(a) do sum = sum + v end return sum end ~~~ 函数的参数跟局部变量一样,用传入的实参来初始化,多余的实参被丢弃,多余的形参初始化为nil。示例如下: ~~~ function f(a, b) return a or b end f(3) -- a=3, b=nil f(3, 4) -- a=3, b=4 f(3, 4, 5) -- a=3, b=4 (5被丢弃) ~~~ 虽然Lua可以处理这样的情况,但是不鼓励这种传入错误数量参数的函数调用,可能会使程序运行时有点小问题。不过,有些情况下,这个特性可以加以利用,例如下面示例的默认参数: ## 1.多返回值 不同于常规函数,Lua的函数可以返回多个返回值。一些Lua中预定义的函数可以返回多个返回值。例如*string.find*函数,在string中匹配一个sub-string,*string.find*返回sub-string的起始位置和结束位置。利用多赋值语句来获取函数的多个返回值。 用Lua写的函数也可以返回多个返回值,如下示例,查找array中的最大值,并返回其位置和值 Lua会根据实际情况来使函数的返回值个数适应调用处的期望。 1)如果一个函数调用作为一条语句,所有的返回值都被丢弃 2)如果一个函数调用作为一个表达式,除了3)的情况,返回值只保留第一个 3)在多赋值,返回值作为实参来调用其他函数,table中,return语句中,这4种调用场景,如果函数调用作为其最后一个表达式,那么会保留所有的返回值,然后根据实际调用需要再纠正。 示例: ~~~ -- 多赋值,函数调用是最后一个表达式 x,y = foo2() -- x="a", y="b" x = foo2() -- x="a", "b" is discarded x,y,z = 10,foo2() -- x=10, y="a", z="b" 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 is discarded -- 返回值作为实参来调用其他函数 print(foo0()) --> print(foo1()) --> a print(foo2()) --> a b print(foo2(), 1) --> a 1 print(1, foo2()) --> 1 a b print(foo2() .. "x") --> ax (see next) -- table中 t = {foo0()} -- t = {} (an empty table) t = {foo1()} -- t = {"a"} t = {foo2()} -- t = {"a", "b"} -- table中,但是函数调用不是最后一个表达式 t = {foo0(), foo2(), 4} -- t[1] = nil, t[2] = "a", t[3] = 4 -- return语句中 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)) -- (no results) print(foo(3)) -- (no results) ~~~ 用括号来强制返回值个数为一个: ~~~ print((foo0())) --> nil print((foo1())) --> a print((foo2())) --> a ~~~ 因此,括号千万别乱用,尤其是return后的值,如果用了括号,那么就只返回一个值。 函数unpack可以返回多个值,它传入一个array,然后返回array中的每一个值。 ~~~ print(unpack{10,20,30}) --> 10 20 30 a,b = unpack{10,20,30} -- a=10, b=20, 30 is discarded ~~~ unpack的一个重要用法是泛型调用,提供了比C语言中更大的灵活性。在Lua中,如果你想调用一个函数*f,*传入可变数量的参数,很简单, ~~~ f(unpack(a)) ~~~ unpack返回a中的所有值,并传给*f *作为参数,下面示例: ~~~ f = string.find a = {"hello", "ll"} print(f(unpack(a))) --> 3 4 ~~~ Lua中的unpack是用C实现的。其实我们也可以用Lua来实现它 ~~~ function unpack (t, i) i = i or 1 if t[i] then return t[i], unpack(t, i + 1) end end ~~~ ## 2. 变参 Lua中的一些函数接受可变数量的参数,例如print函数。print函数是用C来实现的,但是我们也可以用Lua来实现变参函数。下面是一个示例: ~~~ 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)) --> 54 ~~~ 参数列表中的'...'指明该函数可以接受变参。我们可以将‘...’当作一个表达式,或者一个多返回值的函数(返回当前函数的所有参数)。例如 ~~~ local a, b = ... ~~~ 用可选参数的前两个初始化局部变量a,b的值。再看下面的例子, ~~~ function foo(a, b, c) <==> function foo(...) local a, b, c = ... ~~~ ~~~ function id (...) return ... end ~~~ 上面这个函数简单地返回它所有的参数。下面的例子,说明了一个跟踪函数调用的技巧 ~~~ function foo1 (...) print("calling foo:", ...) return foo(...) end ~~~ 再看一个实用的例子。Lua提供了不同的函数来格式化文本*string.formant*和写文本*io.write*,我们可以简单地将二者合二为一。 ~~~ function fwrite (fmt, ...) return io.write(string.format(fmt, ...)) end ~~~ 注意有一个固定的参数fmt。变参函数可能含有不定数目的固定参数,后面再跟变参。Lua会将前面的实参赋值给这些固定参数,剩下的实参才能当作变参看待。下面是几个示例: ~~~ CALL PARAMETERS fwrite() -- fmt = nil, no varargs fwrite("a") -- fmt = "a", no varargs fwrite("%d%d", 4, 5) -- fmt = "%d%d", varargs = 4 and 5 ~~~ 如果想要迭代处理变参,可以用{...}来将所有的变参收集到一个table中。但是有时变参中可能含有非法的*nil*,我们可以用*select*函数。*select*函数有一个固定的参数*selector*,然后跟一系列的变参。调用的时候,如果*selector*的值为数字n,那么select函数返回变参中的第n个参数,否则*selector*的值为'#',*select*函数会返回可变参数的总数目。下面示例: ~~~ for i=1, select('#', ...) do local arg = select(i, ...) -- get i-th parameter end ~~~ 注意,select("#", ...)返回变参的数目,包括nil在内。 ## 3. 带名字的参数 Lua中函数的参数传递是基于位置的,当调用函数的时候,实参根据位置来匹配形参。但是,有的时候,根据名字来匹配更实用。例如,系统函数*os.rename*,我们会经常忘记新名字和旧名字哪个在前;为了解决这个问题,我们尝试重新定义这个函数。下面这个 ~~~ -- invalid code rename(old="temp.lua", new="temp1.lua") ~~~ 上面这个代码是非法的,Lua并不支持这样的语法。但是我们可以修改一点点,来实现相同的效果。 ~~~ function rename (arg) return os.rename(arg.old, arg.new) end ~~~ 用这种方式来传递参数是很实用的,尤其是,当函数有多个参数,并且其中一些是可有可无时。例如,用GUI库创建一个新的窗口 ~~~ w = Window{ x=0, y=0, width=300, height=200, title = "Lua", background="blue", border = true } ~~~ *Window*函数可以检查必须的参数,并且给可选参数赋予默认值等。假设*_Window*函数可以用来创建一个新窗口,但是它必须要全部的参数。那我们就可以重新定义一个*Window*函数如下: ~~~ function Window (options) -- check mandatory 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 -- everything else is optional _Window(options.title, options.x or 0, -- default value options.y or 0, -- default value options.width, options.height, options.background or "white", -- default options.border -- default is false (nil) ) end ~~~ 水平有限,如果有朋友发现错误,欢迎留言交流。
';