Lua function 函數


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.



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM