繼續學習Swift文檔,從上一章節:控制流,咱們學習了Swift控制流相關的內容,如for-in循環、while循環、if語句、switch語句、continue、break、fallthrough、帶標籤的語句、guard語句這些內容。其中switch語句在swift裏的用法與OC相比,有很大的差異,功能更豐富,具體的能夠參閱上一章節的內容。如今,咱們學習Swift的函數相關的內容。這一章節的內容很重要了,幾乎全部開發語言都離不開函數的使用,因此咱們要認真對待並熟練掌握這一章節的內容。因爲篇幅較長,這裏分篇來記錄,接下來,Fighting!html
若是你已經熟練掌握了函數的使用,那麼請參閱下一章節:閉包swift
函數是執行特定任務的獨立代碼塊。 您爲函數指定一個名稱,該名稱能夠標識其功能,而且該名稱用於「調用」該函數以在須要時執行其任務。數組
Swift的統一函數語法足夠靈活,能夠表達任何內容,從沒有參數名稱的簡單C樣式函數到具備每一個參數名稱和參數標籤的複雜的Objective-C樣式方法。 參數能夠提供默認值以簡化函數調用,而且能夠做爲輸入-輸出參數傳遞,參數在函數完成執行後會修改傳遞的變量。安全
Swift中的每一個函數都有一個類型,包括該函數的參數類型和返回類型。 您能夠像Swift中的任何其餘類型同樣使用此類型,這使得將函數做爲參數傳遞給其餘函數以及從函數返回函數變得容易。 也能夠在其餘函數中編寫函數,以將有用的函數封裝在嵌套函數範圍內。bash
定義函數時,能夠選擇定義一個或多個該函數做爲輸入的命名,鍵入的值,稱爲參數。 您還能夠選擇定義一種值類型,該函數完成後將做爲輸出傳回,稱爲函數的返回類型。閉包
每一個功能都有一個功能名稱,該名稱描述了該功能執行的任務。 要使用函數,請使用函數名稱「調用」該函數,而後將與函數參數類型匹配的輸入值(稱爲自變量)傳遞給它。 必須始終以與函數參數列表相同的順序提供函數的參數。ide
在下面的示例中,該函數稱爲greet(person :),由於它就是這樣作的,它將一我的的名字做爲輸入並返回該人的問候語。 爲此,您須要定義一個輸入參數-一個名爲person的String值,以及一個String返回類型,其中將包含對該人的問候:函數
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
複製代碼
全部這些信息都會彙總到函數的定義中,並以func關鍵字爲前綴。 您可使用返回箭頭->(連字符後接直角括號)指示函數的返回類型,其後是要返回的類型的名稱。post
定義描述了函數的功能,指望接收的功能以及完成後返回的內容。 經過定義,能夠輕鬆地從代碼中的其餘地方明確調用該函數:學習
print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"
複製代碼
您能夠經過在person參數標籤以後傳遞一個String值來調用greet(person :)函數,例如greet(person:「 Anna」)。 因爲該函數返回一個String值,所以能夠將greet(person :)包裝在對print(_:separator:terminator :)函數的調用中,以打印該字符串並查看其返回值,如上所示。
注意
print(_:separator:terminator :)函數的第一個參數沒有標籤,其餘參數是可選的,由於它們具備默認值。 有關函數語法的這些變體,將在下面的「函數參數標籤」,「參數名稱」和「默認參數值」中進行討論。
greet(person :)函數的主體經過定義一個新的String常量(稱爲greeting)並將其設置爲簡單的問候消息開始。 而後,使用return關鍵字將此greeting傳回函數。 在表示返回greeting的代碼行中,該函數完成其執行並返回greeting的當前值。
您可使用不一樣的輸入值屢次調用greet(person :)函數。 上面的示例顯示了若是使用輸入值「 Anna」和輸入值「 Brian」進行調用會發生的狀況。 該函數在每種狀況下都會返回定製的問候語。
爲了使該函數的主體更短,能夠將消息建立和return語句合併爲一行:
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
複製代碼
函數參數和返回值在Swift中很是靈活。 您能夠定義任何東西,從具備單個未命名參數的簡單實用程序函數到具備表達性參數名稱和不一樣參數選項的複雜函數。
不須要功能來定義輸入參數。 這是一個沒有輸入參數的函數,不管什麼時候調用,該函數老是返回相同的String消息:
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "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))
// Prints "Hello again, Tim!"
複製代碼
您能夠經過在圓括號中將標爲person的String參數值和標爲hasgreeted的Bool參數值都傳遞給括號來調用greet(person:alreadyGreeted :)函數,並用逗號分隔。 請注意,此函數不一樣於前面部分中顯示的greet(person :)函數。 儘管兩個函數的名稱都以greet開頭,可是greet(person:alreadyGreeted :)函數採用兩個參數,而greet(person :)函數僅採用一個參數。
不須要函數來定義返回類型。 這是greet(person :)函數的一個版本,該函數打印其本身的String值,而不是返回該值:
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"
複製代碼
由於它不須要返回值,因此函數的定義不包含返回箭頭(->)或返回類型。
注意
嚴格來講,即便未定義返回值,此版本的greet(person :)函數仍會返回值。 沒有定義返回類型的函數將返回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 :)調用第一個函數,但忽略其返回值。 當調用第二個函數時,消息仍然由第一個函數打印,可是不使用返回的值。
注意
返回值能夠忽略,可是說返回值的函數必須始終這樣作。 在函數的底部結束時,具備定義了返回類型的函數不返回值,會致使編譯時錯誤。
您可使用元組類型做爲函數的返回類型,以將多個值做爲一個複合返回值的一部分返回。
下面的示例定義了一個名爲minMax(array :)的函數,該函數查找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(array :)函數返回一個包含兩個Int值的元組。 這些值分別標記爲min和max,以便在查詢函數的返回值時能夠按名稱訪問它們。
minMax(array :)函數的主體經過將兩個名爲currentMin和currentMax的工做變量設置爲數組中第一個整數的值開始。 而後,該函數循環訪問數組中的其他值,並檢查每一個值是否分別小於或大於currentMin和currentMax的值。 最後,總的最小值和最大值做爲兩個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"
複製代碼
請注意,在從函數返回元組時不須要命名元組的成員,由於它們的名稱已被指定爲函數返回類型的一部分。
若是要函數返回的元組類型,多是「無值」,則可使用可選的元組返回類型來反映整個元組能夠爲零。 您經過在元組類型的右括號後面加上問號來編寫可選的元組返回類型,例如(Int,Int)? 或(字符串,整數,布爾)?
注意
可選的元組類型,例如(Int,Int)? 與包含可選類型(例如(Int ?, Int?))的元組不一樣。 對於可選的元組類型,整個元組是可選的,而不只僅是元組中的每一個單獨的值。
上面的minMax(array :)函數返回一個包含兩個Int值的元組。 可是,該函數不會對傳遞的數組執行任何安全檢查。 若是array參數包含一個空數組,則如上所述,minMax(array :)函數將在嘗試訪問array [0]時觸發運行時錯誤。
爲了安全地處理空數組,請編寫具備可選元組返回類型的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"
複製代碼
若是函數的整個主體是單個表達式,則函數隱式返回該表達式。 例如,下面的兩個函數具備相同的行爲:
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// Prints "Hello, Dave!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// Prints "Hello, Dave!"
複製代碼
greeting(for :)函數定義的是它返回的問候消息,這意味着它可使用這種較短的形式。 anotherGreeting(for :)函數使用相似的函數的return關鍵字返回相同的問候消息。 您只寫一個返回行的任何函數均可以忽略該返回。
就像在Shorthand Getter Declaration中所看到的那樣,屬性getter也可使用隱式返回。
每一個函數參數都具備參數標籤和參數名稱。 參數標籤在調用函數時使用; 每一個參數都寫在函數調用中,並帶有其參數標籤。 參數名稱在函數的實現中使用。 默認狀況下,參數使用其參數名稱做爲其參數標籤。
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)
複製代碼
全部參數必須具備惟一的名稱。 儘管多個參數可能具備相同的參數標籤,但獨特的參數標籤有助於使代碼更具可讀性。
您在參數名稱前寫一個參數標籤,並用空格隔開:
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."
複製代碼
變量標籤的使用可讓函數以表達方式,相似於句子的方式被調用,同時還能夠提供可讀且意圖明確的函數主體。
若是您不但願爲參數添加參數標籤,請爲該參數寫下劃線(_)而不是顯式參數標籤。
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)
複製代碼
若是參數具備參數標籤,則在調用函數時必須對參數進行標籤。
您能夠爲函數中的任何參數定義默認值,方法是在該參數的類型以後爲該參數分配一個值。 若是定義了默認值,則能夠在調用函數時忽略該參數。
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
複製代碼
將沒有默認值的參數放在函數的參數列表的開頭,在具備默認值的參數以前。 沒有默認值的參數一般對函數的意義更重要-首先編寫它們可使您更容易識別正在調用相同的函數,而不論是否忽略了任何默認參數。
可變參數接受零個或多個指定類型的值。 您可使用可變參數來指定在調用函數時能夠向該參數傳遞不一樣數量的輸入值。 經過在參數的類型名稱後插入三個句點(...)來編寫可變參數。
傳遞給可變參數的值可在函數體內以適當類型的數組形式使用。 例如,在函數體內可使用帶有數字名稱和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
複製代碼
注意
一個函數最多能夠具備一個可變參數。
函數參數默認爲常量。 試圖從函數主體內部更改函數參數的值會致使編譯時錯誤。 這意味着您不能錯誤地更改參數的值。 若是您但願函數修改參數的值,而且但願這些更改在函數調用結束後仍然存在,請將該參數定義爲in-out參數。
您能夠經過將in-out關鍵字放在參數類型的前面來編寫in-out參數。 in-out參數具備一個值,該值傳遞給函數,由函數修改,而後傳遞迴函數以替換原始值。 有關in-out參數的行爲以及相關的編譯器優化的詳細討論,請參見In-Out Parameters。
您只能將變量做爲in-out參數的參數傳遞。 您不能將常量或文字值做爲參數傳遞,由於沒法修改常量和文字。 當您將"與"符號(&)用做變量的參數時,能夠直接在其名稱前放置一個&符號,以代表該變量能夠被函數修改。
注意
in-out 參數不能有默認值,而且可變參數不能被用爲in-out參數
這是一個名爲swapTwoInts(: :)的函數的示例,該函數具備兩個稱爲a和b的in-out整數參數:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
複製代碼
swapTwoInts(: :)函數只是將b的值交換爲a,而後將a的值交換爲b。 該函數經過將a的值存儲在一個稱爲臨時A的臨時常量中,而後將b的值賦給a,而後將臨時A賦給b來執行此交換。
您可使用兩個Int類型的變量調用swapTwoInts(: :)函數來交換它們的值。 請注意,當將someInt和anotherInt的名稱傳遞給swapTwoInts(: :)函數時,它們的前綴爲&符:
ar 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(: :)函數修改的,即便它們最初是在函數外部定義的也是如此。
注意
in-out參數與從函數返回值不一樣。 上面的swapTwoInts示例未定義返回類型或返回值,但仍修改someInt和anotherInt的值。 in0out參數是函數在函數主體範圍以外產生影響的另外一種方法。
每一個函數都有特定的函數類型,由函數的參數類型和返回類型組成。
例如:
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
複製代碼
此示例定義了兩個簡單的數學函數,分別稱爲addTwoInts和multipleTwoInts。 這些函數各自採用兩個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,並打印結果 8。
printMathResult(::_ :)的做用是打印對適當類型的數學函數的調用結果。 該函數的實現實際執行什麼可有可無,僅與該函數的類型正確無關。 這使printMathResult(::_ :)以類型安全的方式將其某些功能移交給函數的調用者。
您能夠將一個函數類型用做另外一個函數的返回類型。 您能夠經過在返回函數的返回箭頭(->)以後當即編寫完整的函數類型來執行此操做。
下一個示例定義了兩個簡單的函數,分別稱爲stepForward(_ :)和stepBackward(_ :)。 stepForward(_ :)函數返回的值比其輸入值大一,而stepBackward(_ :)函數返回的值比其輸入值小一。 這兩個函數的類型都是(Int)-> Int:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
複製代碼
這是一個名爲choiceStepFunction(backward :)的函數,其返回類型爲(Int)-> Int。 choiceStepFunction(backward :)函數基於布爾值向後返回stepForward(_ :)函數或stepBackward(_ :)函數:
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
複製代碼
如今,您可使用choiceStepFunction(backward :)來得到向一個方向或另外一個方向步進的函數:
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
複製代碼
上面的示例肯定是否須要正步或負步來逐步將名爲currentValue的變量逐漸移近零。 currentValue的初始值爲3,這意味着currentValue> 0返回true,從而致使selectStepFunction(backward :)返回stepBackward(_ :)函數。 對返回函數的引用存儲在名爲moveNearerToZero的常量中。
如今moveNearerToZero引用了正確的函數,它能夠用於計數爲零:
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
複製代碼
到目前爲止,您在本章中遇到的全部功能都是全局函數的示例,這些函數是在全局範圍內定義的。 您還能夠在其餘函數體內定義函數,稱爲嵌套函數。
嵌套函數默認狀況下對外界隱藏,但仍能夠由其封閉函數調用和使用。 封閉函數還能夠返回其嵌套函數之一,以容許該嵌套函數在另外一個做用域中使用。
您能夠重寫上面的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("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!
複製代碼
這一章節內容到這裏就結束了,咱們作個小結:
func someFunction(_ firstParameterName: Int, secondParameterName: Int)
,調用時就能夠someFunction(1, secondParameterName: 2)
。func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12)
,調用時:someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6)
或someFunction(parameterWithoutDefault: 4)
。func arithmeticMean(_ numbers: Double...) -> Double
,調用時:arithmeticMean(1, 2, 3, 4, 5)
。func swapTwoInts(_ a: inout Int, _ b: inout Int)
,調用時,要聲明兩個var變量做爲參數,並傳入函數:swapTwoInts(&someInt, &anotherInt)
,用「&+變量」做爲參數。最後一句話,喜歡的朋友能夠給個👍嗎,你的鼓勵就是個人動力,嘿嘿~
參考文檔:Swift - Functions