Lua入門教程 5.函數

0x05 函數


定義與調用形式


定義:函數是對語句表達式的抽象。


函數的調用形式:無論是語句或者表達式,都需要將所有參數放到一對圓括號中。即使調用沒有參數也必須寫出一對空括號。
注:此條規則對一種情況例外—一個函數只有一個參數,並且此參數是一個字面字符串或者table構造式(見下列代碼)

print "Hello World"   --只有一個參數且爲字面字符串
f{x = 20, y = 30}     --只有一個參數且爲table構造式

冒號操作符 Lua爲面向對象式的調用提供了一種特殊的語法—冒號表達式 如 o.foo(o,x)o:foo(x)是等價的

關於函數中的參數數量調用函數提供的實參數量可以與形參數量不同

function f(a, b) return a or b end

--調用函數      形參
f(3)            a=3 b=nil
f(3,4)          a=3 b=4
f(3,4,5)        a=3 b=4 5被拋棄

Lua中函數的調用規則與多重賦值的規則相似

參數規則 “實參多於形參,則捨棄多餘實參;實參少於形參,則多餘形參初始化爲nil”


多重返回值

Lua允許函數返回多個結果

    function morevals()
        return "Hello", "World"
    end

    print(morevals())   --"Hello" "World"

可以直接將多個值在函數中進行返回


Lua將會根據實際情況進行返回參數的調整:

假如有以下函數:

function foo0() end
function foo1() return "a" end
function foo2() return "a","b" end
  • 若一個函數的調用是最後的一個表達式,那麼Lua會保留儘可能多的返回值
x,y = foo2()   x="a" y="b"
x,y,z = 10,foo2()   x=10 y="a" z="b"
  • 如果一個函數沒有返回值或者沒有足夠多的返回值,那麼Lua會用nil來代替缺失的值
x,y = foo0()    x=nil y=nil
x,y = foo1()    x="a" y=nil
  • 如果一個函數不是一系列表達式的最後一個元素,那麼只返回一個值
x,y = foo2(),20     x="a" y=20
x,y = foo0(),20,30  x=nil y=20 30被拋棄

一系列表達式 : 多重賦值,函數調用時傳入的實參列表,table的構造式和return語句

  • 當一個函數調用作爲另一個函數調用的最後一個參數時,第一個函數所有的返回值都將作爲實參傳入第二個函數
print (foo0())     
print (foo1())      a
print (foo2())      a b
print (foo2(),10)   a 10 (出現在表達式中時,Lua會將其返回值數量調整爲1)

table構造式可以完整接收所有返回的參數而不會有任何的參數調整

t = {foo0()}     t={}   (nil)
t = {foo1()}     t={"a"}
t = {foo2()}     t={"a","b"}

不過這種情況是隻有當函數調用作爲最後一個參數時纔會發生,如果是在其他位置則只會返回一個值

  • return語句:return f() 將會返回f()中的所有返回值

變長參數

Lua中的函數可以接收不同數量的實參,看下面代碼

function add(...)
local s = 0
    for i,v in ipairs{...} do
    s = s + v
    end
return s
end

print(add(1,2,3,4))   -->10

參數表中的...表示可接收不同數量的實參。當這個函數被調用時,表示所有的參數會被收集到一塊。函數中使用時還依然需要使用...但此時是作爲一個表達式來使用的–>{...}表示一個有所有參數構造的數組。
表達式...的行爲類似一個具有多重返回值的函數。


selector 用於操作變長參數
如果selector爲數字n,則返回它的第n個可變實參;否則selector只能爲字符串#,這樣select會返回變長參數的總數。下面的代碼演示瞭如何用selector來遍歷所有的參數

for i=1, select('#', ...) do
local arg = select(i, ...)
    <循環體>
end

--Lua5.0的代碼如下
function foo(a, b, ...)
local arg = {...}; 
arg.n = select("#", ...)
end

具名實參


通過名稱來指定實參

例如一個函數os.rename,用於更改文件名

