在 Lua 中,函數是一種對語句和表達式進行抽象的主要機制。函數既可以完成某項特定的任務,也可以只做一些計算並返回結果。
在第一種情況中,一句函數調用被視為一條語句;而在第二種情況中,則將其視為一句表達式。
示例代碼:
print("hello world!") -- 用 print() 函數輸出 hello world!
local m = math.max(1, 5) -- 調用數學庫函數 max,
-- 用來求 1,5 中的最大值,並返回賦給變量 m
使用函數的好處:
1. 降低程序的複雜性:把函數作為一個獨立的模塊,寫完函數後,只關心它的功能,而不再考慮函數里面的細節。
2. 增加程序的可讀性:當我們調用 math.max() 函數時,很明顯函數是用於求最大值的,實現細節就不關心了。
3. 避免重複代碼:當程序中有相同的代碼部分時,可以把這部分寫成一個函數,通過調用函數來實現這部分代碼的功能,節約空間,減少代碼長度。
4. 隱含局部變量:在函數中使用局部變量,變量的作用範圍不會超出函數,這樣它就不會給外界帶來干擾。
函數定義
Lua 使用關鍵字 function 定義函數,語法如下:
function function_name (arc) -- arc 表示參數列表,函數的參數列表可以為空
-- body
end
上面的語法定義了一個全局函數,名為 function_name . 全局函數本質上就是函數類型的值賦給了一個全局變量,即上面的語法等價於
function_name = function (arc)
-- body
end
由於全局變量一般會汙染全局名字空間,同時也有性能損耗(即查詢全局環境表的開銷),因此我們應當儘量使用“局部函數”,其記法是類似的,只是開頭加上 local 修飾符:
local function function_name (arc)
-- body
end
由於函數定義本質上就是變量賦值,而變量的定義總是應放置在變量使用之前,所以函數的定義也需要放置在函數調用之前。
示例代碼:
local function max(a, b) --定義函數 max,用來求兩個數的最大值,並返回
local temp = nil --使用局部變量 temp,保存最大值
if(a > b) then
temp = a
else
temp = b
end
return temp --返回最大值
end
local m = max(-12, 20) --調用函數 max,找去 -12 和 20 中的最大值
print(m) --> output 20
如果參數列表為空,必須使用 () 表明是函數調用。
示例代碼:
local function func() --形參為空
print("no parameter")
end
func() --函數調用,圓擴號不能省
--> output:
no parameter
在定義函數要注意幾點:
- 利用名字來解釋函數、變量的目的,使人通過名字就能看出來函數、變量的作用。
- 每個函數的長度要儘量控制在一個屏幕內,一眼可以看明白。
- 讓代碼自己說話,不需要註釋最好。
由於函數定義等價於變量賦值,我們也可以把函數名替換為某個 Lua 表的某個字段,例如
function foo.bar(a, b, c)
-- body ...
end
此時我們是把一個函數類型的值賦給了 foo 表的 bar 字段。換言之,上面的定義等價於
foo.bar = function (a, b, c)
print(a, b, c)
end
對於此種形式的函數定義,不能再使用 local 修飾符了,因為不存在定義新的局部變量了。
函數的參數
按值傳遞
Lua 函數的參數大部分是按值傳遞的。值傳遞就是調用函數時,實參把它的值通過賦值運算傳遞給形參,然後形參的改變和實參就沒有關係了。在這個過程中,實參是通過它在參數表中的位置與形參匹配起來的。
示例代碼:
local function swap(a, b) --定義函數swap,函數內部進行交換兩個變量的值
local temp = a
a = b
b = temp
print(a, b)
end
local x = "hello"
local y = 20
print(x, y)
swap(x, y) --調用swap函數
print(x, y) --調用swap函數後,x和y的值並沒有交換
-->output
hello 20
20 hello
hello 20
在調用函數的時候,若形參個數和實參個數不同時,Lua 會自動調整實參個數。調整規則:
若實參個數大於形參個數,從左向右,多餘的實參被忽略;若實參個數小於形參個數,從左向右,沒有被實參初始化的形參會被初始化為 nil。
示例代碼:
local function fun1(a, b) --兩個形參,多餘的實參被忽略掉
print(a, b)
end
local function fun2(a, b, c, d) --四個形參,沒有被實參初始化的形參,用nil初始化
print(a, b, c, d)
end
local x = 1
local y = 2
local z = 3
fun1(x, y, z) -- z被函數fun1忽略掉了,參數變成 x, y
fun2(x, y, z) -- 後面自動加上一個nil,參數變成 x, y, z, nil
-->output
1 2
1 2 3 nil
變長參數
上面函數的參數都是固定的,其實 Lua 還支持變長參數。若形參為 ... , 表示該函數可以接收不同長度的參數。訪問參數的時候也要使用 ... 。
示例代碼:
local function func( ... ) -- 形參為 ... ,表示函數採用變長參數
local temp = {...} -- 訪問的時候也要使用 ...
local ans = table.concat(temp, " ") -- 使用 table.concat 庫函數對數
-- 組內容使用 " " 拼接成字符串。
print(ans)
end
func(1, 2) -- 傳遞了兩個參數
func(1, 2, 3, 4) -- 傳遞了四個參數
-->output
1 2
1 2 3 4
值得一提的是,LuaJIT 2 尚不能 JIT 編譯這種變長參數的用法,只能解釋執行。所以對性能敏感的代碼,應當避免使用此種形式。
具名參數
Lua 還支持通過名稱來指定實參,這時候要把所有的實參組織到一個 table 中,並將這個table 作為唯一的實參傳給函數。
示例代碼:
local function change(arg) -- change 函數,改變長方形的長和寬,使其各增長一倍
arg.width = arg.width * 2
arg.height = arg.height * 2
return arg
end
local rectangle = { width = 20, height = 15 }
print("before change:", "width =", rectangle.width,
"height =", rectangle.height)
rectangle = change(rectangle)
print("after change:", "width =", rectangle.width,
"height =", rectangle.height)
-->output
before change: width = 20 height = 15
after change: width = 40 height = 30
按引用傳遞
當函數參數是 table 類型時,傳遞進來的是 實際參數的引用,此時在函數內部對該 table 所做的修改,會直接對調用者所傳遞的實際參數生效,而無需自己返回結果和讓調用者進行賦值。 我們把上面改變長方形長和寬的例子修改一下。
示例代碼:
function change(arg) --change函數,改變長方形的長和寬,使其各增長一倍
arg.width = arg.width * 2 --表arg不是表rectangle的拷貝,他們是同一個表
arg.height = arg.height * 2
end -- 沒有return語句了
local rectangle = { width = 20, height = 15 }
print("before change:", "width = ", rectangle.width,
" height = ", rectangle.height)
change(rectangle)
print("after change:", "width = ", rectangle.width,
" height =", rectangle.height)
--> output
before change: width = 20 height = 15
after change: width = 40 height = 30
在常用基本類型中,除了 table 是按址傳遞類型外,其它的都是按值傳遞參數。 用全局變量來代替函數參數的不好編程習慣應該被抵制,良好的編程習慣應該是減少全局變量的使用。
函數返回值
Lua 具有一項與眾不同的特性,允許函數返回多個值。Lua 的庫函數中,有一些就是返回多個值。
示例代碼:使用庫函數 string.find ,在源字符串中查找目標字符串,若查找成功,則返回目標字符串在源字符串中的起始位置和結束位置的下標。
local s, e = string.find("hello world", "llo")
print(s, e) -->output 3 5
返回多個值時,值之間用“,”隔開。
示例代碼:定義一個函數,實現兩個變量交換值
local function swap(a, b) -- 定義函數 swap,實現兩個變量交換值
return b, a -- 按相反順序返回變量的值
end
local x = 1
local y = 20
x, y = swap(x, y) -- 調用 swap 函數
print(x, y) --> output 20 1
當函數返回值的個數和接收返回值的變量的個數不一致時,Lua 也會自動調整參數個數。
調整規則: 若返回值個數大於接收變量的個數,多餘的返回值會被忽略掉; 若返回值個數小於參數個數,從左向右,沒有被返回值初始化的變量會被初始化為 nil。
示例代碼:
function init() --init 函數 返回兩個值 1 和 "lua"
return 1, "lua"
end
x = init()
print(x)
x, y, z = init()
print(x, y, z)
--output
1
1 lua nil
當一個函數有一個以上返回值,且函數調用不是一個列表表達式的最後一個元素,那麼函數調用只會產生一個返回值, 也就是第一個返回值。
示例代碼:
local function init() -- init 函數 返回兩個值 1 和 "lua"
return 1, "lua"
end
local x, y, z = init(), 2 -- init 函數的位置不在最後,此時只返回 1
print(x, y, z) -->output 1 2 nil
local a, b, c = 2, init() -- init 函數的位置在最後,此時返回 1 和 "lua"
print(a, b, c) -->output 2 1 lua
函數調用的實參列表也是一個列表表達式。考慮下面的例子:
local function init()
return 1, "lua"
end
print(init(), 2) -->output 1 2
print(2, init()) -->output 2 1 lua
如果你確保只取函數返回值的第一個值,可以使用括號運算符,例如
local function init()
return 1, "lua"
end
print((init()), 2) -->output 1 2
print(2, (init())) -->output 2 1
值得一提的是,如果實參列表中某個函數會返回多個值,同時調用者又沒有顯式地使用括號運算符來篩選和過濾,則這樣的表達式是不能被 LuaJIT 2 所 JIT 編譯的,而只能被解釋執行。
全動態函數調用
調用回調函數,並把一個數組參數作為回調函數的參數。
local args = {...} or {}
method_name(unpack(args, 1, table.maxn(args)))
使用場景
如果你的實參 table 中確定沒有 nil 空洞,則可以簡化為method_name(unpack(args))
1. 你要調用的函數參數是未知的;
2. 函數的實際參數的類型和數目也都是未知的。
偽代碼
add_task(end_time, callback, params)
if os.time() >= endTime then
callback(unpack(params, 1, table.maxn(params)))
end
值得一提的是, unpack 內建函數還不能為 LuaJIT 所 JIT 編譯,因此這種用法總是會被解釋執行。對性能敏感的代碼路徑應避免這種用法。
小試牛刀
local function run(x, y)
print('run', x, y)
end
local function attack(targetId)
print('targetId', targetId)
end
local function do_action(method, ...)
local args = {...} or {}
method(unpack(args, 1, table.maxn(args)))
end
do_action(run, 1, 2) -- output: run 1 2
do_action(attack, 1111) -- output: targetId 111
至此,Lua函數就介紹完了,下一篇將介紹Lua模塊。
後續計劃內容:
Lua入門+高階
Nginx
OpenResty
LuaRestyRedisLibrary
LuaCjsonLibrary
PostgresNginxModule
LuaNginxModule
LuaRestyDNSLibrary
LuaRestyLock
stream_lua_module
balancer_by_lua
OpenResty 與 SSL
測試
Web服務
火焰圖
OpenResty周邊
零碎知識點
閱讀更多 互聯網技能圖譜 的文章