使用func和closure加工資料(一)
使用func和closure加工資料(一)
[TOC]
函式的返回值以及靈活多變的引數
作為開始,我們就簡單的快速學習一下Swift中函式的基本要素,這將是我們接下來所有內容的基礎。
一個簡單的函式
一個簡單的函式看上去是這個樣子的:
func printName() {
print("My name is Mars")
}
其中:
-
func
是定義函式的關鍵字,後面是函式名; -
()
中是可選的引數列表,既然是最簡單的函式,自然我們可以讓它留空; -
()
後面,是函式的返回值,同樣,簡單起見,我們也沒有定義返回值 -
{}
中是函式要封裝的邏輯,其實,在這裡,我們呼叫print
,也是一個函式,只不過,它是一個定義在標準庫中的函式,並且帶有一個引數罷了
向函式傳遞引數
我們定義一個計算兩個整數的乘機:
func mul(m: Int, n: Int) {
print(m*n)
}
然後我們通過下面這樣來使用mul
:
mul(m: 2, n: 3) // 6
為引數設定預設值
我們在宣告函式時,經常會遇到處理引數的預設值問題。它可以用來約束函式的預設行為,或者簡化大多數時候都會傳遞的值。例如:
func mul(_ m: Int, of n: Int = 1) {
print(m*n)
}
當我們使用的時候:
mul(2) //2
擁有預設值的函式引數必須從右向左依次排列,有預設值的引數不能出現在無預設值的引數的左邊。
定義可變長引數
接下來,如果我們要計算不確定個數引數的乘積該怎麼辦呢?Swift還允許我們通過下面的方式,定義可變長度的引數列表:
func mul(_ numbers: Int ... ) {
let arrayMul = number.reduct(1, *)
print("mul: \(arrayMul)")
}
在上面的例子中,我們用numbers:Int ...
的形式,表示函式可以接受的Int
引數的個數是可變的。實際上,numbers
的型別,是一個Array<Int>
,因此,為了計算乘積,我們直接使用Array
型別的reduce
方法就好了。
定義好以後,我們可以這樣呼叫它:
mul(2, 3, 4, 5, 6, 7) //5040
定義inout引數
Swift裡,函式的引數有一個性質:預設情況下,引數是隻讀的,這也就意味著:
- 你不能再函式內部修改引數值;
- 你也不能通過函式引數對外返回值;
func mul(result: Int, _ number: Int ...) {
result = numbers.reduce(1, *) //!!! Error Here !!!
print("mul: \(arrayMul)")
}
在上面的實現裡,函式的引數預設是個常量,因此編譯器會提示你不能再函式內部對常量賦值。然後再來第二條:如果我們希望引數可以被修改,並且把修改過的結果返回給傳遞進來的引數,該怎麼辦呢?
其實,很簡單,我們需要用inout
關鍵字修飾一下引數的型別就ok了!明確告訴Swift編譯器我們要修改這個引數的值:
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
對於inout
型別的引數,我們在呼叫的時候,也需要在引數前明確使用&
.這樣,mul
執行結束後,就可以看到result
的值變成了5040
通過函式返回內容
當然,通過引數來獲取返回值只能算函式的某種副作用,更"正統"的做法應該是把返回值放在函式的定義裡,像這樣:
func mul(_ number: Int ...) -> Int {
return numbers.reduce(1, *)
}
我們通過->Type
的方式,在引數列表後面定義返回值。然後,就可以用mul
的返回值來定義變數了:
let result = mul(2,3,4,5,6,7) // 5040
函式和Closure真的是不同的型別麼?
提起closure,如果你有過其他程式語言的經歷,你可能會立即聯想起一些類似的事物,例如:匿名函式、或者可以捕獲變數的一對{}
等等。但實際上,我們很容易搞混兩個概念:Closure expression和Closure。他們究竟是什麼呢?我們先從closure expression開始。
理解Closure Expression
簡單來說,closure expression就是函式的一種簡寫形式。例如,對於下面這個計算引數平方的引數:
func square(n: Int) -> Int {
return n*n
}
我們也可以這樣來定義:
let squareExpression = { (n: Int) -> Int in
return n*n
}
在呼叫的時候是完全相同的:
square(2) // 4
squareExpression(2) // 4
並且它們都可以當做函式的引數來使用:
let numbers = [1, 2, 3, 4, 5]
numbers.map(square) // [1, 4, 9 ,16, 25]
numbers.map(squareExpressions) // [1, 4, 9 ,16, 25]
我們在這個例子裡,用於定於squareExpressions
的{}
就叫做closure expression,它只是把函式引數、返回值以及實現統統寫在了一個{}裡。
沒錯,此時的{}
以及squareExpressions
並不能叫做closure,它只是一個closure expression。那麼,為什麼要有兩種不同的方式來定義函式呢?最直接的理由就是,為了寫起來更簡單。Closure expression可以再定義它的上下文裡,被不斷的簡化,讓程式碼儘可能的呈現出最自然的語義形態。
例如,當我們把一個完成的closure expression定義在map
引數裡,是這樣的:
numbers.map ({ (n: Int) -> Int in
return n * n
})
首先Swift可以根據numbers
的型別,自動推匯出map
中的函式引數以及返回值的型別,因此,我們可以在closure expression中去掉它:
numbers.map ({ n in return n * n })
其次,如果closure expression中只有一條語句,Swift可以自動把這個語句的值作為整個expression的值返回,因此,我們還可以去掉return關鍵字:
numbers.map ({ n in n * n })
第三,如果你覺得在closure expression中為引數起名字是個意義不大的事情,我們還可以使用Swift內建的$0/$1/$2/$3
這樣的形式作為closure expression的引數替代符,這樣,我們連引數宣告和in
關鍵字也可以省略了:
numbers.map ({ $0 * $0 })
第四,如果函式型別的引數在引數列表的最後一個,我們還可以把closure expression寫在()
外面,讓它和其它普通引數更明顯的區分開:
numbers.map(){ $0 * $0 }
最後,如果函式只有一個函式型別的引數,我們甚至可以再呼叫的時候,去掉()
:
numbers.map { $0 * $0 }
看到這裡,就應該知道當我們把closure expression用在它的上下文裡,究竟有多方便了,相比一開始的定義,或者單獨定義一個函式,然後傳遞給它,都好太多。但事情至此還沒結束,相比這樣:
numbers.sorted(by: {$0 > $1}) // [5,4,3,2,1]
closure expression 還有一種更簡單的形式:
numbers.sorted(by: > ) // [5,4,3,2,1]
這是因為,numbers.sorted(by:)
的函式引數是這樣的:(Int ,Int) -> Bool
,而Swift為Int
型別定義的>
操作符也正好和這個型別相同,這樣,我們就可以直接把操作符傳遞給它,本質上,這和我們傳遞函式名是一樣的。
另外,除了寫起來更簡單之外,closure expression還有一個副作用,就是預設情況下,我們無法忽略它的引數,編譯器會對這種情況報錯。看個例子,如果我們要得到一個包含了10個隨機數的Array
,最簡單的方法,就是一個CountableRange
呼叫map
方法:
(0 ... 9).map { arc4random() } // Error in Swift
這樣看似很好,但是由於map
的函式引數預設是帶有一個引數的,在我們的例子裡,表示range中的每個值,因此,如果我們在整個closure expression裡都沒有使用這個引數,Swift編譯器就會提示我們錯誤。
我們不能預設忽略closure expression中的引數,如果堅持如此,我們必須用_
明確表明這個意圖:
(0 ... 9).map { _ in arc4random() }
這也算是Swift為了型別和程式碼安全,利用編譯器,為我們提供的一層保障。以上,就是和closure expression有關的內容,如你看到的一樣,它就是函式的另外一種在上下文中更簡單的寫法和用func
定義的函式沒有任何區別。
究竟什麼是closure
如果我們翻翻Wikipedia
,就能找到下面的定義:a closure is a record storing a function together with an environment。
說的通俗一點,一個函式加上它捕獲的變數一起,才算一個closure
。來看個例子:
func makeCounter() -> () -> Int {
var value = 0
return {
value += 1
return value
}
}
makeCounter()
返回一個函式,用來返回它被呼叫的次數。然後,我們分別定義兩個計數器,並各自呼叫幾次:
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
這樣,三次呼叫counter1()
會在控制檯列印"123",6次呼叫會列印“123456”。這說明什麼呢?
首先,儘管從makeCounter
返回後,value
已經離開了它的作用域,但我們多次呼叫counter1
或counter2
時,value
的值還是各自進行了累加。這就說明,makeCounter
返回的函式,捕獲了makeCounter
的內部變數value。
其次,counter1
和counter2
分別有其各自捕獲的value
,也就是其各自的上下文環境,他們並不共享任何內容。
理解了closure的含義之後,我們就知道了,closure expression和closure並不是一回事兒。然後,捕獲變數是{}
的專利麼?實際上也不是,函式也可以捕獲變數。
函式同樣可以是一個Closure
還是之前makeCounter
的例子,我們把返回的closure expression改成一個local function:
func makeCounter() -> () -> Int {
var value = 0
return increase() -> Int {
value += 1
return value
}
return increase
}
然後你就會發現,之前counter1
和counter2
的例子的執行結果,和之前是一樣的:
(0...2).forEach { _ in print(counter1()) } // 1 2 3
(0...5).forEach { _ in print(counter2()) } // 1 2 3 4 5 6
所以,捕獲變數這種行為,實際上,跟用{}
定義函式也沒關係。
相關文章
- Lua的function、closure和upvalue(轉)Function
- 食品加工MES系統如何實現資料採集和裝置管理
- Javascript ClosureJavaScript
- PHP 中 call_user_func 的使用PHP
- MaxCompute幫你五步實現使用者畫像的資料加工
- 北京雲棲大會workshop:《資料處理:資料建模與加工》篇
- Closure的應用和替代方案比較
- Java中的閉包(Closure)和回撥Java
- JavaScript Closure MemorizationJavaScript
- Js closure and bindJS
- 使用者管理和資料庫安全(一)資料庫
- ADAMoracle將資訊彙總加工使資料可以被區塊鏈使用且有意義Oracle區塊鏈
- 徹底弄懂C#中delegate、event、EventHandler、Action、Func的使用和區別C#
- Asp.Net中的Action和Func委託ASP.NET
- call_user_func()與call_user_func_array函式函式
- 使用Spark和Cassandra進行資料處理(一)Spark
- JS閉包ClosureJS
- PHP 閉包(Closure)PHP
- Javascript閉包(Closure)JavaScript
- 使用 deploy 部署專案時報 Serialization of 'Closure' is not allowed 錯誤
- 【資料庫使用-oracle索引的建立和分類】一資料庫Oracle索引
- PHP 中`Closure`和`Callable`的區別以及在 Redis 訂閱方法中的使用PHPRedis
- Golang中十分nice的一個func技巧Golang
- 機械加工如何使用ERP管理系統?
- php 使用Callable Closure強制指定回撥型別PHP型別
- (一)如何使用 Parsel 和 XPath 進行網頁資料提取網頁
- 8使用資料庫和表資料庫
- split使用和特殊使用(包括擷取第一個字元後的資料)字元
- i_init_func_execute_data
- JS中的 閉包(Closure)JS
- 學習Javascript閉包(Closure)JavaScript
- 達觀智慧文字分析系統,賦能企業大資料加工處理大資料
- JSP資料和JavaScirpt資料互動使用問題的一種解決方法 (轉)JSJava
- 一文說清資料管理、資料治理和資料資產管理
- Mac下使用Google Drive和Dropbox同步同一個資料夾MacGo
- go func 時發生了什麼Go
- JS-閉包(closure)的理解JS
- 閉包捕捉(closure capture)淺析APT