函式是一段完成特定任務的獨立程式碼片段。可以通過給函式命名來標識某個函式的功能。這個名字可以被用來在需要的時候呼叫這個函式來完成它的任務。
Swift統一的函式語法非常靈活,可以用來表示任何函式,包括從簡單的沒有引數名字的C風格函式,到複雜的帶區域性和外部引數名的OC風格。引數可以提供預設值,可以簡化函式呼叫。引數也可以既當做傳入引數,也當做傳出引數,也就是說,一旦函式執行結束,傳入的引數值將被修改。
在Swift中,每個函式都有一個由函式的引數值型別和返回值型別組成的型別。可以把函式型別當做任何其他普通變數型別一樣處理,這樣就可以簡單的把函式當做別的函式的引數,也可以從其他函式中返回函式。函式的定義可以寫在其他函式定義中,這樣可以巢狀函式範圍內實現功能封裝。
本文涉及的內容有函式定義與呼叫
、函式引數與返回值
、函式引數標籤和引數名稱
、函式型別
、巢狀函式
函式的定義與呼叫
- 當定義一個函式時,可以定義一個或者多個有名字和型別的值作為函式的輸入,稱為引數,也可以定義某種型別的值作為函式執行結束的輸出,稱為返回型別。每個函式有個函式名(方法名)。使用函式名來呼叫這個函式,傳入輸入值(實參)。返回對應的返回值。舉例如下:
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
print(greet(person: "Anna"))
// 列印 "Hello, Anna!"
print(greet(person: "Brian"))
// 列印 "Hello, Brian!"
複製程式碼
注: 在greet(person:)
的函式體中,先定義了一個新的名為greeting
的String
常量。同時,把對personName
的問候訊息賦值給了greeting
。然後用return
關鍵字把這個問候返回出去。一旦return greening
被呼叫,該函式結束它的執行並返回greeting
的當前值。
函式引數與返回值
- 函式可以沒有引數或者多個引數,可以沒有返回值或者多重返回值。
無引數函式
無引數函式,返回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!"
複製程式碼
注: 嚴格來說,雖然返回值沒有被定義,但是green(person:)
函式依然返回了值。沒有定義返回型別的函式會返回一個特殊的Void
值。它其實是一個空的元祖,沒有任何元素,可以寫成()
。
多重返回值
- 可以用元組型別讓多個值作為一個複合值從函式中返回。舉例如下:
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)
}
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"
複製程式碼
注: 元組的成員不需要在元組從函式中返回時命名,因為它們的名字已經在函式返回型別中指定了。
可選元組返回型別
- 如果函式返回的元組型別沒有值,可以使用可選的元組返回型別反映整個元組可以是
nil
的事實。通過在元組型別的右括號後放置一個問號來定義一個可選元組。前面的minMax(array:)
函式返回了一個包含兩個Int
值的元組。但是函式不會對傳入的陣列執行任何安全檢查,如果array
引數是一個空陣列,執行就可能會報錯,所以就需要進行容錯處理。舉例如下:
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)
}
複製程式碼
注: 可選元組型別如 (Int, Int)?
與元組包含可選型別如 (Int?, Int?)
是不同的。可選的元組型別,整個元組是可選的,而不只是元組中的每個元素值。
函式引數標籤和引數名稱
- 每個函式引數都有一個引數標籤以及一個引數名稱。引數標籤在呼叫函式的時候使用。呼叫的時候需要將函式的引數標籤寫在對應的引數前面。引數名稱在函式的實現中使用。預設情況下,函式引數使用引數名稱來作為它們的引數標籤。為了避免多個引數有同樣的引數標籤,命名儘量清楚,這樣會使程式碼的可讀性更強。
指定引數標籤
- 可以在引數名稱前指定它的引數標籤,中間以空格分隔。舉例如下:
func someFunction(argumentLabel parameterName: Int) {
// argumentLabel 即為引數標籤
// 在函式體內,parameterName 代表引數值
}
複製程式碼
- 如果你不希望為某個引數新增一個標籤,可以使用一個下劃線(
_
)來代替一個明確的引數標籤 - 呼叫時,有引數標籤、(
_
)代替引數標籤和無引數標籤對比如下:
func someFunction(parameterName: Int) {}
func someFunctions(_ parameterName: Int) {}
func someFunctions_(argumentLabel parameterName: Int) {}
someFunction(parameterName: 3)
someFunctions(3)
someFunctions_(argumentLabel: 3)
複製程式碼
預設引數
可以在函式體中通過給引數賦值來為任意一個引數定義預設值。當預設值被定義後,呼叫這個函式可以忽略這個引數。舉例如下:
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// 如果你在呼叫時候不傳第二個引數,parameterWithDefault 會值為 12 傳入到函式體中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
複製程式碼
多個引數
- 函式的引數可以是零個或多個。函式呼叫時。通過在變數型別名後面加入(
...
)的方式來定義多個引數(可變引數)。 - 多個引數的傳入值在函式體中變為此型別的一個陣列。例如一個叫做
numbers
的Double...
型可變引數,在函式體內可以當做一個叫numbers
的[Double]
型的陣列常量。舉例如下:
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 個數的平均數。
複製程式碼
輸入輸出引數
- 函式引數預設值是常量。試圖在函式體中更改引數值將會導致編譯錯誤。這意味著你不能錯誤的更改引數值。如果你想要一個函式可以修改引數值,並且想要這些修改在函式呼叫結束後仍然存在,那麼就應該把這個引數定義為輸入輸出引數。
- 定義一個輸入輸出引數時,在引數定義前加
inout
關鍵字。一個輸入輸出引數
有傳入函式的值,這個值被函式修改,然後被傳出函式,替換原來的值。 - 在傳遞時只能傳遞變數給輸入輸出引數。不能傳入常量。當傳入的引數作為輸入輸出引數時,需要在引數名前加
&
符表示這個值可以被修改。 - 輸入輸出引數不能有預設值,而且可變引數不能用
inout
標記。
一個簡單的交換值方法舉例如下:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
複製程式碼
- 可以用兩個
Int
型的變數來呼叫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"
複製程式碼
注: 輸入輸出引數和返回值是不一樣的。上面的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")
}
複製程式碼
這個函式的型別是:() -> Void
,或者叫“沒有引數,並返回 Void
型別的函式”。
使用函式型別
- 在Swift中,使用函式型別就像使用其他型別一樣。例如,可以定義一個型別為函式的常量或者變數,並將適合的函式賦值給它,舉例如下:
var mathFunction: (Int, Int) -> Int = addTwoInts
複製程式碼
”定義一個叫做 mathFunction
的變數,型別是‘一個有兩個 Int
型的引數並返回一個 Int
型的值的函式’,並讓這個新變數指向 addTwoInts
函式”。
addTwoInts
和 mathFunction
有同樣的型別,所以這個賦值過程在 Swift 型別檢查中是允許的。
現在,可以用 mathFunction
來呼叫被賦值的函式如下:
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"
複製程式碼
有相同匹配型別的不同函式可以被賦值給同一個變數,就像非函式型別的變數一樣:
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"
複製程式碼
就像其他型別一樣,當賦值一個函式給常量或變數時,你可以讓 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"
複製程式碼
這個例子定義了 printMathResult(_:_:_:)
函式,它有三個引數:第一個引數叫 mathFunction
,型別是 (Int, Int) -> Int
,第二個和第三個引數叫 a
和 b
,它們的型別都是 Int
。printMathResult(_:_:_:)
中實現mathFunction
的方法,輸出結果8
。
函式作為返回型別
- 還可以用函式的型別當做另一個函式的返回型別
(->)
後面需要寫一個完整的函式型別。舉例如下:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 現在指向 stepBackward() 函式。
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("end!")
// 3...
// 2...
// 1...
// end!
複製程式碼
上述例子中, chooseStepFunction(backward:)
的函式,它的返回型別是 (Int) -> Int
型別的函式,而stepForward(_:)
和 stepBackward(_:)
實際就是 (Int) -> Int
的型別。當傳入3時,執行stepBackward(_:)
,然後重新複製給函式外部的currentValue
。
巢狀函式
- 到目前為止本文中你所見的函式都叫全域性函式, 他們定義在全域性中,也可以把函式定義在別的函式體中,成為巢狀函式,預設情況下巢狀函式是對外界不可見的,但是可以被它們的外圍函式呼叫。一個外圍函式也可以是返回巢狀函式,似的這個函式可以在其他域中被使用。
- 用返回巢狀函式的方式重寫
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("end!")
// -4...
// -3...
// -2...
// -1...
// end!
複製程式碼