天天看點

[Lua]Lua語言基礎彙總(4) -- 函數

Lua中的函數和C++中的函數的含義是一緻的,Lua中的函數格式如下:

1

2

3

<code>function MyFunc(param)</code>

<code>     </code><code>-- Do something</code>

<code>end</code>

在調用函數時,也需要将對應的參數放在一對圓括号中,即使調用函數時沒有參數,也必須寫出一對空括号。對于這個規則隻有一種特殊的例外情況:一個函數若隻有一個參數,并且此參數是一個字元串或table構造式,那麼圓括号便可以省略掉。看以下代碼:

4

5

6

<code>print </code><code>"Hello World"</code>          <code>--&gt; print(</code><code>"Hello World"</code><code>)等價</code>

<code>print [[a multi-line</code>

<code>          </code><code>message]]          --&gt;print([[a multi-line</code>

<code>                              </code><code>--&gt;               message]]) 等價</code>

<code>-- f是一個函數</code>

<code>f{x=10, y=20}               --&gt;f({x=10, y=20}) 等價</code>

上面代碼的一些簡便寫法,如果不熟悉的話,在閱讀别人的代碼時,就會是一頭霧水。

一個函數定義具有一個名稱、一系列的參數和一個函數體。函數定義時,所定義的參數的使用方式與局部變量非常相似,它們是由調用函數時的“實際參數”初始化的。調用函數時提供的實參數量可以與形參數量不同。Lua會自動調整實參的數量,以比對參數表的要求,若“實參多餘形參,則舍棄多餘的實參;若實參不足,則多餘的形參初始化為nil”。這個與接下來要介紹的多重傳回值非常相似。

多重傳回值

這個應該是Lua的一個特征吧。允許函數傳回多個結果,隻需要在return關鍵字後列出所有的傳回值即可。以下根據帶來來說明情況:

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<code>function foo0() end                         -- 無傳回值</code>

<code>function foo1() </code><code>return</code> <code>"a"</code> <code>end          -- 傳回一個結果</code>

<code>function foo2() </code><code>return</code> <code>"a"</code><code>, </code><code>"b"</code> <code>end     -- 傳回兩個結果</code>

<code>-- 在多重指派時,如果一個函數調用是最後,或僅有的一個表達式,</code>

<code>-- 那麼Lua會保留其盡可能多的傳回值,用于比對指派變量</code>

<code>x, y = foo2()               -- x = </code><code>"a"</code><code>, y = </code><code>"b"</code>

<code>x = foo2()                    -- x = </code><code>"a"</code><code>, </code><code>"b"</code><code>被丢棄</code>

<code>x, y, z = 10, foo2()     -- x = 10, y = </code><code>"a"</code><code>, z = </code><code>"b"</code>

<code>-- 如果一個函數沒有傳回值或者沒有足夠多的傳回值,那麼Lua會用</code>

<code>-- nil來補充缺失的值</code>

<code>x, y = foo0()               -- x = nil, y = nil</code>

<code>x, y = foo1()               -- x = </code><code>"a"</code><code>, y = nil</code>

<code>x, y, z = foo2()          -- x = </code><code>"a"</code><code>, y = </code><code>"b"</code><code>, z = nil</code>

<code>-- 如果一個函數調用不是一系清單達式的最後一個元素,那麼将隻産生一個值:</code>

<code>x, y = foo2(), 20          -- x = </code><code>"a"</code><code>, y = 20</code>

<code>x, y = foo0(), 20, 30     -- x = nil, y = 20, 30則被丢棄</code>

<code>-- table構造式可以完整的接收一個函數調用的所有結果,即不會有任何數量</code>

<code>-- 方面的調整</code>

<code>local t = {foo0()}          -- t = {}(一個空的table)</code>

<code>local t = {foo1()}          -- t = {</code><code>"a"</code><code>}</code>

<code>local t = {foo2()}          -- t = {</code><code>"a"</code><code>, </code><code>"b"</code><code>}</code>

