你可以為函式的任何參數提供預設參數值,方法是在參數列表中,緊接在該參數的型別註解之後為這個參數指派一個值。當指定了預設參數值之後,函式的呼叫者在呼叫函式時就可以省略該參數,此時會改用預設值。
func greeting(name: String = "guest", duration: Int = 2) -> String {
"Welcome, \(name). Enjoy your \(duration) night stay."
}
greeting(name: "Bobo", duration: 7)
// Returns "Welcome, Bobo. Enjoy your 7 night stay."
greeting(duration: 3)
// Returns "Welcome, guest. Enjoy your 3 night stay."
greeting(name: "Wynona")
// Returns "Welcome, Wynona. Enjoy your 2 night stay."
greeting()
// Returns "Welcome, guest. Enjoy your 2 night stay."
可變參數讓函式可以針對單一參數接受零個或多個指定型別的值。
只要在參數的型別註解後面加上三個點(...),就能定義一個可變參數。
在函式主體中,Swift 會把這些值以指定型別的陣列形式提供使用:
import Foundation
func geometricMean(_ numbers: Double...) -> Double {
var total = 1.0
for number in numbers {
total *= number
}
return pow(total, 1.0 / Double(numbers.count))
}
let result = geometricMean(1, 2, 3, 4, 5)
// result is 2.605171084697352
如果函式在可變參數之後還有其他參數,那麼緊接在可變參數後面的第一個參數必須有明確的引數標籤,Swift 才知道可變引數列表在哪裡結束。
在函式主體中,參數會被視為常數,而不是變數;嘗試修改參數的值會引發編譯期錯誤。 如果函式想要修改某個參數的值,就必須使用輸入輸出參數,明確表達這種可變性。
要使用輸入輸出參數,程式設計師必須確認三件事:
inout 寫在 : 和型別名稱之間。&,明確標記它是可修改的。完成這些之後,函式會為輸入輸出參數建立一份內部副本,依照函式主體中的指示修改它,然後在函式回傳時,把修改過的值複製回傳入的那個變數。
func updateVersion(_ record: inout (version: Int, title: String)) {
record.version += 1
}
var dbRecord = (version: 2, title: "Exercism")
updateVersion(&dbRecord)
// dbRecord is now (3, "Exercism")
關於輸入輸出參數,還有幾條額外的規則需要注意。
1 在含有輸入輸出參數的函式中,你不能引用作為輸入輸出參數傳入的那個變數。
2. 同一個變數不能在同一個函式中作為多個輸入輸出參數傳入。
例如,`inoutFunc(&mutVar, &mutVar)` 會引發編譯器錯誤。
在 Swift 中,你可以在其他函式裡面定義函式。 這些函式稱為巢狀函式。
巢狀函式通常用來當作私有的輔助函式,負責執行外層函式需要的特定工作,同時又不會弄亂外層作用域:
func makeNumber(_ number: Int) -> Int {
func double(_ x: Int) -> Int {
x * 2
}
func increment(_ x: Int) -> Int {
x + 1
}
return number.isMultiple(of: 2) ? increment(double(number)) : double(number)
}
print(makeNumber(3)) // Prints 6
print(makeNumber(4)) // Prints 9
巢狀函式的行為和一般函式一樣,但只有在外層函式的主體中才能看見。
在這個練習中,你要再多寫一些程式碼,幫你做出最愛食譜書裡的完美千層麵。
你有 5 個任務,全都跟烹調千層麵所花費的時間有關。
你想寫一個改良版的remainingMinutesInOven函式,它接收千層麵已經在烤箱裡的經過分鐘數作為參數,並根據上一個任務中預期的烤箱時間(以分鐘計),回傳千層麵還得在烤箱裡待幾分鐘。
先前,你依賴的是來自remainingMinutesInOven函式外部的常數值expectedMinutesInOven。
這對你來說既有點太雜亂,也不夠有彈性,因為它不讓你能更改預期的烤箱時間。
為了整理一下,請定義remainingMinutesInOven函式,它接收千層麵已經在烤箱裡的經過分鐘數作為參數,同時也接收一個預設值為 40 的expectedMinutesInOven參數。
你的函式應該根據上一個任務中預期的烤箱時間(以分鐘計),回傳千層麵還得在烤箱裡待幾分鐘。
remainingMinutesInOven(elapsedMinutes: 30)
// Returns 10
remainingMinutesInOven(elapsedMinutes: 30, expectedMinutesInOven: 75)
// Returns 45
你先前寫過一個preparationTimeInMinutes函式,它接收你加到千層麵裡的層數作為參數,並回傳你花了幾分鐘準備千層麵,假設每一層要花你 2 分鐘準備。
不過,你對這個函式不太滿意,因為在呼叫它之前,你得自己先數千層麵裡有幾層,誰有那麼多時間啊?
為了讓自己輕鬆一點,請寫一個函式preparationTimeInMinutes,它接收一個可變的String參數layers,你可以在裡面列出千層麵的不同層。
這個函式接著會計算千層麵裡的層數,並回傳所需的準備時間。
preparationTimeInMinutes(layers: "sauce", "noodles")
// Returns 4
preparationTimeInMinutes(layers: "sauce", "noodles", "sauce", "meat", "mozzarella", "noodles", "sauce", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns 24
在開始組裝千層麵之前,你想確認自己有足夠的醬汁和麵條,能做出夢想中的千層麵。
千層麵裡的每一層麵條,你需要 3 片麵條。
千層麵裡的每一層醬汁,你需要 0.2 公升的醬汁。
請定義函式quantities,它接收一個可變的String參數layers,你可以在裡面列出千層麵的不同層。
這個函式接著會算出製作這道餐點所需的麵條和醬汁數量。結果應該以(noodles: Int, sauce: Double)型別的元組回傳。
quantities(layers: "sauce", "noodles", "sauce", "meat", "mozzarella", "noodles", "sauce", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns (noodles: 9, sauce: 0.8)
quantities(layers: "sauce", "noodles", "sauce", "noodles", "meat", "noodles", "mozzarella")
// Returns (noodles: 9, sauce: 0.4)
不幸的是,你手邊的醬汁,容量標示的是液體盎司,而不是公升。
為了幫你判斷手邊的醬汁是否足夠,請寫一個函式toOz,它接收一個 in-out 元組參數(noodles: Int, sauce: Double),並把醬汁的量轉換成液體盎司。
注意,每公升有 33.814 液體盎司。
var amount = (noodles: 9, sauce: 0.8)
toOz(&amount)
amount
// Returns (noodles: 9, sauce: 27.0512)
最後,你想知道該用哪種葡萄酒來搭配你的千層麵。
你聽說,如果千層麵裡的莫札瑞拉起司、瑞可塔起司和白醬比肉和醬汁多,就該搭配白酒,否則就搭配紅酒。
請寫一個函式redWine,它接收一個可變的String參數layers,你可以在裡面列出千層麵的不同層。
這個函式會用 5 個巢狀函式,判斷千層麵裡莫札瑞拉起司、瑞可塔起司、白醬、醬汁和肉的層數,並比較這些層數,判斷是否該搭配紅酒,若是就回傳true,否則回傳false。
redWine(layers: "sauce", "noodles", "sauce", "meat", "mozzarella", "noodles", "sauce", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns true
redWine(layers: "sauce", "noodles", "béchamel", "meat", "mozzarella", "noodles", "ricotta", "eggplant", "béchamel", "noodles", "sauce", "mozzarella")
// Returns false