rename{old = "temp.lua", new = "temp1,lua"}

作用:當一個函數擁有大量的參數,而其中大部分參數是可選的話,具名實參會非常有用。

w = Window{ x=0, y=0, width=200, height=100, title="lua"}

函數是第一類值,這表示函數可以與其他傳統類型的值一樣存儲到變量中
函數還具有詞法域:指一個函數可以嵌套在另外一個函數中,內部函數可以訪問外部函數

對於函數print(),其實是指一個存儲着可以打印字符串函數的變量print,這個變量可以存儲其它的函數

a = print
a("Hello World")  --Hello World

print = math.sin --現在print變量存儲了math.sin引用的函數
a(print(1.0))  --調用了正弦函數,打印出結果 0.8414709848079

函數可以被當作一種類型

function foo() return "a" end
等價於
foo = function () return "a" end

表達式 function(x) <body> end 可以被當作一種函數的構造式 如 table構造式一般

function表達式的應用,table.sort接受一個table並對其進行排序,其中一個次序函數就可以在需要時直接使用function表達式的創建

network = {
    {name = "a", IP = "1.2.3.4"},
    {name = "b", IP = "2.4.21.3"},
    {name = "c", IP = "2.32.24.33"}
}

--接下來調用table.sort
tabel.sort(network, function(a, b) return(a.name > b.name) end)

閉合函數

若將一個函數寫在另外一個函數裏面,那麼這個位於內部的函數便可以訪問外部函數中的局部變量。

names = {"Peter", "Paul", "Mary"}
gradees = {Mary = 10, Paul = 7, Peter = 8}
table.sort(names, function(n1, n2)
                    return grades[n1] > grades[n2]
                  end)

上面代碼中的grades在這個匿名函數中既不是全局變量也不是局部變量,稱爲 “非局部的變量”


看一下以下的代碼,說明一下非局部的變量存在的合理性:

function newCounter()
local i = 0
    return function()      
            i = i + 1
           end
end

c1 = newCounter()
print(c1())  --1
print(c1())  --2
c2 = newCounter()
print(c2())  --1

Lua將以上的情況視爲Closure,一個Closure代表一個函數加上這個函數所訪問的非局部的變量。函數c1 c2是同一個函數創建的不同的Closure(閉合函數),他們擁有不同的非局部的變量i

非全局的函數


將函數與table結合起來

test = {}
test.foo1 = function(a, b) return a+b end
test.foo2 = function(a, b) return a-b end

test = {
    foo1 = function(a,b) return a+b end,
    foo2 = function(a,b ) return a-b end
}

--此外 Lua還提供了另外一種方式
test = {}
function test.foo1(a, b)
    return a + b
end

function test.foo2(a, b)
    return a - b
end

只要將一個函數存儲在一個局部變量中,就得到一個局部函數,該函數只能在特定的作用域中使用

局部函數的定義

local f = function(參數)
    <函數體>
end

local function f(參數)
    <函數體>
end

遞歸函數中會較多的使用到局部函數:

local fact = function(n)
if(n==0) then return 1
    else return fact(n-1)  --Error
    end
end

在使用fact(n-1)時局部的fact的定義尚未完成,因此此時調用的是全局的fact函數。可以先定義一個局部變量,保存這個函數:

local fact 
fact = function(n)
    if(n==0) then return 1
        else return fact(n-1)
        end
end

尾調用

當一個函數調用是另一個函數的最後一個動作時,該調用是一條“尾調用” 尾調用不會耗費任何棧空間(尾調用消除)

function f(x) return g(x) end

--由於有尾調用消除的特性,下列函數不管傳入什麼數字都不會造成棧溢出
function foo (n)
    if n >0 then return foo(n-1) end
end

判斷是否符合尾調用消除的原則
一個函數在調用完另外一個函數後,是否就無其他事情需要做了?

在Lua中只有return <func>(<args>)這樣的調用形式纔算是一條尾調用


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章