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>)
這樣的調用形式纔算是一條尾調用