【正文】
n=0}g(3, 4) a=3, b=4, arg={n=0}g(3, 4, 5, 8) a=3, b=4, arg={5, 8。 n=2}如上面所示,Lua會(huì)將前面的實(shí)參傳給函數(shù)的固定參數(shù),后面的實(shí)參放在arg表中。舉個(gè)具體的例子。一個(gè)典型的方法是使用啞元(dummy variable,下劃線):local _, x = (s, p) now use `x39。...還可以利用可變參數(shù)聲明一個(gè)select函數(shù):function select (n, ...) return arg[n]endprint((hello hello, hel)) 6 9print(select(1, (hello hello, hel))) 6print(select(2, (hello hello, hel))) 9有時(shí)候需要將函數(shù)的可變參數(shù)傳遞給另外的函數(shù)調(diào)用,可以使用前面我們說(shuō)過(guò)的unpack(arg)返回arg表所有的可變參數(shù),(類(lèi)似C語(yǔ)言的sprintf函數(shù)):function fwrite(fmt, ...) return ((fmt, unpack(arg)))end這個(gè)例子將文本格式化操作和寫(xiě)操作組合為一個(gè)函數(shù)。 命名參數(shù)Lua的函數(shù)參數(shù)是和位置相關(guān)的,調(diào)用時(shí)實(shí)參會(huì)按順序依次傳給形參。有時(shí)候用名字指定參數(shù)是很有用的,比如rename函數(shù)用來(lái)給一個(gè)文件重命名,有時(shí)候我們我們記不清命名前后兩個(gè)參數(shù)的順序了: invalid coderename(old=, new=)上面這段代碼是無(wú)效的,Lua可以通過(guò)將所有的參數(shù)放在一個(gè)表中,把表作為函數(shù)的唯一參數(shù)來(lái)實(shí)現(xiàn)上面這段偽代碼的功能。因?yàn)長(zhǎng)ua語(yǔ)法支持函數(shù)調(diào)用時(shí)實(shí)參可以是表的構(gòu)造。rename{old=, new=}根據(jù)這個(gè)想法我們重定義了rename:function rename (arg) return (, )end當(dāng)函數(shù)的參數(shù)很多的時(shí)候,這種函數(shù)參數(shù)的傳遞方式很方便的。例如GUI庫(kù)中創(chuàng)建窗體的函數(shù)有很多參數(shù)并且大部分參數(shù)是可選的,可以用下面這種方式:w = Window { x=0, y=0, width=300, height=200, title = Lua, background=blue, border = true}function Window (options) check mandatory options if type() ~= string then error(no title) elseif type() ~= number then error(no width) elseif type() ~= number then error(no height) end everything else is optional _Window(, or 0, default value or 0, default value , , or white, default default is false (nil) )end第6章 再論函數(shù)Lua中的函數(shù)是帶有詞法定界(lexical scoping)的第一類(lèi)值(firstclass values)。第一類(lèi)值指:在Lua中函數(shù)和其他值(數(shù)值、字符串)一樣,函數(shù)可以被存放在變量中,也可以存放在表中,可以作為函數(shù)的參數(shù),還可以作為函數(shù)的返回值。詞法定界指:嵌套的函數(shù)可以訪問(wèn)他外部函數(shù)中的變量。這一特性給Lua提供了強(qiáng)大的編程能力。Lua中關(guān)于函數(shù)稍微難以理解的是函數(shù)也可以沒(méi)有名字,匿名的。當(dāng)我們提到函數(shù)名(比如print),實(shí)際上是說(shuō)一個(gè)指向函數(shù)的變量,像持有其他類(lèi)型值的變量一樣:a = {p = print}(Hello World) Hello Worldprint = `print39。 now refers to the sine function(print(1)) sin = `sin39。 now refers to the print functionsin(10, 20) 10 20既然函數(shù)是值,那么表達(dá)式也可以創(chuàng)建函數(shù)了,Lua中我們經(jīng)常這樣寫(xiě):function foo (x) return 2*x end這實(shí)際上是Lua語(yǔ)法的特例,下面是原本的函數(shù):foo = function (x) return 2*x end函數(shù)定義實(shí)際上是一個(gè)賦值語(yǔ)句,將類(lèi)型為function的變量賦給一個(gè)變量。我們使用function (x) ... end來(lái)定義一個(gè)函數(shù)和使用{}創(chuàng)建一個(gè)表一樣。table標(biāo)準(zhǔn)庫(kù)提供一個(gè)排序函數(shù),接受一個(gè)表作為輸入?yún)?shù)并且排序表中的元素。這個(gè)函數(shù)必須能夠?qū)Σ煌?lèi)型的值(字符串或者數(shù)值)按升序或者降序進(jìn)行排序。Lua不是盡可能多地提供參數(shù)來(lái)滿足這些情況的需要,而是接受一個(gè)排序函數(shù)作為參數(shù)(類(lèi)似C++的函數(shù)對(duì)象),排序函數(shù)接受兩個(gè)排序元素作為輸入?yún)?shù),并且返回兩者的大小關(guān)系,例如:network = { {name = grauna, IP = }, {name = arraial, IP = }, {name = lua, IP = }, {name = derain, IP = },}如果我們想通過(guò)表的name域排序:(network, function (a,b) return ( )end)以其他函數(shù)作為參數(shù)的函數(shù)在Lua中被稱作高級(jí)函數(shù)(higherorder function),如上面的sort。在Lua中,高級(jí)函數(shù)與普通函數(shù)沒(méi)有區(qū)別,它們只是把“作為參數(shù)的函數(shù)”當(dāng)作第一類(lèi)值(firstclass value)處理而已。下面給出一個(gè)繪圖函數(shù)的例子:function eraseTerminal() (\27[2J)end writes an 39。*39。 at column 39。x39。 , 39。row y39。function mark (x,y) ((\27[%d。%dH*, y, x))end Terminal sizeTermSize = {w = 80, h = 24} plot a function (assume that domain and image are in the range [1,1])function plot (f) eraseTerminal() for i=1, do local x = (i/)*2 1 local y = (f(x) + 1)/2 * mark(i, y) end () wait before spoiling the screenend要想讓這個(gè)例子正確的運(yùn)行,你必須調(diào)整你的終端類(lèi)型和代碼中的控制符[3]一致:plot(function (x) return (x*2*) end)將在屏幕上輸出一個(gè)正弦曲線。將第一類(lèi)值函數(shù)應(yīng)用在表中是Lua實(shí)現(xiàn)面向?qū)ο蠛桶鼨C(jī)制的關(guān)鍵,這部分內(nèi)容在后面章節(jié)介紹。 閉包當(dāng)一個(gè)函數(shù)內(nèi)部嵌套另一個(gè)函數(shù)定義時(shí),內(nèi)部的函數(shù)體可以訪問(wèn)外部的函數(shù)的局部變量,這種特征我們稱作詞法定界。雖然這看起來(lái)很清楚,事實(shí)并非如此,詞法定界加上第一類(lèi)函數(shù)在編程語(yǔ)言里是一個(gè)功能強(qiáng)大的概念,很少語(yǔ)言提供這種支持。下面看一個(gè)簡(jiǎn)單的例子,假定有一個(gè)學(xué)生姓名的列表和一個(gè)學(xué)生名和成績(jī)對(duì)應(yīng)的表;現(xiàn)在想根據(jù)學(xué)生的成績(jī)從高到低對(duì)學(xué)生進(jìn)行排序,可以這樣做:names = {Peter, Paul, Mary}grades = {Mary = 10, Paul = 7, Peter = 8}(names, function (n1, n2) return grades[n1] grades[n2] pare the gradesend)假定創(chuàng)建一個(gè)函數(shù)實(shí)現(xiàn)此功能:function sortbygrade (names, grades) (names, function (n1, n2) return grades[n1] grades[n2] pare the grades end)end例子中包含在sortbygrade函數(shù)內(nèi)部的sort中的匿名函數(shù)可以訪問(wèn)sortbygrade的參數(shù)grades,在匿名函數(shù)內(nèi)部grades不是全局變量也不是局部變量,我們稱作外部的局部變量(external local variable)或者upvalue。(upvalue意思有些誤導(dǎo),然而在Lua中他的存在有歷史的根源,還有他比起external local variable簡(jiǎn)短)??聪旅娴拇a:function newCounter() local i = 0 return function() anonymous function i = i + 1 return i endendc1 = newCounter()print(c1()) 1print(c1()) 2匿名函數(shù)使用upvalue i保存他的計(jì)數(shù),當(dāng)我們調(diào)用匿名函數(shù)的時(shí)候i已經(jīng)超出了作用范圍,因?yàn)閯?chuàng)建i的函數(shù)newCounter已經(jīng)返回了。然而Lua用閉包的思想正確處理了這種情況。簡(jiǎn)單的說(shuō),閉包是一個(gè)函數(shù)以及它的upvalues。如果我們?cè)俅握{(diào)用newCounter,將創(chuàng)建一個(gè)新的局部變量i,因此我們得到了一個(gè)作用在新的變量i上的新閉包。c2 = newCounter()print(c2()) 1print(c1()) 3print(c2()) 2cc2是建立在同一個(gè)函數(shù)上,但作用在同一個(gè)局部變量的不同實(shí)例上的兩個(gè)不同的閉包。技術(shù)上來(lái)講,閉包指值而不是指函數(shù),函數(shù)僅僅是閉包的一個(gè)原型聲明;盡管如此,在不會(huì)導(dǎo)致混淆的情況下我們繼續(xù)使用術(shù)語(yǔ)函數(shù)代指閉包。閉包在上下文環(huán)境中提供很有用的功能,如前面我們見(jiàn)到的可以作為高級(jí)函數(shù)(sort)的參數(shù);作為函數(shù)嵌套的函數(shù)(newCounter)。這一機(jī)制使得我們可以在Lua的函數(shù)世界里組合出奇幻的編程技術(shù)。閉包也可用在回調(diào)函數(shù)中,比如在GUI環(huán)境中你需要?jiǎng)?chuàng)建一系列button,但用戶按下button時(shí)回調(diào)函數(shù)被調(diào)用,可能不同的按鈕被按下時(shí)需要處理的任務(wù)有點(diǎn)區(qū)別。具體來(lái)講,一個(gè)十進(jìn)制計(jì)算器需要10個(gè)相似的按鈕,每個(gè)按鈕對(duì)應(yīng)一個(gè)數(shù)字,可以使用下面的函數(shù)創(chuàng)建他們:function digitButton (digit) return Button{ label = digit, action = function () add_to_display(digit) end }end這個(gè)例子中我們假定Button是一個(gè)用來(lái)創(chuàng)建新按鈕的工具, label是按鈕的標(biāo)簽,action是按鈕被按下時(shí)調(diào)用的回調(diào)函數(shù)。(實(shí)際上是一個(gè)閉包,因?yàn)樗L問(wèn)upvalue digit)。digitButton完成任務(wù)返回后,局部變量digit超出范圍,回調(diào)函數(shù)仍然可以被調(diào)用并且可以訪問(wèn)局部變量digit。閉包在完全不同的上下文中也是很有用途的。因?yàn)楹瘮?shù)被存儲(chǔ)在普通的變量?jī)?nèi)我們可以很方便的重定義或者預(yù)定義函數(shù)。通常當(dāng)你需要原始函數(shù)有一個(gè)新的實(shí)現(xiàn)時(shí)可以重定義函數(shù)。例如你可以重定義sin使其接受一個(gè)度數(shù)而不是弧度作為參數(shù):oldSin = = function (x) return oldSin(x*)end更清楚的方式:do local oldSin = local k = = function (x) return oldSin(x*k) endend這樣我們把原始版本放在一個(gè)局部變量?jī)?nèi),訪問(wèn)sin的唯一方式是通過(guò)新版本的函數(shù)。利用同樣的特征我們可以創(chuàng)建一個(gè)安全的環(huán)境(也稱作沙箱,和java里的沙箱一樣),當(dāng)我們運(yùn)行一段不信任的代碼(比如我們運(yùn)行網(wǎng)絡(luò)服務(wù)器上獲取的代碼)時(shí)安全的環(huán)境是需要的,比如我們可以使用閉包重定義io庫(kù)的open函數(shù)來(lái)限制程序打開(kāi)的文件。do local oldOpen = = function (filename, mode) if access_OK(filename, mode) then return oldOpen(filename, mode) else return nil, access denied end endend 非全局函數(shù)Lua中函數(shù)可以作為全局變量也可以作為局部變量,我們已經(jīng)看到一些例子:函數(shù)作為table的域(、)。這種情況下,必須注意函數(shù)和表語(yǔ)法:1. 表和函數(shù)放在一起Lib = {} = function (x,y) return x + y end = function (x,y) return x y end2. 使用表構(gòu)造函數(shù)Lib = { foo = function (x,y) return x + y end, goo = function (x,y) return x y end}3. Lua提供另一種語(yǔ)法方式Lib = {}function (x,y) return x + yendfunction (x,y)