本頁包含內容:
- 函數定義與調用(Defining and Calling Functions)
- 函數參數與返回值(Function Parameters and Return Values)
- 函數參數名稱(Function Parameter Names)
- 函數類型(Function Types)
- 嵌套函數(Nested Functions)
函數是用來完成特定任務的獨立的代碼塊。你給一個函數起一個合適的名字,用來標識函數做什么,并且當函數需要執行的時候,這個名字會被用于“調用”函數。
Swift 統一的函數語法足夠靈活,可以用來表示任何函數,包括從最簡單的沒有參數名字的 C 風格函數,到復雜的帶局部和外部參數名的 Objective-C 風格函數。參數可以提供默認值,以簡化函數調用。參數也可以既當做傳入參數,也當做傳出參數,也就是說,一旦函數執行結束,傳入的參數值可以被修改。
在 Swift 中,每個函數都有一種類型,包括函數的參數值類型和返回值類型。你可以把函數類型當做任何其他普通變量類型一樣處理,這樣就可以更簡單地把函數當做別的函數的參數,也可以從其他函數中返回函數。函數的定義可以寫在其他函數定義中,這樣可以在嵌套函數范圍內實現功能封裝。
函數的定義與調用(Defining and Calling Functions)
當你定義一個函數時,你可以定義一個或多個有名字和類型的值,作為函數的輸入(稱為參數,parameters),也可以定義某種類型的值作為函數執行結束的輸出(稱為返回類型,return type)。
每個函數有個函數名,用來描述函數執行的任務。要使用一個函數時,你用函數名“調用”,并傳給它匹配的輸入值(稱作實參,arguments)。一個函數的實參必須與函數參數表里參數的順序一致。
在下面例子中的函數叫做"sayHello(_:)"
,之所以叫這個名字,是因為這個函數用一個人的名字當做輸入,并返回給這個人的問候語。為了完成這個任務,你定義一個輸入參數-一個叫做?personName
?的?String
?值,和一個包含給這個人問候語的?String
?類型的返回值:
func sayHello(personName: String) -> String {let greeting = "Hello, " + personName + "!"return greeting
}
所有的這些信息匯總起來成為函數的定義,并以?func
?作為前綴。指定函數返回類型時,用返回箭頭?->
(一個連字符后跟一個右尖括號)后跟返回類型的名稱的方式來表示。
該定義描述了函數做什么,它期望接收什么和執行結束時它返回的結果是什么類型。這樣的定義使得函數可以在別的地方以一種清晰的方式被調用:
print(sayHello("Anna"))
// prints "Hello, Anna!"
print(sayHello("Brian"))
// prints "Hello, Brian!"
調用?sayHello(_:)
?函數時,在圓括號中傳給它一個?String
?類型的實參,例如?sayHello("Anna")
。因為這個函數返回一個?String
?類型的值,sayHello
?可以被包含在?print(_:separator:terminator:)
?的調用中,用來輸出這個函數的返回值,正如上面所示。
在?sayHello(_:)
?的函數體中,先定義了一個新的名為?greeting
?的?String
?常量,同時,把對?personName
?的問候消息賦值給了?greeting
?。然后用?return
?關鍵字把這個問候返回出去。一旦?return greeting
?被調用,該函數結束它的執行并返回?greeting
?的當前值。
你可以用不同的輸入值多次調用?sayHello(_:)
。上面的例子展示的是用"Anna"
和"Brian"
調用的結果,該函數分別返回了不同的結果。
為了簡化這個函數的定義,可以將問候消息的創建和返回寫成一句:
func sayHelloAgain(personName: String) -> String {return "Hello again, " + personName + "!"
}
print(sayHelloAgain("Anna"))
// prints "Hello again, Anna!"
函數參數與返回值(Function Parameters and Return Values)
函數參數與返回值在 Swift 中極為靈活。你可以定義任何類型的函數,包括從只帶一個未名參數的簡單函數到復雜的帶有表達性參數名和不同參數選項的復雜函數。
無參函數(Functions Without Parameters)
函數可以沒有參數。下面這個函數就是一個無參函數,當被調用時,它返回固定的?String
?消息:
func sayHelloWorld() -> String {return "hello, world"
}
print(sayHelloWorld())
// prints "hello, world"
盡管這個函數沒有參數,但是定義中在函數名后還是需要一對圓括號。當被調用時,也需要在函數名后寫一對圓括號。
多參數函數 (Functions With Multiple Parameters)
函數可以有多種輸入參數,這些參數被包含在函數的括號之中,以逗號分隔。
這個函數用一個人名和是否已經打過招呼作為輸入,并返回對這個人的適當問候語:
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:)
函數。
當調用超過一個參數的函數時,第一個參數后的參數根據其對應的參數名稱標記
無返回值函數(Functions Without Return Values)
函數可以沒有返回值。下面是?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)。
多重返回值函數(Functions with Multiple Return Values)
你可以用元組(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 Tuple Return Types)
如果函數返回的元組類型有可能整個元組都“沒有值”,你可以使用可選的(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"
函數參數名稱(Function Parameter Names)
函數參數都有一個外部參數名(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)
一般情況下,第一個參數省略其外部參數名,第二個以及隨后的參數使用其局部參數名作為外部參數名。所有參數必須有獨一無二的局部參數名。盡管多個參數可以有相同的外部參數名,但不同的外部參數名能讓你的代碼更有可讀性。
指定外部參數名(Specifying External Parameter Names)
你可以在局部參數名前指定外部參數名,中間以空格分隔:
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:)
函數時兩個外部參數名都必須寫出來。
使用外部函數名可以使函數以一種更富有表達性的類似句子的方式調用,并使函數體意圖清晰,更具可讀性。
忽略外部參數名(Omitting External Parameter Names)
如果你不想為第二個及后續的參數設置外部參數名,用一個下劃線(_
)代替一個明確的參數名。
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)
注意
因為第一個參數默認忽略其外部參數名稱,顯式地寫下劃線是多余的。
默認參數值(Default Parameter Values)
你可以在函數體中為每個參數定義默認值(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 Parameters)
一個可變參數(variadic parameter)
可以接受零個或多個值。函數調用時,你可以用可變參數來指定函數參數可以被傳入不確定數量的輸入值。通過在變量類型名后面加入(...)
的方式來定義可變參數。
可變參數的傳入值在函數體中變為此類型的一個數組。例如,一個叫做?numbers
?的?Double...
?型可變參數,在函數體內可以當做一個叫?numbers
?的?[Double]
?型的數組常量。
下面的這個函數用來計算一組任意長度數字的算術平均數(arithmetic mean)
:
func arithmeticMean(numbers: Double...) -> Double {var total: Double = 0for 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 Parameters)
函數參數默認是常量。試圖在函數體中更改參數值將會導致編譯錯誤。這意味著你不能錯誤地更改參數值。如果你想要一個函數可以修改參數的值,并且想要在這些修改在函數調用結束后仍然存在,那么就應該把這個參數定義為輸入輸出參數(In-Out Parameters)。
定義一個輸入輸出參數時,在參數定義前加 inout 關鍵字。一個輸入輸出參數有傳入函數的值,這個值被函數修改,然后被傳出函數,替換原來的值。
你只能傳遞變量給輸入輸出參數。你不能傳入常量或者字面量(literal value),因為這些量是不能被修改的。當傳入的參數作為輸入輸出參數時,需要在參數名前加&
符,表示這個值可以被函數修改。
注意
輸入輸出參數不能有默認值,而且可變參數不能用?inout
?標記。
下面是例子,swapTwoInts(_:_:)
?函數,有兩個分別叫做?a
?和?b
?的輸入輸出參數:
func swapTwoInts(inout a: Int, inout _ b: Int) {let temporaryA = aa = bb = 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
?的值。輸入輸出參數是函數對函數體外產生影響的另一種方式。
函數類型(Function Types)
每個函數都有種特定的函數類型,由函數的參數類型和返回類型組成。
例如:
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
?類型的函數”。
使用函數類型(Using Function Types)
在 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
函數類型作為參數類型(Function Types as Parameter Types)
你可以用(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)的方式將一部分功能轉給調用者實現。
函數類型作為返回類型(Function Types as Return Types)
你可以用函數類型作為另一個函數的返回類型。你需要做的是在返回箭頭(->
)后寫一個完整的函數類型。
下面的這個例子中定義了兩個簡單函數,分別是?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!
嵌套函數(Nested Functions)
這章中你所見到的所有函數都叫全局函數(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!")
// -4...
// -3...
// -2...
// -1...
// zero!
再次感謝~~~~~~~~~~~~