在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
<loop body>
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
~~~
水平有限,如果有朋友發現錯誤,歡迎留言交流。