轨道
/
Swift
Swift
/
练习
/
千层面大师
千层面大师

千层面大师

学习练习

简介

默认参数值

在形参列表中,在形参的类型注解后面给该形参赋值,就能为函数的任意形参提供默认参数值。指定了默认参数值之后,函数的调用方在调用该函数时可以省略这个形参,转而使用默认值。

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
Note

如果函数在可变参数之后还包含其他形参,那么紧随可变参数之后的第一个形参必须带一个显式的实参标签,这样 Swift 才知道可变实参列表在哪里结束。

输入输出参数

在函数体内部,形参被当作常量而不是变量;试图修改形参的值会引发编译时错误。 如果函数想要修改形参的值,就必须使用输入输出参数,把这种可修改性明确表达出来。

要使用输入输出参数,程序员必须确认三件事:

  1. 函数定义必须显式地标记输入输出参数:在形参类型注解的:和类型名之间写上关键字inout。
  2. 传入输入输出参数的值必须声明为变量,而不能是常量。同样,它也不能是字面量。
  3. 在调用函数时,变量名前面必须加一个&,显式地标明它是可修改的。

做到这一点后,函数会在内部复制一份输入输出参数,按照函数体中的指示修改它,然后在函数返回时把修改后的值复制回传入的那个变量。

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

嵌套函数的行为与普通函数相同,但可见范围仅限于所在外层函数的函数体。

说明

在这个练习中,你将再写一些代码,帮助你按照最喜欢的烹饪书做出美味的千层面。

你共有五个任务,它们都与烹饪千层面所花的时间有关。

1. 允许修改预期的烘烤时间

你想写一个改进版的remainingMinutesInOven函数,它接受千层面已经在烤箱里度过的分钟数作为形参,并根据上一个任务中给出的预期烘烤时间(分钟),返回千层面还需要在烤箱里待多久。 之前,你依赖的是来自remainingMinutesInOven函数外部的常量expectedMinutesInOven。 这样的写法对你来说既有点杂乱,又不够灵活,因为它不允许你修改预期的烘烤时间。

为了理清这些,请定义remainingMinutesInOven函数,它接受千层面已经在烤箱里度过的分钟数作为形参,还接受一个默认值为 40 的expectedMinutesInOven形参。 你的函数应根据上一个任务中给出的预期烘烤时间(分钟),返回千层面还需要在烤箱里待多久。

remainingMinutesInOven(elapsedMinutes: 30)
// Returns 10
remainingMinutesInOven(elapsedMinutes: 30, expectedMinutesInOven: 75)
// Returns 45

2. 允许层数不同

你之前写过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. 计算面条和酱料的用量

在开始组装千层面之前,你想确认自己是否有足够的酱料和面条,来做出梦想中的千层面。 千层面中每有一层,就需要 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)

4. 原地将升转换为液量盎司

不巧的是,你手头这瓶酱料的体积标注的是液量盎司,而不是升。 为了方便判断手头的酱料是否够用,请写一个toOz函数,它接受一个输入输出元组形参(noodles: Int, sauce: Double),并把酱料的用量转换为液量盎司。 注意,每升等于 33.814 液量盎司。

var amount = (noodles: 9, sauce: 0.8)
toOz(&amount)

amount
// Returns (noodles: 9, sauce: 27.0512)

5. 确定最适合搭配的葡萄酒

最后,你想知道吃千层面时该搭配哪种葡萄酒。 你听说,如果千层面里的马苏里拉奶酪、里科塔奶酪和白酱比肉和酱料多,就应该配白葡萄酒,否则配红葡萄酒。 请写一个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
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Swift Exercism

准备好开始 千层面大师 了吗?

注册 Exercism,借助 35 个概念116 个练习 和真人导师指导,学习并掌握 Swift,全部免费。