<code>-- 但是,對于上述的行為,隻有當一個函數調用作為最後一個元素時才會發生,</code>

<code>-- 而在其他位置上的函數調用總是隻産生一個結果值</code>

<code>local t = {foo0(), foo2(), 4}          -- t[1] = nil, t[2] = </code><code>"a"</code><code>, t[3] = 4</code>

<code>-- 我們也可以在一個函數中,使用</code><code>return</code><code>傳回另一個函數</code>

<code>function MyFunc()          -- 傳回a</code>

<code>     </code><code>return</code> <code>foo1()          -- 注:這裡是</code><code>return</code> <code>foo1(),而不是</code><code>return</code> <code>(foo1())</code>

<code>-- </code><code>return</code> <code>foo1()和</code><code>return</code> <code>(foo1())是兩個完全不同的意思</code>

<code>-- 将一個函數調用放入一對圓括号中,進而迫使它隻傳回一個結果</code>

<code>print((foo0()))          -- nil</code>

<code>print((foo1()))          -- a</code>

<code>print((foo2()))          -- a</code>

變長參數

在C語言中,函數可以接受不同數量的實參,Lua中的函數也可以接受不同數量的實參,例如以下代碼:

<code>-- 列印所有的參數</code>

<code>function VarArguments(...)</code>

<code>     </code><code>for</code> <code>i, v in ipairs{...} </code><code>do</code>

<code>          </code><code>print(v)</code>

<code>     </code><code>end</code>

<code>VarArguments(1, 2, 3)</code>

參數表中的3個點(…)表示該函數可接受不同數量的實參。當這個函數被調用時,它的所有參數都會被收集到一起。這部分收集起來的實參稱為這個函數的“變長參數”。一個函數要通路它的變長參數時,仍需要用到3個點(…)。但不同的是,此時這3個點是作為一個表達式來使用的。在上例中,表達式{…}表示一個由所有變長參數構成的數組。在C語言中使用變長參數需要注意的問題,在Lua中同樣需要注意。

通常一個函數在周遊其變長參數時隻需要使用表達式{…},這就像通路一個table一樣,通路所有的變長參數。然而在某些特殊的情況下,變長參數中可能會包含一些故意傳入的nil,那麼此時就需要用select來通路變長參數了。調用select時,必須傳入一個固定實參selector和一系列變長參數。如果selector為數字n,那麼select傳回它的第n個可變實參;否則selector隻能為字元串“#”,這樣select會傳回變長參數的總數,請看以下代碼:

<code>for</code> <code>i = 1, select(</code><code>'#'</code><code>, ...) </code><code>do</code>

<code>    </code><code>local arg = select(i, ...) -- 得到第i個參數</code>

<code>    </code><code>-- Do something </code><code>else</code>

