方法是執行特殊任務的自包含代碼塊。你可以給方法名字來表示它的功能,而且在需要的時候調用這個名字的方法來執行它的任務。
Swift方法的語法表達很靈活,從類似c的沒有參數名的方法到oc復雜的帶有名字和參數的方法。參數在簡單方法的調用中可以提供默認的初始值,也可以傳入一個變量的參數,當方法執行完后可以修改這個參數。
每個方法在Swift中都有一個類型,其中包括參數類型和返回類型。你可以享其他任何在Swift類型一樣使用這些類型,它可以讓方法更容易的為作為另外一個方法的一個參數值,從方法的返回值獲取返回值。函數也可以寫在其他的函數中,以便在嵌套的函數范圍內封裝有效的功能。
1.函數的聲明與調用
當你定義一個函數的時候,你可能定義一個或者多個名字,函數的傳入值的類型稱為參數。你也可以選擇定義一個方法執行后可能返回值的類行,稱為返回值。
每個函數的名字最好能描述出它的功能。使用函數的時候,調用這個函數的名字並出入一個輸入值(及我們所稱的參數),這個參數的類型要與這個函數的參數類型一致。函數的參數必須始終以與函數的參數列表相同的順序提供。
下面的例子定義了一個greet(person:),該函數輸入一個人的名字,返回一個對這個人的問候字符串。為了實現這個例子,可以定義輸入的參數,參數名為person、類型為String;以及返回一個String類型的返回值。
func greet(person: String) -> String { let greeting = "Hello, " + person + "!" return greeting }
定義一個函數時把所有這些都組合一起,其中前綴用func 關鍵字。你定義函數返回值時用return的箭頭“->”,后面跟着類型的名字返回。
這個函數的定義描述了它的功能是什么,接收什么參數,以及函數執行后返回的參數。這個定義可以讓你在代碼中任何地方更容易清楚的調用。
2.函數的參數與返回值
Swit函數中的參數和返回值是相當靈活的。你可以用從一個功能函數帶一個沒有命名的參數到 帶有表達函數的參數和不同可選的參數的復雜函數定義一切。
(1)沒有參數的函數
函數不需要定義輸入參數。如下的這個函數沒有帶參數,每次調用該函數的時候,返回相同的字符串信息。
func sayHelloWorld() -> String { return "hello, world" } print(sayHelloWorld()) // Prints "hello, world"
函數定義的時候仍然需要在函數名后面加上括號,盡管括號里面並沒有參數。當函數調用的時候,函數名后面把空括號寫上。
(2)函數帶多個參數
函數可以帶有多個輸入參數,寫在括號內,用“,”號分隔開。
func greet(person: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { return greetAgain(person: person) } else { return greet(person: person) } } print(greet(person: "Tim", alreadyGreeted: true)) // Prints "Hello again, Tim!"
(3)函數不帶返回值
函數不需要定義一個返回類型。
func greet(person: String) { print("Hello, \(person)!") } greet(person: "Dave") // Prints "Hello, Dave!"
因為不需要返回一個值,這個函數在定義的時候不包含返回箭頭(->)或者返回值。
注意:嚴格的說,greet(person: String)的函數也有返回值,盡管它的返回並沒有定義。函數沒有定義返回值的類型時通常返回的是一個特殊類型值Void。它是一個簡單的空元組,可以用“()”表示。
調用一個函數的時候,它的返回值可以忽略。
func printAndCount(string: String) -> Int { print(string) return string.count } func printWithoutCounting(string: String) { let _ = printAndCount(string: string) } printAndCount(string: "hello, world") // prints "hello, world" and returns a value of 12 printWithoutCounting(string: "hello, world") // prints "hello, world" but does not return a value
第一個方法 printAndCount(string:,打印一個字符串,然后返回Int類型的字符串個數。第二個方法
printWithoutCounting(string:)調用了第一個方法,但是忽略的它的返回值。當調用第二個函數的時候,第一個函數的信息依然打印,但是它的返回值並沒有用到。
(4)函數帶有多個返回值
你可以用元組的返回類型作為一個函數的多個返回值。
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(array:)這個函數返回一個帶有兩個Int值的元組。
因為元組中的成員值被命名為函數返回值的一部分,它們可以通過點語法來獲取到最小和最大值。
let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) print("min is \(bounds.min) and max is \(bounds.max)") // Prints "min is -6 and max is 109"
注意:tuple的成員不需要在tuple從函數返回的時候被命名,因為它們的名稱已經被指定為函數的返回類型的一部分。
(5)可選的元組返回值
如果從函數返回一個元組類型有可能是一個沒有值的元組類型,你可以用一個可選的元組返回類型去表明這個元組可能為nil。你寫一個可選的元組返回類型通過在元組類型的括號外加上一個“?”號,例如: (Int, Int)?或者(String, Int, Bool)?。
注意:一個可選的元組類型例如(Int,Int)?不同於(Int?, Int?)。對於可選元組類型,是整個元組類型 可選,不是元組內的每一個值可選。
為了解決空數組的安全,編寫一個minMax(array:)函數,這個函數有一個可選元組返回值,當數組為空時,這個返回值為nil。
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)") } // Prints "min is -6 and max is 109"
3.函數的參數標簽和參數名稱
每個函數的參數都有參數的標簽和參數名稱。參數標簽在函數調用時很有用的。在函數的實現用方法名會用得到。通常,參數用它們的參數名作為它們參數標簽。
func someFunction(firstParameterName: Int, secondParameterName: Int) { // In the function body, firstParameterName and secondParameterName // refer to the argument values for the first and second parameters. } someFunction(firstParameterName: 1, secondParameterName: 2)
所有的參數必須用不一樣的名字。盡管,多個參數可能會有相同的參數標簽,唯一的參數標簽可以幫助你的代碼更加易讀。
(1)指定參數標簽
你可以在參數名字面前寫上參數標簽,用 空格分隔開。
func someFunction(argumentLabel parameterName: Int) { // In the function body, parameterName refers to the argument value // for that parameter. }
當下是一個關於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")) // Prints "Hello Bill! Glad you could visit from Cupertino."
標簽的使用使得函數像表達式一樣調用,類似於句子,同時還使得函數體更容易解讀、更清晰。
(2)省略參數標簽
如果你的參數不需要參數標簽,用下划線代替參數的參數標簽。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) { // In the function body, firstParameterName and secondParameterName // refer to the argument values for the first and second parameters. } someFunction(1, secondParameterName: 2)
如果一個參數有參數標簽,當你調用函數的時候,參數必須被標記。
(3)默認參數值
你可以在函數中為任何的參數定義一個默認的值,通過在參數類型的后面為參數分配一個值。如果你設置了默認的值,當你調用函數的時候,可以省略參數。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) { // If you omit the second argument when calling this function, then // the value of parameterWithDefault is 12 inside the function body. } someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6 someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
在函數的參數列表開始時,在具有默認值的參數之前,放置沒有默認值的參數。沒有默認值的參數通常對函數的意義更為重要——首先編寫它們使識別相同的函數被調用變得更加容易,無論是否省略了默認參數。
(4)可變參數
一個可變的參數可以結束從0到其他更多指定類型的值。當函數調用的時候,你可以用可變參數指定可以輸入參數的值。可變參數的實現是通過在參數名的后面插入三個省略號“...”。
傳入可變參數的值在函數是有效地在函數體中以數組的形式存在。例如,一個可變參數名為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) // returns 3.0, which is the arithmetic mean of these five numbers arithmeticMean(3, 8.25, 18.75) // returns 10.0, which is the arithmetic mean of these three numbers
注意:一個函數可能最多有一個可變參數。
(5)輸入輸出參數
函數的參數值通常都是默認的。嘗試在函數體的內部改變一個函數的參數值會出現編譯時錯誤。這就是意味着你不能錯誤地改變一個參數的值。如果你想要修改一個函數的參數值,而且在函數調用結束后仍然保持這個改變值,定義這個參數為in-out參數來替代。
你可以用在in-out參數中傳入一個變量,你不能傳入常量作為參數,因為常量是不可更改的。當你給in-out參數傳入一個參數值時,在變量名前面用&符號,表明這個變量在函數中是可改變的。
注意:輸入輸出參數不能有默認值,可變參數不能標記為輸入輸出參數。
func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA }
這個函數是兩個值的交換,當你調用swapTwoInts這個方法是,傳入2個Int類型的變量。當someInt和anotherInt值傳入swapTwoInts時,前面帶上&符號。
var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") // Prints "someInt is now 107, and anotherInt is now 3"
注意:輸入輸出參數與函數定義的返回值時不同的。swapTwoInts的例子中沒有定義一個返回值和返回類型,但它仍然修改了someInt和anotherInt的值。輸入輸出參數為函數在函數體的范圍內改變輸出值提供了另外一種選擇。
5.函數類型
每個函數都有一個特定的函數類型,由參數類型和函數返回值類型組成。
例如:
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。
(1)用函數類型
正如使用Swift中其他類型一樣使用函數類型。例如,你可以定義一個常量或者變量為函數類型,並為這個變量分配一個合適的函數。
var mathFunction: (Int, Int) -> Int = addTwoInts
可以解讀如下:
定義一個叫做 mathFunction 的變量,這個變量是 帶有兩個Int參數的函數並返回一個Int值的類型。讓這個變量指向addTwoInts的函數。
你可以用mathFunction調用分配的函數。
print("Result: \(mathFunction(2, 3))") // Prints "Result: 5"
不同的函數有相同的類型就可以分配給相同的變量,對於沒有函數類型的也一樣:
mathFunction = multiplyTwoInts print("Result: \(mathFunction(2, 3))") // Prints "Result: 6"
與其他類型類似,你可以讓Swift推斷函數類型,當你把一個函數復制給一個常量或者變量的時候。
let anotherMathFunction = addTwoInts // anotherMathFunction is inferred to be of type (Int, Int) -> Int
(2)函數類型作為參數類型
你可以用函數類型例如(Int, Int)->Int 作為另外一個函數的參數類型。這使您能夠在函數調用時為函數的調用方保留函數實現的某些方面。
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { print("Result: \(mathFunction(a, b))") } printMathResult(addTwoInts, 3, 5) // Prints "Result: 8"
這個叫做printMathResult方法的例子,有是三個參數。第一個參數叫做mathFunction,它的類型是(Int, Int)->Int。你可以傳入任何符合這個函數類型的函數。第二和第三個參數分別是a和b,兩個的類型都是Int類型。
printMathResult(_:_:_)的作用是將調用的結果打印成合適類型的數學函數。函數的實現實際上並不重要——它只關系到函數的正確類型。這使printMathResult(_:_:_:)能夠以類型安全的方式將其功能傳遞給調用者。
(3)函數類型作為返回值類型
你可以用一個函數類型作為另外一個函數的返回值類型。在一個函數的返回箭頭(->)后面加上函數類型來實現。
func stepForward(_ input: Int) -> Int { return input + 1 } func stepBackward(_ input: Int) -> Int { return input - 1 }
下面這個方法叫做 chooseStepFunction(backward:),它的返回類型是
(Int) -> Int。chooseStepFunction(backward:)函數根據backward的布爾值來判斷是返回stepBackward函數還是返回stepForward函數。
6.嵌套函數
目前在本章節你所遇到的函數都是全部函數,它定義於全局范圍。你可以在函數體中定義函數,也就是我們所說的嵌套函數。
默認情況下,嵌套函數隱藏在外部世界中,但仍然可以通過其封閉函數調用和使用。一個封閉函數還可以返回它的一個嵌套函數,允許在另一個范圍內使用嵌套函數。
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!