函式教學 |
|
函式以 function
關鍵字建立,如下所示:
function
(
args )
body end
下例顯示一個簡單的函式,接收單一參數並回傳其兩倍的值
> foo = function (n) return n*2 end > = foo(7) 14
參數 (也稱為 變數) 指定在 ( )
部分中,而值會使用 return
關鍵字從函式中回傳。沒有 return
,函式將不會回傳任何值。
請注意,在以上範例中,我們並沒有真正「命名」這個函式,我們僅將其指定給一個變數。這是因為 Lua 中的函式是規律性值 (例數字、字串、表格等),而且你可以使用任何可以用來操作其他值的動作來操作它們。這與你可能了解的許多其他語言 (例如 C) 有很大的不同,其中函式在編譯時具有固定的永久名稱,而且不能像值一樣被操作。
function
區塊是一個表達式 (就和「1 + 2」是一個表達式一樣),會評估為一個新的函式值。函式值可以使用 ( )
函式執行函式中的程式碼。( )
對會在函式表達式後面,可以包含以逗號分隔的參數清單 (選擇性)。
這代表 Lua 函式被認為是 匿名的 (沒有預設名稱) 和 一級的 (不受不同於其他值的對待)。
還要記得的一件事是,函式與表格類似,是透過參考傳遞。例如,當你指定包含函式的變數到其他變數時,你只是為相同的函式建立一個新的「處理程式」。
函式可以接受 0 或更多參數。這些是呼叫函式時提供給函式的值,而函式中儲存的程式碼可以使用這些值。在函式中,變數看起來就像變數,只不過它們只存在於函式中。
展示參數如何運作以及如何將它們傳遞至函式的範例
> f = function (op, a, b) >> if op == 'add' then >> return a + b >> elseif op == 'sub' then >> return a - b >> end >> error("invalid operation") >> end > g = function (value) >> print(value) >> end > = f('add', 1, 2) -- args are given inside (), separated by commas. 3 > = f('add', 1, 2, 123) -- extra args are ignored 3 > = f('add', 1) -- missing args aren't an error, instead they will be filled with nil, which might cause an error in the function's code stdin:1: attempt to perform arithmetic on local 'b' (a nil value) > = g() -- to call a function with no args, use () nil > = g "example" -- the () can be omitted if you have one quoted string arg example > = g {} -- same with one table constructor table: 0x820ee0
函式也可以使用 return
關鍵字將值回傳給呼叫它們的程式碼。那個值會成為函式呼叫表達式的值。Lua 的獨特功能是函式可以回傳任意數量的值。在大部分的語言中,函式總是回傳一個值。若要使用這項功能,請在 return
關鍵字後放置以逗號分隔的值
> f = function () >> return "x", "y", "z" -- return 3 values >> end > a, b, c, d = f() -- assign the 3 values to 4 variables. the 4th variable will be filled with nil > = a, b, c, d x y z nil > a, b = (f()) -- wrapping a function call in () discards multiple return values > = a, b x, nil > = "w"..f() -- using a function call as a sub-expression discards multiple returns wx > print(f(), "w") -- same when used as the arg for another function call... x w > print("w", f()) -- ...except when it's the last arg w x y z > print("w", (f())) -- wrapping in () also works here like it does with = w x > t = {f()} -- multiple returns can be stored in a table > = t[1], t[2], t[3] x y z
一件要記住的最後範例 ( {f()}
) 是如果函數傳回 nil,由於表單中的 nil
被認為是「沒值」,#
算子不能可靠地用於取得值的數量,因為如果一個陣列有「洞」會是未定義的。
如果你習慣了使用(像 Python)語言,它會透過儲存在「元組」類型來傳回多個值,這不是 Lua 的運作方式。Lua 函數實際上傳回個別值,而不是單一容器。
function f(switch) if not switch then --if switch is nil, function f() will not complete anything else below Return return end print("Hello") end f()--doesn't print anything
function f(switch) if not switch then --switch is no longer nil but is instead "1" return end print("Hello") end f(1)--prints "hello"
將函數作為參數或使用它們作回傳值是一個有用的功能,因為它讓你將自己的指令插入到現有的程式碼中。一個好的範例是 table.sort
,它可以選擇性地使用自訂的「小於」函數
> list = {{3}, {5}, {2}, {-1}} > table.sort(list) attempt to compare two table values stack traceback: [C]: in function 'sort' stdin:1: in main chunk [C]: in ? > table.sort(list, function (a, b) return a[1] < b[1] end) > for i,v in ipairs(list) do print(v[1]) end -1 2 3 5
函數可以在其引數清單的末尾放置 ...
。這將擷取命名後的剩餘參數。然後你可以在函數的內文中使用 ...
,它將評估多個值(與有多重回傳值的函數呼叫相同規則)。
例如,一個函數不變地將其多餘的參數傳遞給另一個函數
> f = function (x, ...) >> x(...) >> end > f(print, "1 2 3") 1 2 3
這也是函數將另一個函數作為參數示例。
若要從 ...
取得特定項目,請使用 select
函數,它會取得一個數字和可變數量的參數,並從該索引開始傳回參數。它也可以將 "#"
作為索引,並傳回參數的數量
> f=function(...) print(select("#", ...)) print(select(3, ...)) end > f(1, 2, 3, 4, 5) 5 3 4 5
...
也可以打包成一個表單
> f=function(...) tbl={...} print(tbl[2]) end > f("a", "b", "c") b
也可將具有陣列項目的表單「解壓縮」至參數清單
> f=function(...) tbl={...} print(table.unpack(tbl)) end -- it's just "unpack" (without the table.) in 5.1 > f("a", "b", "c") a b c > f("a", nil, "c") -- undefined result, may or may not be what you expect
#
算子(table.unpack
在內部使用它),因為如果陣列有 nil「洞」,它是不明確的。即使迴圈表單,以找到擁有最大金鑰的項目,仍然無法取得真正的長度,如果 nil 是傳給函數的最後參數。Lua 5.2 新增了一個 table.pack
函數來協助解決此問題,其作用方式類似於 {...}
,但它會新增一個包含項目數量的「n」欄位
> f=function(...) tbl=table.pack(...) print(tbl.n, table.unpack(tbl, 1, tbl.n)) end > f("a", "b", "c") 3 a b c > f("a", nil, "c") 3 a nil c
table.unpack
的開始和結束索引參數,如果給定這些參數,它將使用它們,而不是從 1 開始在 #tbl
結束。儘管 Lua 讓我們自由地使用函數就像使用其他值一樣,但我們通常只會想給它們一個名稱(透過將它們儲存在變數中),並依該名稱使用它們。Lua 有一些語法糖,可讓儲存在變數中的函數看起來更漂亮
function f(...) end -- is equivalent to: f = function (...) end
這種語法本可以用本教程範例說明,但 = 符號讓變數為值的函式更加清楚。一般建議在實際指令碼中使用快捷語法,除非沒有要為函式命名之理由。
此外,也有類似的語法可將函式儲存在資料表中
function a.b.f(...) end -- is equivalent to: a.b.f = function (...) end
遞迴函式是指呼叫自身函式的函式。例如
function factorial(x) if x == 1 then return 1 end return x * factorial(x-1) end
也可有相互遞迴函式,例如 a
呼叫 b
,b
再呼叫 a
,如此重複執行。
此類函式會有個問題,每次呼叫函式時,Lua 都必須記錄呼叫來源,才能知道返回位置。此資訊會儲存在稱為呼叫堆疊的資料結構,每呼叫一次函式就增加內容,每返回一次函式就減少內容。因此,當撰寫函式可以遞迴呼叫自身數千次時,會使堆疊大量增加。
解決方法是尾呼叫:假設函式回傳另一函式呼叫的精確、未變更結果,Lua 就知道不必再返回函式,可以重複使用目前堆疊槽,並讓所呼叫的函式直接返回呼叫目前函式的函式。
如果以上說明讓人困惑,也可採用另一種想法:尾呼叫只是跳躍,而非實際函式呼叫。
以下是以上函式的尾端遞迴範例
function factorial_helper(i, acc) if i == 0 then return acc end return factorial_helper(i-1, acc*i) end function factorial(x) return factorial_helper(x, 1) end
以下是何謂尾呼叫與非尾呼叫的一些範例
return f(arg) -- tail call return t.f(a+b, t.x) -- tail call return 1, f() -- not a tail call, the function's results are not the only thing returned return f(), 1 -- not a tail call, the function's results are not the only thing returned return (f()) -- not a tail call, the function's possible multiple return values need to be cut down to 1 after it returns return f() + 5 -- not a tail call, the function's return value needs to be added to 5 after it returns return f().x -- not a tail call, the function's return value needs to be used in a table index expression after it returns
並非所有非尾呼叫的遞迴函式都不好,如果呼叫次數不會過多,這通常是最自然的方法。但假設會執行數百次以上的反覆執行時,應改考慮使用尾端遞迴,或直接使用迴圈。
最後,引進尾呼叫時,唯一理由是基於遞迴,因為這是最常會使用到的時候。但這並不表示尾呼叫僅限於呼叫同一個函式時使用,呼叫不同函式時依然有效。