使用func和closure加工數(shù)據(jù)(一)
[TOC]
函數(shù)的返回值以及靈活多變的參數(shù)
作為開始,我們就簡(jiǎn)單的快速學(xué)習(xí)一下Swift中函數(shù)的基本要素,這將是我們接下來所有內(nèi)容的基礎(chǔ)。
一個(gè)簡(jiǎn)單的函數(shù)
一個(gè)簡(jiǎn)單的函數(shù)看上去是這個(gè)樣子的:
func printName() {
print("My name is Mars")
}
其中:
-
func是定義函數(shù)的關(guān)鍵字,后面是函數(shù)名; -
()中是可選的參數(shù)列表,既然是最簡(jiǎn)單的函數(shù),自然我們可以讓它留空; -
()后面,是函數(shù)的返回值,同樣,簡(jiǎn)單起見,我們也沒有定義返回值 -
{}中是函數(shù)要封裝的邏輯,其實(shí),在這里,我們調(diào)用print,也是一個(gè)函數(shù),只不過,它是一個(gè)定義在標(biāo)準(zhǔn)庫中的函數(shù),并且?guī)в幸粋€(gè)參數(shù)罷了
向函數(shù)傳遞參數(shù)
我們定義一個(gè)計(jì)算兩個(gè)整數(shù)的乘機(jī):
func mul(m: Int, n: Int) {
print(m*n)
}
然后我們通過下面這樣來使用mul:
mul(m: 2, n: 3) // 6
為參數(shù)設(shè)置默認(rèn)值
我們?cè)诼暶骱瘮?shù)時(shí),經(jīng)常會(huì)遇到處理參數(shù)的默認(rèn)值問題。它可以用來約束函數(shù)的默認(rèn)行為,或者簡(jiǎn)化大多數(shù)時(shí)候都會(huì)傳遞的值。例如:
func mul(_ m: Int, of n: Int = 1) {
print(m*n)
}
當(dāng)我們使用的時(shí)候:
mul(2) //2
擁有默認(rèn)值的函數(shù)參數(shù)必須從右向左依次排列,有默認(rèn)值的參數(shù)不能出現(xiàn)在無默認(rèn)值的參數(shù)的左邊。
定義可變長參數(shù)
接下來,如果我們要計(jì)算不確定個(gè)數(shù)參數(shù)的乘積該怎么辦呢?Swift還允許我們通過下面的方式,定義可變長度的參數(shù)列表:
func mul(_ numbers: Int ... ) {
let arrayMul = number.reduct(1, *)
print("mul: \(arrayMul)")
}
在上面的例子中,我們用numbers:Int ...的形式,表示函數(shù)可以接受的Int參數(shù)的個(gè)數(shù)是可變的。實(shí)際上,numbers的類型,是一個(gè)Array<Int>,因此,為了計(jì)算乘積,我們直接使用Array類型的reduce方法就好了。
定義好以后,我們可以這樣調(diào)用它:
mul(2, 3, 4, 5, 6, 7) //5040
定義inout參數(shù)
Swift里,函數(shù)的參數(shù)有一個(gè)性質(zhì):默認(rèn)情況下,參數(shù)是只讀的,這也就意味著:
- 你不能再函數(shù)內(nèi)部修改參數(shù)值;
- 你也不能通過函數(shù)參數(shù)對(duì)外返回值;
func mul(result: Int, _ number: Int ...) {
result = numbers.reduce(1, *) //!!! Error Here !!!
print("mul: \(arrayMul)")
}
在上面的實(shí)現(xiàn)里,函數(shù)的參數(shù)默認(rèn)是個(gè)常量,因此編譯器會(huì)提示你不能再函數(shù)內(nèi)部對(duì)常量賦值。然后再來第二條:如果我們希望參數(shù)可以被修改,并且把修改過的結(jié)果返回給傳遞進(jìn)來的參數(shù),該怎么辦呢?
其實(shí),很簡(jiǎn)單,我們需要用inout關(guān)鍵字修飾一下參數(shù)的類型就ok了!明確告訴Swift編譯器我們要修改這個(gè)參數(shù)的值:
func mul(result: inout Int, _ number: Int ...) {
result = numbers.reduce(1, *)
print(mul: \(result))
}
然后就可以這樣使用mul了
var result = 0
mul(result: &result,2,3,4,5,6,7)
result //5040
對(duì)于inout類型的參數(shù),我們?cè)谡{(diào)用的時(shí)候,也需要在參數(shù)前明確使用&.這樣,mul執(zhí)行結(jié)束后,就可以看到result的值變成了5040
通過函數(shù)返回內(nèi)容
當(dāng)然,通過參數(shù)來獲取返回值只能算函數(shù)的某種副作用,更"正統(tǒng)"的做法應(yīng)該是把返回值放在函數(shù)的定義里,像這樣:
func mul(_ number: Int ...) -> Int {
return numbers.reduce(1, *)
}
我們通過->Type的方式,在參數(shù)列表后面定義返回值。然后,就可以用mul的返回值來定義變量了:
let result = mul(2,3,4,5,6,7) // 5040
函數(shù)和Closure真的是不同的類型么?
提起closure,如果你有過其他編程語言的經(jīng)歷,你可能會(huì)立即聯(lián)想起一些類似的事物,例如:匿名函數(shù)、或者可以捕獲變量的一對(duì){}等等。但實(shí)際上,我們很容易搞混兩個(gè)概念:Closure expression和Closure。他們究竟是什么呢?我們先從closure expression開始。
理解Closure Expression
簡(jiǎn)單來說,closure expression就是函數(shù)的一種簡(jiǎn)寫形式。例如,對(duì)于下面這個(gè)計(jì)算參數(shù)平方的參數(shù):
func square(n: Int) -> Int {
return n*n
}
我們也可以這樣來定義:
let squareExpression = { (n: Int) -> Int in
return n*n
}
在調(diào)用的時(shí)候是完全相同的:
square(2) // 4
squareExpression(2) // 4
并且它們都可以當(dāng)做函數(shù)的參數(shù)來使用:
let numbers = [1, 2, 3, 4, 5]
numbers.map(square) // [1, 4, 9 ,16, 25]
numbers.map(squareExpressions) // [1, 4, 9 ,16, 25]
我們?cè)谶@個(gè)例子里,用于定于squareExpressions的{}就叫做closure expression,它只是把函數(shù)參數(shù)、返回值以及實(shí)現(xiàn)統(tǒng)統(tǒng)寫在了一個(gè){}里。
沒錯(cuò),此時(shí)的{}以及squareExpressions并不能叫做closure,它只是一個(gè)closure expression。那么,為什么要有兩種不同的方式來定義函數(shù)呢?最直接的理由就是,為了寫起來更簡(jiǎn)單。Closure expression可以再定義它的上下文里,被不斷的簡(jiǎn)化,讓代碼盡可能的呈現(xiàn)出最自然的語義形態(tài)。
例如,當(dāng)我們把一個(gè)完成的closure expression定義在map參數(shù)里,是這樣的:
numbers.map ({ (n: Int) -> Int in
return n * n
})
首先Swift可以根據(jù)numbers的類型,自動(dòng)推導(dǎo)出map中的函數(shù)參數(shù)以及返回值的類型,因此,我們可以在closure expression中去掉它:
numbers.map ({ n in return n * n })
其次,如果closure expression中只有一條語句,Swift可以自動(dòng)把這個(gè)語句的值作為整個(gè)expression的值返回,因此,我們還可以去掉return關(guān)鍵字:
numbers.map ({ n in n * n })
第三,如果你覺得在closure expression中為參數(shù)起名字是個(gè)意義不大的事情,我們還可以使用Swift內(nèi)置的$0/$1/$2/$3這樣的形式作為closure expression的參數(shù)替代符,這樣,我們連參數(shù)聲明和in關(guān)鍵字也可以省略了:
numbers.map ({ $0 * $0 })
第四,如果函數(shù)類型的參數(shù)在參數(shù)列表的最后一個(gè),我們還可以把closure expression寫在()外面,讓它和其它普通參數(shù)更明顯的區(qū)分開:
numbers.map(){ $0 * $0 }
最后,如果函數(shù)只有一個(gè)函數(shù)類型的參數(shù),我們甚至可以再調(diào)用的時(shí)候,去掉():
numbers.map { $0 * $0 }
看到這里,就應(yīng)該知道當(dāng)我們把closure expression用在它的上下文里,究竟有多方便了,相比一開始的定義,或者單獨(dú)定義一個(gè)函數(shù),然后傳遞給它,都好太多。但事情至此還沒結(jié)束,相比這樣:
numbers.sorted(by: {$0 > $1}) // [5,4,3,2,1]
closure expression 還有一種更簡(jiǎn)單的形式:
numbers.sorted(by: > ) // [5,4,3,2,1]
這是因?yàn)椋?code>numbers.sorted(by:)的函數(shù)參數(shù)是這樣的:(Int ,Int) -> Bool,而Swift為Int類型定義的>操作符也正好和這個(gè)類型相同,這樣,我們就可以直接把操作符傳遞給它,本質(zhì)上,這和我們傳遞函數(shù)名是一樣的。
另外,除了寫起來更簡(jiǎn)單之外,closure expression還有一個(gè)副作用,就是默認(rèn)情況下,我們無法忽略它的參數(shù),編譯器會(huì)對(duì)這種情況報(bào)錯(cuò)。看個(gè)例子,如果我們要得到一個(gè)包含了10個(gè)隨機(jī)數(shù)的Array,最簡(jiǎn)單的方法,就是一個(gè)CountableRange調(diào)用map方法:
(0 ... 9).map { arc4random() } // Error in Swift
這樣看似很好,但是由于map的函數(shù)參數(shù)默認(rèn)是帶有一個(gè)參數(shù)的,在我們的例子里,表示range中的每個(gè)值,因此,如果我們?cè)谡麄€(gè)closure expression里都沒有使用這個(gè)參數(shù),Swift編譯器就會(huì)提示我們錯(cuò)誤。
我們不能默認(rèn)忽略closure expression中的參數(shù),如果堅(jiān)持如此,我們必須用_明確表明這個(gè)意圖:
(0 ... 9).map { _ in arc4random() }
這也算是Swift為了類型和代碼安全,利用編譯器,為我們提供的一層保障。以上,就是和closure expression有關(guān)的內(nèi)容,如你看到的一樣,它就是函數(shù)的另外一種在上下文中更簡(jiǎn)單的寫法和用func定義的函數(shù)沒有任何區(qū)別。
究竟什么是closure
如果我們翻翻Wikipedia,就能找到下面的定義:a closure is a record storing a function together with an environment。
說的通俗一點(diǎn),一個(gè)函數(shù)加上它捕獲的變量一起,才算一個(gè)closure。來看個(gè)例子:
func makeCounter() -> () -> Int {
var value = 0
return {
value += 1
return value
}
}
makeCounter()返回一個(gè)函數(shù),用來返回它被調(diào)用的次數(shù)。然后,我們分別定義兩個(gè)計(jì)數(shù)器,并各自調(diào)用幾次:
let counter1 = makeCounter()
let counter2 = makeCounter()
(0...2).forEach { _ in print(counter1())} // 1 2 3
(0...5).forEach { _ in print(counter2())} // 1 2 3 4 5 6
這樣,三次調(diào)用counter1()會(huì)在控制臺(tái)打印"123",6次調(diào)用會(huì)打印“123456”。這說明什么呢?
首先,盡管從makeCounter返回后,value已經(jīng)離開了它的作用域,但我們多次調(diào)用counter1或counter2時(shí),value的值還是各自進(jìn)行了累加。這就說明,makeCounter返回的函數(shù),捕獲了makeCounter的內(nèi)部變量value。
其次,counter1和counter2分別有其各自捕獲的value,也就是其各自的上下文環(huán)境,他們并不共享任何內(nèi)容。
理解了closure的含義之后,我們就知道了,closure expression和closure并不是一回事兒。然后,捕獲變量是{}的專利么?實(shí)際上也不是,函數(shù)也可以捕獲變量。
函數(shù)同樣可以是一個(gè)Closure
還是之前makeCounter的例子,我們把返回的closure expression改成一個(gè)local function:
func makeCounter() -> () -> Int {
var value = 0
return increase() -> Int {
value += 1
return value
}
return increase
}
然后你就會(huì)發(fā)現(xiàn),之前counter1和counter2的例子的執(zhí)行結(jié)果,和之前是一樣的:
(0...2).forEach { _ in print(counter1()) } // 1 2 3
(0...5).forEach { _ in print(counter2()) } // 1 2 3 4 5 6
所以,捕獲變量這種行為,實(shí)際上,跟用{}定義函數(shù)也沒關(guān)系。