標籤:
函數
Swift的函數文法非常獨特,也提供了很高的靈活性和可讀性。它可以充分表達從簡單的無參數C風格函數到複雜的擁有局部變數和外部變數的OC風格的方法。參數可以有預設值,方便函數的調用。Swift中的每個函數都有一個類型,由其參數類型和傳回值類型組成,這個類型可以像Swift中的任何其他類型一樣被使用,因此,函數被作為參數傳遞,或者從一個函數返回一個函數等。函數也可以嵌套函數形成嵌套鏈。
定義和調用函數
定義函數時,可以給它定義一個或多個參數和傳回值類型,這些並不是必須的。比如:
func sayHello(personName: String) -> String { let greeting = "Hello, " + personName + "!" return greeting}
如果函數不需要傳回值,那麼“->type”就可以省略掉,其實此時函數仍然返回了一個特殊Void類型的值,它是一個空的元組(tuple)。
函數也可以同時返回多個值,這就是返回一個包含多個值的元組。比如:
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)}
因為函數返回的元組的元素是已經定義了名稱的,因此可以通過點文法來擷取它的元素,比如minMax([1,2,3]).min。在函數體裡邊返回元組的時候,不需要標明元素的名字,因為這些名字在函數定義的時候就已經給出了。
返回可選元群組類型
如果返回的元組可能存在整個元組都沒有值,則可使用可選元組傳回型別(optional tuple return type)來表明這個傳回值可能是nil。這種類型可以寫為:(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)}
之後,就可以用可選綁定來確定該函數的傳回值是否有值了:
if let bounds = minMax([8, -6, 2, 109, 3, 71]) { println("min is \(bounds.min) and max is \(bounds.max)")}// prints "min is -6 and max is 109”
函數參數名稱
函數的參數都可以指定名稱,然後就可以在函數體裡使用這些名稱了:
func someFunction(parameterName: Int) { // function body goes here, and can use parameterName // to refer to the argument value for that parameter}
不過,這裡的參數名只能在函數體內部使用,所以又稱之為局部參數名稱(local parameter name)。這些名稱可以用來在表示傳入參數的意義。如果希望函數調用者在調用函數時提供參數名,就可以在局部參數名稱之外再定義外部參數名稱(external parameter name)。它的文法是在局部參數名稱前寫上外部參數名稱,然後用一個空格將這兩個名稱隔開:
func someFunction(externalParameterName localParameterName: Int) { // function body goes here, and can use localParameterName // to refer to the argument value for that parameter}
注意:如果你提供了外部參數名稱,那麼在調用函數的時候,必須每次都使用外部參數名稱。
func join(s1: String, s2: String, joiner: String) -> String { return s1 + joiner + s2}join("hello", "world", ", ")//對比func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String { return s1 + joiner + s2}join(string: "hello", toString: "world", withJoiner: ", ")
這種方式提供名稱的靈活性,在調用函數的時候是以一種更清晰的語義化方式傳遞參數的,而在函數內部,則可以使用比較簡潔的方式來標識參數。大大提高了代碼的可讀性。
如果你的局部參數名稱已經定義了,又想把它也作為外部參數名稱,那麼可以使用簡寫文法而不需要把名稱重寫一遍,形式是在參數名稱之前加上#符號。比如:
func containsCharacter(#string: String, #characterToFind: Character) -> Bool { for character in string { if character == characterToFind { return true } } return false}
預設參數值
作為函數定義的一部分,你可以為參數設定預設值,如果參數具備預設值,那麼在調用函數的時候可以省略該參數。
在設定參數預設值時,最佳實務是將具有預設值的參數放置在參數列表的最後,這樣可以確保在多次函數調用的時候,不具備預設值的參數順序都是保持一致的,這樣就可以確定這些多次調用是調用的同一個函數。設定參數預設值的文法如下:
func join(string s1: String, toString s2: String, withJoiner joiner: String = " ") -> String { return s1 + joiner + s2}
可以看出,如果為參數提供了預設值,那麼最佳實務就是總是為設定了預設值的參數設定外部參數名稱,這就使得在函數調用的時候,不能省略這個參數,保持了函數調用方式的一致性和可讀性,為了簡便,Swift提供了便捷的特性,就是給所有設定了預設值的參數自動加上了外部參數名稱,這個外部參數名稱和其局部參數名稱是相同的,就相當於前面所講的(#)符號,這就使得設定了預設值的參數在被調用時也必須顯示地給出:
func join(s1: String, s2: String, joiner: String = " ") -> String { return s1 + joiner + s2}join("hello", "world", joiner: "-")//這裡必須顯示給定joiner// returns "hello-world”
這裡在調用函數的時候,如果你實在不想寫給定了預設值的參數名,那麼可以用底線(_)代替顯示給定的外部名稱,不過並不推薦這樣做。
可變參數
一個可變參數接收0個或多個某個指定類型的值。使用可變參數意味著在調用該函數時可以向該參數傳遞可變數目的值。可變參數的聲明文法是在參數的類型名後邊加上三個點(...),向可變參數傳遞的值在函數體內部被組合成了一個特定類型的數組,比如:
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)// returns 3.0, which is the arithmetic mean of these five numbersarithmeticMean(3, 8.25, 18.75)// returns 10.0, which is the arithmetic mean of these three numbers
注意:一個函數最多隻能有一個可變參數,並且它必須位於參數列表的最後,這是為了避免在函數調用的時候多參數時的混淆不清。如果函數有一個或者多個指定預設值的參數,同時又有可變參數,那麼把可變參數放置在所有指定預設值的參數後邊,即列表的最後邊。
常量和變數參數
函數的參數預設都是常量,在函數內部嘗試改變參數的值會觸發編譯錯誤,這使得誤操作改變參數值不會發生。
但是,有時候需要函數有一個參數的變數副本,通過指定一個或多個參數為變數參數,就不用再在函數裡邊重新定義變數了。變數參數是作為變數而非常量使用,它提供了一個可改變的值副本來供你的函數操作。
通過在參數名稱前面加上var關鍵字就可以定義一個變數參數:
func alignRight(var string: String, count: Int, pad: Character) -> String { let amountToPad = count - count(string) if amountToPad < 1 { return string } let padString = String(pad) for _ in 1...amountToPad { string = padString + string } return string}let originalString = "hello"let paddedString = alignRight(originalString, 10, "-")// paddedString is equal to "-----hello"// originalString is still equal to "hello”
注意:對變數參數的修改在函數調用結束之後就不存在了,並且變數參數在函數外部是不可見的,它只存在於函數調用的生命週期內。
輸入輸出參數(In-Out Parameters)
如前所述,變數參數是可以也只能在函數內部被改變,如果你希望函數改變一個參數,並且函數調用結束後仍然有效,就需要將那個參數定義為輸入輸出參數。通過在參數定義之前加上inout關鍵字,可以建立一個in-out參數。一個in-out參數有一個被傳入函數的值,這個值在函數內部被改變,然後被傳回到函數之外替換這個參數的初始值。只能用變數建立輸入輸出參數,常量和字面量都不可以。在調用函數時,在變數名稱之前加上(&)號標明這個參數可以被函數修改:
func swapTwoInts(inout a: Int, inout b: Int) { let temporaryA = a a = b b = temporaryA}var someInt = 3var anotherInt = 107swapTwoInts(&someInt, &anotherInt)println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")// prints "someInt is now 107, and anotherInt is now 3”
注意:in-out參數不能有預設值,可變參數(variadic parameters)也不能被標記為inout,如果標記了一個參數為inout,那麼不能同時把它標記為var或者let了。
in-out參數並不同於函數的傳回值,如上面的例子,函數並沒有返回任何值,它是函數能夠在函數體範圍外產生影響的一種有效方法。
函數類型(Function Types)
每個函數都有一個特定的函數類型,它是由函數的參數類型和傳回值類型組成的。比如:
func addTwoInts(a: Int, b: Int) -> Int { return a + b}func multiplyTwoInts(a: Int, b: Int) -> Int { return a * b}
這兩個簡單的函數都接受兩個Int型別參數,然後返回一個Int類型值,因此它們的函數類型都是 (Int, Int) -> Int 。這個讀作“一個包含兩個Int型參數並且返回一個Int類型值的函數類型”。如果函數沒有參數,也不傳回值,則函數類型就是 () -> () 。後邊這對空括弧表示函數返回Void,它在Swift中被表示為空白的元組。
函數類型可以像Swift中的所有其他類型一樣被使用,比如你可以定義一個某函數類型的變數或者常量,並且將某個合適的函數賦值給它:
var mathFunction: (Int, Int) -> Int = addTwoInts
然後就可以像調用普通函數一樣把這個變數當做函數調用了。
只要函數類型是一樣的,另一個不同的函數也可以被賦值給同一個變數。
作為參數類型的函數類型
函數類型同樣也可以作為其他函數的參數類型,這就可以將函數的一部分邏輯實現交給函數的調用者去實現,然後作為參數傳進函數。比如:
func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) { println("Result: \(mathFunction(a, b))")}printMathResult(addTwoInts, 3, 5)// prints "Result: 8”
這個例子中,mathFunction的實現是交給函數調用者實現的,比如傳入的addTwoInts。
作為傳回型別的函數類型
函數類型也可以作為傳回型別被其他函數返回。比如:
func stepForward(input: Int) -> Int { return input + 1}func stepBackward(input: Int) -> Int { return input - 1}func chooseStepFunction(backwards: Bool) -> (Int) -> Int { return backwards ? stepBackward : stepForward}
注意這個chooseStepFunction函數的傳回型別,它是一個函數類型((Int) -> Int),因此這個函數最終返回的其實就是一個函數。
嵌套函數
之前介紹的函數都是在全域範圍下定義的,因此都是全域函數。函數也可以在函數內部被定義,此時就是嵌套函數了,它的範圍在函數內部,對外預設是隱藏的。但是,當外層函數把嵌套函數當做傳回值返回以後,外部環境就可以其他範圍調用這個內部嵌套函數了(閉包的概念)。比如,上面的例子,就可以用嵌套函數的形式實現:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int { func stepForward(input: Int) -> Int { return input + 1 } func stepBackward(input: Int) -> Int { return input - 1 } return backwards ? stepBackward : stepForward}var currentValue = -4let moveNearerToZero = chooseStepFunction(currentValue > 0)// moveNearerToZero now refers to the nested stepForward() functionwhile currentValue != 0 { println("\(currentValue)... ") currentValue = moveNearerToZero(currentValue)}println("zero!")// -4...// -3...// -2...// -1...// zero!
Swift學習筆記八