select(‘#’, …)會傳回所有變長參數的總數,其中包括nil(還記得table.maxn麼?)對于Lua 5.0版本來說,變長參數則有另外一套機制。聲明函數的文法是一樣的,也是将3個點作為最後一個參數。但Lua 5.0沒有提供“…”表達式。而是通過一個隐含的局部table變量“arg”來接受所有的變長參數。這個table還有一個名為“n”的字段,用來記錄變長參數的總數,例如以下代碼:

<code>function MyFunc(a, b, ...)</code>

<code>     </code><code>print(arg.n)</code>

<code>MyFunc(1, 2, 3, 4, 5)     --&gt;3</code>

這套舊機制的缺點在于,每當程式調用了一個具有變長參數的函數時,都會建立一個新的table。而在新機制中,隻有在需要時才會去建立這個用于變長參數通路的table。這裡隻是對這個方法進行簡單介紹,别在閱讀别人的代碼時,看不懂!!!

深入讨論函數

在Lua中,函數與其它傳統類型的值具有相同的權利。函數可以存儲到變量或table中,也可以作為實參傳遞給其它函數,還可以作為其它函數的傳回值。在Lua中有一個容易混淆的概念是,函數與所有其它值一樣都是匿名的,即它們都沒有名稱。當讨論一個函數名時,實際上是在讨論一個持有某函數的變量,例如以下代碼:

<code>-- 我們經常這樣定義函數</code>

<code>function foo(x) </code><code>return</code> <code>2 * x end</code>

<code>-- 實際上,這隻是一種“文法糖”而已;</code>

<code>-- 上述代碼隻是下面代碼的一種簡化書寫形式</code>

<code>foo = function (x) </code><code>return</code> <code>2 * x end</code>

實際上,一個函數定義實際就是一條語句(更準确地說是一條指派語句),這條語句建立了一種類型為“函數”的值,并将這個值賦予一個變量。由于函數在Lua中就是一個普通的值,是以不僅可以将其存儲在全局變量中,還可以存儲在局部變量甚至table的字段中。

内嵌函數

若将一個函數寫在另一個函數之内,那麼這個位于内部的函數便可以通路外部函數中的局部變量,這個特征叫做“詞法域”。我們來看看下面一段有趣的代碼:

<code>function newCounter()</code>

<code>     </code><code>local i = 0</code>

<code>     </code><code>return</code> <code>function () -- 匿名函數</code>

<code>          </code><code>i = i + 1</code>

<code>          </code><code>return</code> <code>i</code>

<code>c1 = newCounter()</code>

<code>print(c1())     --&gt;輸出什麼?</code>

<code>print(c1())     --&gt;又輸出什麼?</code>

如果你很明白上面的輸出,很明白上面的代碼,那麼閉合函數這一小節就不需要閱讀了。在上述代碼中,有一個變量i,對于函數newCounter來說,i是一個局部變量,但是對于匿名函數來說,當它通路這個i時,i既不是全局變量,也不是局部變量,對于我們來說,我們稱這樣的變量為一個“非局部的變量”。下面這段代碼也是同樣的道理:

<code>function newCounter(i)</code>

<code>c1 = newCounter(10)</code>

匿名函數通路了一個“非局部的變量”i,該變量用于保持一個計數器。乍一看,由于建立變量i的函數,也就是newCounter已經傳回,是以之後每次調用匿名函數時,i都應該是已經超出了作用範圍。但是,Lua會以closure的概念來正确地處理這種情況。在這裡簡單的講,一個closure就是一個函數加上該函數所需通路的所有“非局部的變量”。如果再次調用newCounter,那麼它會建立一個新的局部變量i,進而将得到一個新的closure。在後續的總結中,我會專門總結一篇關于Lua中的閉包的博文,敬請期待。

非全局的函數

由于函數和普通變量一樣,是以函數不僅可以存儲在全局變量中,還可以存儲在table的字段中,或局部變量中。我們可以把函數存在一個table中,比如以下代碼:

<code>Lib = {}</code>

<code>Lib.foo = function (x, y) </code><code>return</code> <code>x + y end</code>

<code>Lib.goo = function (x, y) </code><code>return</code> <code>x - y end</code>

隻要将一個函數存儲在一個局部變量中,就得到了一個“局部函數”,也就是說這個函數隻能在某個特定的作用域内才有效。我們可以這樣定義一個局部的函數:

<code>local f = function (&lt;參數&gt;)</code>

<code>     </code><code>&lt;函數體&gt;</code>

<code>-- Lua還提供另一種特殊的“文法糖”</code>

<code>local function f (&lt;參數&gt;)</code>

有的時候,我們需要進行函數的前置聲明,比如以下代碼:

<code>local f, g</code>

<code>function f()</code>

<code>     </code><code>&lt;一些其它操作&gt;</code>

<code>     </code><code>g()</code>

<code>function g()</code>

<code>     </code><code>f()</code>

總結

這篇博文對Lua中的函數進行了大體上的總結,至少看完這篇博文,你會使用Lua寫函數了,會使用Lua中的函數了。但是對于比較深的東西,這裡沒有總結,比如“閉包”。我會專門寫一篇關于Lua中的閉包的文章。