在形参列表中,在形参的类型注解后面给该形参赋值,就能为函数的任意形参提供默认参数值。指定了默认参数值之后,函数的调用方在调用该函数时可以省略这个形参,转而使用默认值。
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
嵌套函数的行为与普通函数相同,但可见范围仅限于所在外层函数的函数体。
在这个练习中,你将再写一些代码,帮助你按照最喜欢的烹饪书做出美味的千层面。
你共有五个任务,它们都与烹饪千层面所花的时间有关。
你想写一个改进版的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函数,它接受一个输入输出元组形参(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,在其中列出千层面的各种层。
该函数会使用五个嵌套函数,分别数出千层面中马苏里拉奶酪、里科塔奶酪、白酱、酱料和肉的层数,再比较这些层数,判断是否应该配红葡萄酒:应该时返回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