6、Swift函式
函式是一段完成特定任務的獨立程式碼片段。你可以通過給函式命名來標識某個函式的功能,這個名字可以被用來在需要的時候“呼叫”這個函式來完成它的任務。
函式的定義與呼叫
當你定義一個函式時,你可以定義一個或多個有名字和型別的值,作為函式的輸入,稱為引數,也可以定義某種型別的值作為函式執行結束時的輸出,稱為返回型別。
每個函式有個函式名,用來描述函式執行的任務。要使用一個函式時,用函式名來“呼叫”這個函式,並傳給它匹配的輸入值(稱作實參)。函式的實參必須與函式引數表裡引數的順序一致。
func greet(person: String) -> String { let greeting = "Hello, " + person + "!" return greeting }
所有的這些資訊彙總起來成為函式的定義,並以 func
作為字首。指定函式返回型別時,用返回箭頭 ->
(一個連字元後跟一個右尖括號)後跟返回型別的名稱的方式來表示。
函式引數與返回值
函式引數與返回值在 Swift 中非常的靈活。你可以定義任何型別的函式,包括從只帶一個未名引數的簡單函式到複雜的帶有表達性引數名和不同引數選項的複雜函式。
無引數函式
函式可以沒有引數。下面這個函式就是一個無引數函式,當被呼叫時,它返回固定的 String
訊息:
func sayHelloWorld() -> String { return "hello, world" } print(sayHelloWorld()) // 列印“hello, world”
多引數函式
函式可以有多種輸入引數,這些引數被包含在函式的括號之中,以逗號分隔。
func greet(person: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { return greetAgain(person: person) } else { return greet(person: person) } } print(greet(person: "Tim", alreadyGreeted: true)) // 列印“Hello again, Tim!”
無返回值函式
函式可以沒有返回值。
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// 列印“Hello, Dave!”
呼叫函式時,可以忽略該函式的返回值:
func printAndCount(string: String) -> Int {
print(string)
return string.count
}
func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// 列印“hello, world”,並且返回值 12
printWithoutCounting(string: "hello, world")
// 列印“hello, world”,但是沒有返回任何值
多重返回值函式
你可以用元組(tuple
)型別讓多個值作為一個複合值從函式中返回。
下例中定義了一個名為 minMax(array:)
的函式,作用是在一個 Int
型別的陣列中找出最小值與最大值。
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
可選元組返回型別
如果函式返回的元組型別有可能整個元組都“沒有值”,你可以使用可選的 元組返回型別反映整個元組可以是 nil
的事實。你可以通過在元組型別的右括號後放置一個問號來定義一個可選元組,例如 (Int, Int)?
或 (String, Int, Bool)?
注意
可選元組型別如(Int, Int)?
與元組包含可選型別如(Int?, Int?)
是不同的。可選的元組型別,整個元組是可選的,而不只是元組中的每個元素值。
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
你可以使用可選繫結來檢查 minMax(array:)
函式返回的是一個存在的元組值還是 nil
:
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
// 列印“min is -6 and max is 109”
隱式返回的函式
如果一個函式的整個函式體是一個單行表示式,這個函式可以隱式地返回這個表示式。
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// 列印 "Hello, Dave!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// 列印 "Hello, Dave!"
greeting(for:)
函式的完整定義是打招呼內容的返回,這就意味著它能使用隱式返回這樣更簡短的形式。anothergreeting(for:)
函式返回同樣的內容,卻因為 return
關鍵字顯得函式更長。任何一個可以被寫成一行 return
語句的函式都可以忽略 return
。
函式引數標籤和引數名稱
每個函式引數都有一個引數標籤(argument label
)以及一個引數名稱(parameter name
)。引數標籤在呼叫函式的時候使用;呼叫的時候需要將函式的引數標籤寫在對應的引數前面。引數名稱在函式的實現中使用。預設情況下,函式引數使用引數名稱來作為它們的引數標籤。
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// 在函式體內,firstParameterName 和 secondParameterName 代表引數中的第一個和第二個引數值
}
someFunction(firstParameterName: 1, secondParameterName: 2)
所有的引數都必須有一個獨一無二的名字。雖然多個引數擁有同樣的引數標籤是可能的,但是一個唯一的引數標籤能夠使你的程式碼更具可讀性。
指定引數標籤
你可以在引數名稱前指定它的引數標籤,中間以空格分隔:
func someFunction(argumentLabel parameterName: Int) {
// 在函式體內,parameterName 代表引數值
}
這個版本的 greet(person:)
函式,接收一個人的名字和他的家鄉,並且返回一句問候:
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 列印“Hello Bill! Glad you could visit from Cupertino.”
引數標籤的使用能夠讓一個函式在呼叫時更有表達力,更類似自然語言,並且仍保持了函式內部的可讀性以及清晰的意圖
忽略引數標籤
如果你不希望為某個引數新增一個標籤,可以使用一個下劃線(_
)來代替一個明確的引數標籤。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// 在函式體內,firstParameterName 和 secondParameterName 代表引數中的第一個和第二個引數值
}
someFunction(1, secondParameterName: 2)
如果一個引數有一個標籤,那麼在呼叫的時候必須使用標籤來標記這個引數。
預設引數值
你可以在函式體中通過給引數賦值來為任意一個引數定義預設值(Deafult Value
)。當預設值被定義後,呼叫這個函式時可以忽略這個引數。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// 如果你在呼叫時候不傳第二個引數,parameterWithDefault 會值為 12 傳入到函式體中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
可變引數
一個可變引數(variadic parameter
)可以接受零個或多個值。函式呼叫時,你可以用可變引數來指定函式引數可以被傳入不確定數量的輸入值。通過在變數型別名後面加入(...
)的方式來定義可變引數。
可變引數的傳入值在函式體中變為此型別的一個數組。例如,一個叫做 numbers
的 Double...
型可變引數,在函式體內可以當做一個叫 numbers
的 [Double]
型的陣列常量。
下面的這個函式用來計算一組任意長度數字的 算術平均數(arithmetic mean):
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 返回 3.0, 是這 5 個數的平均數。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是這 3 個數的平均數。
注意
一個函式最多隻能擁有一個可變引數。
輸入輸出引數
函式引數預設是常量。試圖在函式體中更改引數值將會導致編譯錯誤。這意味著你不能錯誤地更改引數值。如果你想要一個函式可以修改引數的值,並且想要在這些修改在函式呼叫結束後仍然存在,那麼就應該把這個引數定義為輸入輸出引數(In-Out Parameters)。
定義一個輸入輸出引數時,在引數定義前加 inout 關鍵字。一個 輸入輸出引數有傳入函式的值,這個值被函式修改,然後被傳出函式,替換原來的值。想獲取更多的關於輸入輸出引數的細節和相關的編譯器優化,請檢視 輸入輸出引數 一節。
你只能傳遞變數給輸入輸出引數。你不能傳入常量或者字面量,因為這些量是不能被修改的。當傳入的引數作為輸入輸出引數時,需要在引數名前加 &
符,表示這個值可以被函式修改。
注意
輸入輸出引數不能有預設值,而且可變引數不能用 inout 標記。
下例中,swapTwoInts(_:_:)
函式有兩個分別叫做 a
和 b
的輸入輸出引數:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoInts(_:_:)
函式簡單地交換 a
與 b
的值。該函式先將 a
的值存到一個臨時常量 temporaryA
中,然後將 b
的值賦給 a
,最後將 temporaryA
賦值給 b
。
你可以用兩個 Int
型的變數來呼叫 swapTwoInts(_:_:)
。需要注意的是,someInt
和 anotherInt
在傳入 swapTwoInts(_:_:)
函式前,都加了 &
的字首:
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 列印“someInt is now 107, and anotherInt is now 3”
從上面這個例子中,我們可以看到 someInt
和 anotherInt
的原始值在 swapTwoInts(_:_:)
函式中被修改,儘管它們的定義在函式體外。
注意
輸入輸出引數和返回值是不一樣的。上面的 swapTwoInts 函式並沒有定義任何返回值,但仍然修改了 someInt 和 anotherInt 的值。輸入輸出引數是函式對函式體外產生影響的另一種方式。
函式型別
每個函式都有種特定的函式型別,函式的型別由函式的引數型別和返回型別組成。
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
這個例子中定義了兩個簡單的數學函式:addTwoInts
和 multiplyTwoInts
。這兩個函式都接受兩個 Int
值, 返回一個 Int
值。
這兩個函式的型別是 (Int, Int) -> Int
,可以解讀為:
“這個函式型別有兩個 Int
型的引數並返回一個 Int
型的值”。
下面是另一個例子,一個沒有引數,也沒有返回值的函式
func printHelloWorld() {
print("hello, world")
}
使用函式型別
在 Swift 中,使用函式型別就像使用其他型別一樣。例如,你可以定義一個型別為函式的常量或變數,並將適當的函式賦值給它:
var mathFunction: (Int, Int) -> Int = addTwoInts
這段程式碼可以被解讀為:
”定義一個叫做 mathFunction
的變數,型別是‘一個有兩個 Int
型的引數並返回一個 Int
型的值的函式’,並讓這個新變數指向 addTwoInts
函式”
addTwoInts
和 mathFunction
有同樣的型別,所以這個賦值過程在 Swift
型別檢查(type-check
)中是允許的。
現在,你可以用 mathFunction
來呼叫被賦值的函數了:
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"
就像其他型別一樣,當賦值一個函式給常量或變數時,你可以讓 Swift 來推斷其函式型別:
let anotherMathFunction = addTwoInts
// anotherMathFunction 被推斷為 (Int, Int) -> Int 型別
函式型別作為引數型別
你可以用 (Int, Int) -> Int
這樣的函式型別作為另一個函式的引數型別。這樣你可以將函式的一部分實現留給函式的呼叫者來提供。
下面是另一個例子,正如上面的函式一樣,同樣是輸出某種數學運算結果:
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 列印“Result: 8”
函式型別作為返回型別
你可以用函式型別作為另一個函式的返回型別。你需要做的是在返回箭頭(->
)後寫一個完整的函式型別。
下面的這個例子中定義了兩個簡單函式,分別是 stepForward(_:)
和 stepBackward(_:)
。stepForward(_:)
函式返回一個比輸入值大 1
的值。stepBackward(_:)
函式返回一個比輸入值小 1
的值。這兩個函式的型別都是 (Int) -> Int:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
如下名為 chooseStepFunction(backward:)
的函式,它的返回型別是 (Int) -> Int
型別的函式。chooseStepFunction(backward:)
根據布林值 backwards
來返回 stepForward(_:)
函式或 stepBackward(_:)
函式:
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
你現在可以用 chooseStepFunction(backward:)
來獲得兩個函式其中的一個:
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 現在指向 stepBackward() 函式。
上面這個例子中計算出從 currentValue
逐漸接近到0是需要向正數走還是向負數走。currentValue
的初始值是 3
,這意味著 currentValue > 0
為真(true
),這將使得 chooseStepFunction(_:)
返回 stepBackward(_:)
函式。一個指向返回的函式的引用儲存在了 moveNearerToZero
常量中。
現在,moveNearerToZero
指向了正確的函式,它可以被用來數到零:
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
巢狀函式
到目前為止本章中你所見到的所有函式都叫全域性函式(global functions),它們定義在全域性域中。你也可以把函式定義在別的函式體中,稱作 巢狀函式(nested functions)。
預設情況下,巢狀函式是對外界不可見的,但是可以被它們的外圍函式(enclosing function)呼叫。一個外圍函式也可以返回它的某一個巢狀函式,使得這個函式可以在其他域中被使用。
你可以用返回巢狀函式的方式重寫 chooseStepFunction(backward:) 函式:
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!