05.20 OpenResty實戰-Lua入門-Lua函數

OpenResty實戰-Lua入門-Lua函數

在 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

在定義函數要注意幾點:

  1. 利用名字來解釋函數、變量的目的,使人通過名字就能看出來函數、變量的作用。
  2. 每個函數的長度要儘量控制在一個屏幕內,一眼可以看明白。
  3. 讓代碼自己說話,不需要註釋最好。

由於函數定義等價於變量賦值,我們也可以把函數名替換為某個 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周邊

零碎知識點


分享到:


相關文章: