Lua支持面向對象,操作符為冒號‘:’。o:foo(x) <==> o.foo(o, x).
Lua程序可以調用C語言或者Lua實現的函數。Lua基礎庫中的所有函數都是用C實現的。但這些細節對於lua程序員是透明的。調用一個用C實現的函數,和調用一個用Lua實現的函數,二者沒有任何區別。
函數的參數跟局部變量一樣,用傳入的實參來初始化,多余的實參被丟棄,多余的形參初始化為nil。
count=0 function incCount(n) n=n or 1 count=count+n end incCount() print(count) incCount(5) print(count)
多返回值
不同於常規函數,Lua的函數可以返回多個返回值。一些Lua中預定義的函數可以返回多個返回值。例如string.find函數,在string中匹配一個sub-string,string.find返回sub-string的起始位置和結束位置。利用多賦值語句來獲取函數的多個返回值。
s,e=string.find("hello Lua","Lua")
print(s,e)
7 9
function maximum(a) local mi=1 --index local m=a[mi] for i,val in ipairs(a) do if val>m then mi=i;m=val end end return m,mi end print(maximum({8,10,23,12,5}))
ua會根據實際情況來使函數的返回值個數適應調用處的期望。
1)如果一個函數調用作為一條語句,所有的返回值都被丟棄
2)如果一個函數調用作為一個表達式的一部分時,返回值只保留第一個
3)只有當一個函數調用是一系列表達式中的最后一個元素(或僅有一個元素)時,才能獲取它的所有返回值。這里的
“一系列表達式”在lua中表現為4中情況:
多重賦值,函數調用時傳入的實參列表,,table的構造式,return語句中,。
在多重賦值中,若一個函數調用是最后的(或僅有的)一個表達式,那么Lua會保留盡可能多的返回值,用於
匹配賦值變量。
1.
function foo0() end
function foo1() return "a" end
function foo2() return "a","b" end
x,y=foo2()
x=foo2() x="a",b被丟棄。
x,y,z=10,foo2() ->x=10,y="a",z="b"
2.
如果一個函數調用不是一系列表達式的最后一個元素,那么將只產生一個值:
x,y=foo2(),"c"
print(x,y) a,c
當一個函數調用作為另一個函數調用的最后一個(或僅有的)實參時,第一個函數的所有返回值都將作為實參傳入第二個函數。
這樣的例子已經見到很多了,如print。:
print(foo2()) --a b
print(foo2(),1) --a 1
3. table構造式可以完整接受一個函數調用的所有結果。
t={foo2()}
print(#t)
4.
最后一個情況是return語句,諸如return f()這樣的語句,將返回f的所有返回值。
也可以將一個函數調用放入一對圓括號中,從而迫使它只返回以結果:
print((foo2())) --a
請主語return語句后面的內容是不需要圓括號的,如果寫成
return (f(x))
將只返回一個值。
關於多重返回值還有介紹一個特殊函數--unpack,他接受一個數組作為參數,並從下標1開始返回該數組的所有元素:
print(unpack{10,20,30})
a,b=unpack{10,20,30} --a=10,b=20
An important use for unpack
is in a generic call mechanism泛型調用. A generic call mechanism allows you to call any function, with any arguments, dynamically. In ANSI C, for instance, there is no way to do that. You can declare a function that receives a variable number of arguments (with stdarg.h
) and you can call a variable function, using pointers to functions. However, you cannot call a function with a variable number of arguments: Each call you write in C has a fixed number of arguments and each argument has a fixed type. In Lua, if you want to call a variable function f
with variable arguments in an array a
, you simply write
f(unpack(a))
The call to unpack
returns all values in a
, which become the arguments to f
. For instance, if we execute
f = string.find a = {"hello", "ll"}
then the call f(unpack(a))
returns 3 and 4, exactly the same as the static call string.find("hello", "ll")
.
Although the predefined unpack
is written in C, we could write it also in Lua, using recursion: 使用遞歸實現unpack。
function unpack (t, i) i = i or 1 if t[i] ~= nil then return t[i], unpack(t, i + 1) end end
The first time we call it, with a single argument, i
gets 1. Then the function returns t[1]
followed by all results from unpack(t, 2)
, which in turn returns t[2]
followed by all results from unpack(t, 3)
, and so on, until the last non-nil element.
變參
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,5))
參數中的3個點(...)表示該函數可接受不同數量的實參,調用時3個點代表了實參。
表達式"..."的行為類似於一個具有多重返回值的函數,他返回的是當前函數的所有變長參數:
local a,b=...
上例用第一個和第二個變長參數來初始化這兩個局部變量,實際上,還可以通過變長參數來模擬Lua中的
普通的參數傳遞機制。
如:
function foo(a,b,c)
等價於
function foo(...)
local a,b,c=...
end
對於那些喜愛Perl參數傳遞機制的人來說,可能會傾向於第二種形式。
如有這樣一個函數:
function id(...)
return ...
end
他只是簡單的返回所有市場。這是一個“多值恆定式(multi-value identity)"函數。
下面的這個函數行為非常類似於直接調用foo,但在調用foo()前先調用print:
function foo(...)
print("calling foo:",...)
return foo(...)
end
這種技巧對於跟蹤某個特定函數調用很有幫助。
具有參數named argu
(lua函數調用特殊語法,當實參只有一個table時,可以省略圓括號)。
w = Window{ x=0, y=0, width=300, height=200,
title = "Lua", background="blue",
border = true
}
indow函數可以檢查必須的參數,並且給可選參數賦予默認值等。假設_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
二、深入函數:
在Lua中有一個容易混淆的概念是,函數與所有其他值一樣都是匿名的,即他們都沒有名稱。當討論一個函數名時(例如print)。實際上是在討論持有某函數的變量,這與其他變量持有各種值一個道理,下面這個實例足以說明
a = { p = print }
a.p("Hello World")
b = print
b("Hello World")
function foo(x) return 2 * x end
這只是一種所謂的”語法糖“而已,實際等價於:
foo = function(x) return 2 * x end
因此,一個函數定義實際上就是一條語句(更准備說是賦值語句)。這條語句創建了一種類型為”函數“的值。可以將表達式"function(x)<body> end"視為一種函數的構造式,就像table的構造式{}一樣。將這種函數構造式的結果稱為一個”匿名函數”。
下面的示例顯示了匿名函數的方便性,它的使用方式有些類似於Java中的匿名類,如:
table.sort(test_table,function(a,b) return (a.name > b.name) end)
像sort這樣的函數,接受另一個參數作為實參的,稱其是一個”高階函數“,高階函數是一種 強大的編程機制,應用匿名函數來創建高階函數所需的實參則可以帶來更大的靈活性。但請記住,高階函數並沒有什么特權。Lua強調將函數視為”first-class valeu",所以,高階函數只是基於該觀點的應用體現而已。
1. closure(閉合函數):閉包
若將一個函數寫在另一個函數之內,那么這個位於內部的函數便可以訪問外部函數中的局部變量,見如下示例:
function newCounter() local i = 0 return function() --匿名函數 i = i + 1 return i end end c1 = newCounter() print("The return value of first call is " .. c1()) print("The return value of second call is " .. c1()) --輸出結果為: --The return value of first call is 1 --The return value of second call is 2
簡單來講,一個closure就是一個函數加上該函數所需訪問的所有“非局部變量”。
在上面的示例中,我們將newCounter()函數稱為閉包函數。其函數體內的局部變量i被稱為"非局部變量",和普通局部變量不同的是該變量被newCounter函數體內的匿名函數訪問並操作。再有就是在函數newCounter返回后,其值仍然被保留並可用於下一次計算。再看一下下面的調用方式。
在上面的示例中,我們將newCounter()函數稱為閉包函數。其函數體內的局部變量i被稱為"非局部變量",和普通局部變量不同的是該變量被newCounter函數體內的匿名函數訪問並操作。再有就是在函數newCounter返回后,其值仍然被保留並可用於下一次計算。再看一下下面的調用方式。
function newCounter() local i = 0 return function() --匿名函數 i = i + 1 return i end end c1 = newCounter() c2 = newCounter() print("The return value of first call with c1 is " .. c1()) print("The return value of first call with c2 is " .. c2()) print("The return value of second call with c1 is " .. c1()) --輸出結果為: --The return value of first call with c1 is 1 --The return value of first call with c2 is 1 --The return value of second call with c1 is 2
由此可以推出,Lua每次在給新的閉包變量賦值時,都會讓不同的閉包變量擁有獨立的"非局部變量"。下面的示例將給出基於閉包的更為通用性的用法:
do --這里將原有的文件打開函數賦值給"私有變量"oldOpen,該變量在塊外無法訪問。 local oldOpen = io.open --新增一個匿名函數,用於判斷本次文件打開操作的合法性。 local access_OK = function(filename,mode) <檢查訪問權限> end --將原有的io.open函數變量指向新的函數,同時在新函數中調用老函數以完成真正的打開操作。 io.open = function(filename,mode) if access_OK(filename,mode) then return oldOpen(filename,mode) else return nil,"Access denied" end end end
這個示例的精彩之處在於,經過重新定義后,一個程序就只能通過新的受限版本來調用原來那個未受限的open函數了。示例將原來不安全的版本保存到closure的一個私有變量中。從而使得外部再也無法直接訪問原來的版本了。
2. 非全局函數:
從上一小節中可以看出,Lua中的函數不僅可以直接賦值給全局變量,同時也可以賦值給其他類型的變量,如局部變量和table中的字段等。事實上,Lua庫中大多數table都帶有函數,如io.read、math.sin等。這種寫法有些類似於C++中的結構體。如:
Lib = {}
Lib.add = function(x,y) return x + y end
Lib.sub = function(x,y) return x - y end
或者是在table的構造式中直接初始化,如:
Lib = { add = function(x,y) return x + y end,
sub = function(x,y) return x - y end
}
除此之外,Lua還提供另外一種語法來定義此類函數,如:
Lib = {}
function Lib.add(x,y) return x + y end
function Lib.sub(x,y) return x - y end
對於Lua中的局部函數,其語義在理解上也是非常簡單的。由於Lua中都是以程序塊作為執行單元,因此程序塊內的局部函數在程序塊外是無法訪問的,如:
do
2 local f = function(x,y) return x + y end
3 --do something with f.
4 f(4,5)
5 end
對於這種局部函數,Lua還提供另外一種更為簡潔的定義方式,如:
local function f(x,y) return x + y end
該寫法等價於:
local f
f = function(x,y) return x + y end
3. 正確的尾調用:
在Lua中支持這樣一種函數調用的優化,即“尾調用消除”。我們可以將這種函數調用方式視為goto語句,如:
function f(x) return g(x) end
由於g(x)函數是f(x)函數的最后一條語句,在函數g返回之后,f()函數將沒有任何指令需要被執行,因此在函數g()返回時,可以直接返回到f()函數的調用點。由此可見,Lua解釋器一旦發現g()函數是f()函數的尾調用,那么在調用g()時將不會產生因函數調用而引起的棧開銷。這里需要強調的是,尾調用函數一定是其調用函數的最后一條語句,否則Lua不會進行優化。然而事實上,我們在很多看似是尾調用的場景中,實際上並不是真正的尾調用,如:
function f(x) g(x) end --沒有return語句的明確提示
function f(x) return g(x) + 1 --在g()函數返回之后仍需執行一次加一的指令。
function f(x) return x or g(x) --如果g()函數返回多個值,該操作會強制要求g()函數只返回一個值。
function f(x) return (g(x)) --原因同上。
在Lua中,只有"return <func>(<args>)"形式才是標准的尾調用,至於參數中(args)是否包含表達式,由於表達式的執行是在函數調用之前完成的,因此不會影響該函數成為尾調用函數。
在之前提到了,一條“尾調用”就好比是一條“goto語句”。因此在Lua中”尾調用“的一大應用就是編寫”狀態機(state machine)"。這種程序通常以一個函數來表示一個的狀態,改變狀態就是goto到另一個特定的函數。舉一個簡單的迷宮游戲的例子來說明這個問題。例如,一個迷宮有幾間房間,每間房間中最多有東南西北4扇門。用戶在每一步移動中都需要輸入一個移動的方向。如果在某個方向上有門,那么用戶可以進入相應的房間;不然,程序就打印一條警告。游戲目標是讓用戶從最初的房間走到最終的房間。
這個游戲就是一種典型的狀態機,其中當前房間是一種狀態。可以將迷宮中的每間房間實現為一個函數,並使用”尾調用“來實現從一間房間移動到另一間房間。在以下代碼中,實現一個具有4間房間的迷宮:
function room1 () local move = io.read() if move == "south" then return room3() elseif move == "east" then return room2() else print("invalid move") return room1() -- stay in the same room end end function room2 () local move = io.read() if move == "south" then return room4() elseif move == "west" then return room1() else print("invalid move") return room2() end end function room3 () local move = io.read() if move == "north" then return room1() elseif move == "east" then return room4() else print("invalid move") return room3() end end function room4 () print("congratulations!") end
通過調用初始房間來開始這個游戲:
room1()
若沒有”尾調用消除“的話,每次用戶的移動都會創建一個新的stack,移動若干步之后就有可能導致棧溢出。而”尾調用消除“則對用戶移動的次數沒有任何限制。這是因為每次移動實際上都只是完成一條goto語句到另一個函數,而非傳統的函數調用。
對於這個簡單的游戲而言,或許會覺得將程序設計為數據驅動的會更好一點,其中將房間和移動記錄在一些table中。不過,如果游戲中的每間房間都有各自特殊情況的話,采用這種狀態機的設計則更為合適。
轉自;programming in lua.