本頁包含內容:html
函數是用來完成特定任務的獨立的代碼塊。你給一個函數起一個合適的名字,用來標識函數作什麼,而且當函數須要執行的時候,這個名字會被用於「調用」函數。swift
Swift 統一的函數語法足夠靈活,能夠用來表示任何函數,包括從最簡單的沒有參數名字的 C 風格函數,到複雜的帶局部和外部參數名的 Objective-C 風格函數。參數能夠提供默認值,以簡化函數調用。參數也能夠既當作傳入參數,也當作傳出參數,也就是說,一旦函數執行結束,傳入的參數值能夠被修改。數組
在 Swift 中,每一個函數都有一種類型,包括函數的參數值類型和返回值類型。你能夠把函數類型當作任何其餘普通變量類型同樣處理,這樣就能夠更簡單地把函數當作別的函數的參數,也能夠從其餘函數中返回函數。函數的定義能夠寫在在其餘函數定義中,這樣能夠在嵌套函數範圍內實現功能封裝。安全
當你定義一個函數時,你能夠定義一個或多個有名字和類型的值,做爲函數的輸入(稱爲參數,parameters),也能夠定義某種類型的值做爲函數執行結束的輸出(稱爲返回類型,return type)。函數
每一個函數有個函數名,用來描述函數執行的任務。要使用一個函數時,你用函數名「調用」,並傳給它匹配的輸入值(稱做實參,arguments)。一個函數的實參必須與函數參數表裏參數的順序一致。優化
在下面例子中的函數叫作"sayHello(_:)"
,之因此叫這個名字,是由於這個函數用一我的的名字當作輸入,並返回給這我的的問候語。爲了完成這個任務,你定義一個輸入參數-一個叫作 personName
的 String
值,和一個包含給這我的問候語的 String
類型的返回值:ui
func sayHello(personName: String) -> String { let greeting = "Hello, " + personName + "!" return greeting }
全部的這些信息彙總起來成爲函數的定義,並以 func
做爲前綴。指定函數返回類型時,用返回箭頭 ->
(一個連字符後跟一個右尖括號)後跟返回類型的名稱的方式來表示。spa
該定義描述了函數作什麼,它指望接收什麼和執行結束時它返回的結果是什麼類型。這樣的定義使得函數能夠在別的地方以一種清晰的方式被調用:code
print(sayHello("Anna")) // prints "Hello, Anna!" print(sayHello("Brian")) // prints "Hello, Brian!"
調用 sayHello(_:)
函數時,在圓括號中傳給它一個 String
類型的實參,例如 sayHello("Anna")
。由於這個函數返回一個 String
類型的值,sayHello
能夠被包含在 print(_:separator:terminator:)
的調用中,用來輸出這個函數的返回值,正如上面所示。htm
在 sayHello(_:)
的函數體中,先定義了一個新的名爲 greeting
的 String
常量,同時賦值了給 personName
的一個簡單問候消息。而後用 return
關鍵字把這個問候返回出去。一旦 return greeting
被調用,該函數結束它的執行並返回 greeting
的當前值。
你能夠用不一樣的輸入值屢次調用 sayHello(_:)
。上面的例子展現的是用"Anna"
和"Brian"
調用的結果,該函數分別返回了不一樣的結果。
爲了簡化這個函數的定義,能夠將問候消息的建立和返回寫成一句:
func sayHelloAgain(personName: String) -> String { return "Hello again, " + personName + "!" } print(sayHelloAgain("Anna")) // prints "Hello again, Anna!"
函數參數與返回值在 Swift 中極爲靈活。你能夠定義任何類型的函數,包括從只帶一個未名參數的簡單函數到複雜的帶有表達性參數名和不一樣參數選項的複雜函數。
函數能夠沒有參數。下面這個函數就是一個無參函數,當被調用時,它返回固定的 String
消息:
func sayHelloWorld() -> String { return "hello, world" } print(sayHelloWorld()) // prints "hello, world"
儘管這個函數沒有參數,可是定義中在函數名後仍是須要一對圓括號。當被調用時,也須要在函數名後寫一對圓括號。
函數能夠有多種輸入參數,這些參數被包含在函數的括號之中,以逗號分隔。
這個函數取得一我的的名字和是否被招呼做爲輸入,並對那我的返回適當的問候語:
func sayHello(personName: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { return sayHelloAgain(personName) } else { return sayHello(personName) } } print(sayHello("Tim", alreadyGreeted: true)) // prints "Hello again, Tim!"
你經過在括號內傳遞一個String
參數值和一個標識爲alreadyGreeted
的Bool
值,使用逗號分隔來調用sayHello(_:alreadyGreeted:)
函數。
當調用超過一個參數的函數時,第一個參數後的參數根據其對應的參數名稱標記,函數參數命名在函數參數名稱(Function Parameter Names)有更詳細的描述。
函數能夠沒有返回值。下面是 sayHello(_:)
函數的另外一個版本,叫 sayGoodbye(_:)
,這個函數直接輸出 String
值,而不是返回它:
func sayGoodbye(personName: String) { print("Goodbye, \(personName)!") } sayGoodbye("Dave") // prints "Goodbye, Dave!"
由於這個函數不須要返回值,因此這個函數的定義中沒有返回箭頭(->)和返回類型。
注意
嚴格上來講,雖然沒有返回值被定義,sayGoodbye(_:)
函數依然返回了值。沒有定義返回類型的函數會返回特殊的值,叫Void
。它實際上是一個空的元組(tuple),沒有任何元素,能夠寫成()
。
被調用時,一個函數的返回值能夠被忽略:
func printAndCount(stringToPrint: String) -> Int { print(stringToPrint) return stringToPrint.characters.count } func printWithoutCounting(stringToPrint: String) { printAndCount(stringToPrint) } printAndCount("hello, world") // prints "hello, world" and returns a value of 12 printWithoutCounting("hello, world") // prints "hello, world" but does not return a value
第一個函數 printAndCount(_:)
,輸出一個字符串並返回 Int
類型的字符數。第二個函數 printWithoutCounting
調用了第一個函數,可是忽略了它的返回值。當第二個函數被調用時,消息依然會由第一個函數輸出,可是返回值不會被用到。
注意
返回值能夠被忽略,但定義了有返回值的函數必須返回一個值,若是在函數定義底部沒有返回任何值,將致使編譯錯誤(compile-time error)。
你能夠用元組(tuple)類型讓多個值做爲一個複合值從函數中返回。
下面的這個例子中,定義了一個名爲minMax(_:)
的函數,做用是在一個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) }
minMax(_:)
函數返回一個包含兩個Int
值的元組,這些值被標記爲min
和max
,以便查詢函數的返回值時能夠經過名字訪問它們。
minMax(_:)
的函數體中,在開始的時候設置兩個工做變量currentMin
和currentMax
的值爲數組中的第一個數。而後函數會遍歷數組中剩餘的值並檢查該值是否比currentMin
和currentMax
更小或更大。最後數組中的最小值與最大值做爲一個包含兩個Int
值的元組返回。
由於元組的成員值已被命名,所以能夠經過點語法來檢索找到的最小值與最大值:
let bounds = minMax([8, -6, 2, 109, 3, 71]) print("min is \(bounds.min) and max is \(bounds.max)") // prints "min is -6 and max is 109"
須要注意的是,元組的成員不須要在元組從函數中返回時命名,由於它們的名字已經在函數返回類型中指定了。
若是函數返回的元組類型有可能整個元組都「沒有值」,你可使用可選的(Optional) 元組返回類型反映整個元組能夠是nil
的事實。你能夠經過在元組類型的右括號後放置一個問號來定義一個可選元組,例如(Int, Int)?
或(String, Int, Bool)?
注意
可選元組類型如(Int, Int)?
與元組包含可選類型如(Int?, Int?)
是不一樣的.可選的元組類型,整個元組是可選的,而不僅是元組中的每一個元素值。
前面的minMax(_:)
函數返回了一個包含兩個Int
值的元組。可是函數不會對傳入的數組執行任何安全檢查,若是array
參數是一個空數組,如上定義的minMax(_:)
在試圖訪問array[0]
時會觸發一個運行時錯誤。
爲了安全地處理這個「空數組」問題,將minMax(_:)
函數改寫爲使用可選元組返回類型,而且當數組爲空時返回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(_:)
函數返回的是一個實際的元組值仍是nil
:
if let bounds = minMax([8, -6, 2, 109, 3, 71]) { print("min is \(bounds.min) and max is \(bounds.max)") } // prints "min is -6 and max is 109"
函數參數都有一個外部參數名(external parameter name)和一個局部參數名(local parameter name)。外部參數名用於在函數調用時標註傳遞給函數的參數,局部參數名在函數的實現內部使用。
func someFunction(firstParameterName: Int, secondParameterName: Int) { // function body goes here // firstParameterName and secondParameterName refer to // the argument values for the first and second parameters } someFunction(1, secondParameterName: 2)
通常狀況下,第一個參數省略其外部參數名,第二個以及隨後的參數使用其局部參數名做爲外部參數名。全部參數必須有獨一無二的局部參數名。儘管多個參數能夠有相同的外部參數名,但不一樣的外部參數名能讓你的代碼更有可讀性。
你能夠在局部參數名前指定外部參數名,中間以空格分隔:
func someFunction(externalParameterName localParameterName: Int) { // function body goes here, and can use localParameterName // to refer to the argument value for that parameter }
注意
若是你提供了外部參數名,那麼函數在被調用時,必須使用外部參數名。
這個版本的sayHello(_:)
函數,接收兩我的的名字,會同時返回對他倆的問候:
func sayHello(to person: String, and anotherPerson: String) -> String { return "Hello \(person) and \(anotherPerson)!" } print(sayHello(to: "Bill", and: "Ted")) // prints "Hello Bill and Ted!"
爲每一個參數指定外部參數名後,在你調用sayHello(to:and:)
函數時兩個外部參數名都必須寫出來。
使用外部函數名可使函數以一種更富有表達性的相似句子的方式調用,並使函數體意圖清晰,更具可讀性。
若是你不想爲第二個及後續的參數設置外部參數名,用一個下劃線(_
)代替一個明確的參數名。
func someFunction(firstParameterName: Int, _ secondParameterName: Int) { // function body goes here // firstParameterName and secondParameterName refer to // the argument values for the first and second parameters } someFunction(1, 2)
注意
由於第一個參數默認忽略其外部參數名稱,顯式地寫下劃線是多餘的。
你能夠在函數體中爲每一個參數定義默認值(Deafult Values)
。當默認值被定義後,調用這個函數時能夠忽略這個參數。
func someFunction(parameterWithDefault: Int = 12) { // function body goes here // if no arguments are passed to the function call, // value of parameterWithDefault is 12 } someFunction(6) // parameterWithDefault is 6 someFunction() // parameterWithDefault is 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) // 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
注意
一個函數最多隻能有一個可變參數。
若是函數有一個或多個帶默認值的參數,並且還有一個可變參數,那麼把可變參數放在參數表的最後。
函數參數默認是常量。試圖在函數體中更改參數值將會致使編譯錯誤。這意味着你不能錯誤地更改參數值。
可是,有時候,若是函數中有傳入參數的變量值副本將是頗有用的。你能夠經過指定一個或多個參數爲變量參數,從而避免本身在函數中定義新的變量。變量參數不是常量,你能夠在函數中把它當作新的可修改副原本使用。
經過在參數名前加關鍵字 var
來定義變量參數:
func alignRight(var string: String, totalLength: Int, pad: Character) -> String { let amountToPad = totalLength - string.characters.count 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, totalLength: 10, pad: "-") // paddedString is equal to "-----hello" // originalString is still equal to "hello"
這個例子中定義了一個叫作 alignRight(_:totalLength:pad:)
的新函數,用來將輸入的字符串對齊到更長的輸出字符串的右邊緣。左側空餘的地方用指定的填充字符填充。這個例子中,字符串"hello"
被轉換成了"-----hello"
。
alignRight(_:totalLength:pad:)
函數將輸入參數 string
定義爲變量參數。這意味着 string
如今能夠做爲一個局部變量,被傳入的字符串值初始化,而且能夠在函數體中進行操做。
函數首先計算出有多少字符須要被添加到string
的左邊,從而將其在整個字符串中右對齊。這個值存儲在一個稱爲amountToPad
的本地常量。若是不須要填充(也就是說,若是amountToPad
小於1),該函數簡單地返回沒有任何填充的輸入值string
。
不然,該函數用pad
字符建立一個叫作padString
的臨時String
常量,並將amountToPad
個 padString
添加到現有字符串的左邊。(一個String
值不能被添加到一個Character
值上,因此padString
常量用於確保+
操做符兩側都是String
值)。
注意
對變量參數所進行的修改在函數調用結束後便消失了,而且對於函數體外是不可見的。變量參數僅僅存在於函數調用的生命週期中。
變量參數,正如上面所述,僅僅能在函數體內被更改。若是你想要一個函數能夠修改參數的值,而且想要在這些修改在函數調用結束後仍然存在,那麼就應該把這個參數定義爲輸入輸出參數(In-Out Parameters)。
定義一個輸入輸出參數時,在參數定義前加 inout
關鍵字。一個輸入輸出參數有傳入函數的值,這個值被函數修改,而後被傳出函數,替換原來的值。想獲取更多的關於輸入輸出參數的細節和相關的編譯器優化,請查看輸入輸出參數一節。
你只能傳遞變量給輸入輸出參數。你不能傳入常量或者字面量(literal value),由於這些量是不能被修改的。當傳入的參數做爲輸入輸出參數時,須要在參數名前加&
符,表示這個值能夠被函數修改。
注意
輸入輸出參數不能有默認值,並且可變參數不能用inout
標記。若是你用inout
標記一個參數,這個參數不能被var
或者let
標記。
下面是例子,swapTwoInts(_:_:)
函數,有兩個分別叫作 a
和 b
的輸入輸出參數:
func swapTwoInts(inout a: Int, inout _ b: 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)") // prints "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") }
這個函數的類型是:() -> 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 is inferred to be of type (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) // prints "Result: 8"
這個例子定義了 printMathResult(_:_:_:)
函數,它有三個參數:第一個參數叫 mathFunction
,類型是(Int, Int) -> Int
,你能夠傳入任何這種類型的函數;第二個和第三個參數叫 a
和 b
,它們的類型都是 Int
,這兩個值做爲已給出的函數的輸入值。
當 printMathResult(_:_:_:)
被調用時,它被傳入 addTwoInts
函數和整數3
和5
。它用傳入3
和5
調用 addTwoInts
,並輸出結果:8
。
printMathResult(_:_:_:)
函數的做用就是輸出另外一個適當類型的數學函數的調用結果。它不關心傳入函數是如何實現的,它只關心這個傳入的函數類型是正確的。這使得 printMathResult(_:_:_:)
能以一種類型安全(type-safe)的方式將一部分功能轉給調用者實現。
你能夠用函數類型做爲另外一個函數的返回類型。你須要作的是在返回箭頭(->
)後寫一個完整的函數類型。
下面的這個例子中定義了兩個簡單函數,分別是 stepForward
和stepBackward
。stepForward
函數返回一個比輸入值大一的值。stepBackward
函數返回一個比輸入值小一的值。這兩個函數的類型都是 (Int) -> Int
:
func stepForward(input: Int) -> Int { return input + 1 } func stepBackward(input: Int) -> Int { return input - 1 }
下面這個叫作 chooseStepFunction(_:)
的函數,它的返回類型是 (Int) -> Int
類型的函數。chooseStepFunction(_:)
根據布爾值 backwards
來返回 stepForward(_:)
函數或 stepBackward(_:)
函數:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int { return backwards ? stepBackward : stepForward }
你如今能夠用 chooseStepFunction(_:)
來得到兩個函數其中的一個:
var currentValue = 3 let moveNearerToZero = chooseStepFunction(currentValue > 0) // moveNearerToZero now refers to the stepBackward() function
上面這個例子中計算出從 currentValue
逐漸接近到0
是須要向正數走仍是向負數走。currentValue
的初始值是3
,這意味着 currentValue > 0
是真的(true
),這將使得 chooseStepFunction(_:)
返回 stepBackward(_:)
函數。一個指向返回的函數的引用保存在了 moveNearerToZero
常量中。
如今,moveNearerToZero
指向了正確的函數,它能夠被用來數到0
:
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(_:)
函數:
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 = -4 let moveNearerToZero = chooseStepFunction(currentValue > 0) // moveNearerToZero now refers to the nested stepForward() function while currentValue != 0 { print("\(currentValue)... ") currentValue = moveNearerToZero(currentValue) } print("zero!")