Swift-函式(Functions)

優質神經病發表於2018-07-19

函式是一段完成特定任務的獨立程式碼片段。可以通過給函式命名來標識某個函式的功能。這個名字可以被用來在需要的時候呼叫這個函式來完成它的任務。

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:)的函式體中,先定義了一個新的名為greetingString常量。同時,把對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
複製程式碼

多個引數

  • 函式的引數可以是零個或多個。函式呼叫時。通過在變數型別名後面加入(...)的方式來定義多個引數(可變引數)。
  • 多個引數的傳入值在函式體中變為此型別的一個陣列。例如一個叫做numbersDouble...型可變引數,在函式體內可以當做一個叫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函式並沒有定義任何返回值,但仍然修改了someIntanotherInt的值。輸入輸出引數是函式對函式體外產生影響的另一種方式。

函式型別

每個函式都有種特定的函式型別,函式的型別是由函式的引數型別和返回型別組成。 例如:

func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
    return a * b
}
複製程式碼

這個例子中定義了兩個簡單的數學函式:addTwoIntsmultiplyTwoInts。這兩個函式都接受兩個 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 函式”。

addTwoIntsmathFunction 有同樣的型別,所以這個賦值過程在 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,第二個和第三個引數叫 ab,它們的型別都是 IntprintMathResult(_:_:_:)中實現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!
複製程式碼

相關文章