第3天 数组、字典、集合和枚举
今天,我们将学习一些更复杂的数据类型,它们将数据分组。了解它们之间的区别,尤其是知道何时使用哪种类型,有时会让人们在学习时犯错——正如约瑟夫·坎贝尔曾经说过的:“计算机就像《旧约》里的神:规矩森严,毫无怜悯之心。”
不过,别担心:您会发现我们今天学习的三种类型涵盖了分组数据所需的绝大多数要求,如果您在学习时选择了错误的类型,那么也不会发生任何不好的事情。
事实上,人们在学习过程中常犯的一个错误就是害怕运行自己的代码。相信我:只要你按照这里的所有代码来操作,你写的任何代码都不会损坏你的电脑。所以,修改一下代码,然后运行它。再修改一下代码,然后运行它。试着修改一下值,直到你对结果感到满意为止——这很有帮助。
今天您将学习四个教程,学习内容包括数组、字典、集合等等。 观看完每个视频后,如果您想进一步了解某个主题,可以阅读一些可选的额外阅读材料。此外,我们还提供一些简短的测试,帮助您确保理解所学内容。
3.1 如何在数组中存储有序数据
作者:Paul Hudson 2021 年 10 月 25 日 已针对 Xcode 16.4 更新
我们经常需要把大量数据集中放在一个地方,比如一周的天数、一个班级的学生名单、一个城市过去 100 年的人口数据,以及其他无数类似的例子。
在 Swift 中,我们使用数组来实现这种数据分组。数组和String、Int、Double一样,都是独立的数据类型,但它不是只存储一个字符串,而是可以存储零个、一个、两个、三个、五十个、五千万个甚至更多的字符串 —— 数组能够自动调整以容纳你需要的任意数量的数据,并且始终按照你添加数据的顺序来存储。
让我们从创建数组的一些简单示例开始:
var beatles = ["John", "Paul", "George", "Ringo"]
let numbers = [4, 8, 15, 16, 23, 42]
var temperatures = [25.3, 28.2, 26.4]这里创建了三个不同的数组:一个存储人名的字符串数组,一个存储重要数字的整数数组,还有一个存储摄氏度温度的小数数组。注意,数组的开头和结尾都使用方括号,每个元素之间用逗号分隔。
从数组中读取值时,我们通过元素在数组中的位置来获取。元素在数组中的位置通常称为索引。
这一点可能会让初学者感到困惑,但 Swift 实际上是从 0 而不是 1 开始计算元素的索引的 —— 例如,beatles[0]是第一个元素,beatles[1]是第二个元素。
因此,我们可以像这样从数组中读取一些值:
print(beatles[0])
print(numbers[1])
print(temperatures[2])提示: 确保你所访问的索引处确实存在元素,否则你的代码会崩溃 —— 应用程序会直接停止运行。
如果数组是可变的(用var声明),你可以在创建后对其进行修改。例如,你可以使用append()方法添加新元素:
beatles.append("Adrian")而且你可以多次添加同一个元素:
beatles.append("Allen")
beatles.append("Adrian")
beatles.append("Novall")
beatles.append("Vivian")不过,Swift 会监控你尝试添加的数据类型,并确保数组始终只包含一种类型的数据。因此,下面这种代码是不被允许的:
temperatures.append("Chris")这一点也适用于从数组中读取数据 ——Swift 知道beatles数组包含字符串,所以当你从中读取一个值时,得到的一定是字符串。如果你对numbers数组做同样的操作,得到的一定是整数。Swift 不允许你将这两种不同类型的数据混合使用,所以下面这种代码是不被允许的:
let firstBeatle = beatles[0]
let firstNumber = numbers[0]
let notAllowed = firstBeatle + firstNumber这就是类型安全,就像 Swift 不允许我们混合使用整数和小数一样,只是在这里更深入了一层。没错,beatles和numbers都是数组,但它们是特定类型的数组:一个是字符串数组,另一个是整数数组。
当你想从一个空数组开始,然后逐个添加元素时,这一点会更明显。这需要使用非常精确的语法:
var scores = Array<Int>()
scores.append(100)
scores.append(80)
scores.append(85)
print(scores[1])我们已经了解了最后四行代码,但第一行展示了如何创建一个特定类型的数组 —— 这不仅仅是一个普通的数组,而是一个存储整数的数组。这使得 Swift 能够确定beatles[0]一定是一个字符串,同时也阻止我们向字符串数组中添加整数。
Array<Int>后面的一对圆括号是因为在需要的时候,你可以自定义数组的创建方式。例如,你可能想在添加真实数据之前,先用大量的临时数据填充数组。
你可以通过不同的方式指定数组类型来创建其他种类的数组,如下所示:
var albums = Array<String>()
albums.append("Folklore")
albums.append("Fearless")
albums.append("Red")同样,我们已经指定了这个数组必须始终包含字符串,所以我们不能尝试向其中放入整数。
数组在 Swift 中非常常见,因此有一种特殊的创建方式:不用写Array<String>,你可以写成[String]。所以,下面的代码和之前的代码完全相同:
var albums = [String]()
albums.append("Folklore")
albums.append("Fearless")
albums.append("Red")Swift 的类型安全意味着它必须始终知道数组存储的数据类型。这可能意味着需要明确指定albums是Array<String>类型,但如果你提供了一些初始值,Swift 可以自己推断出来:
var albums = ["Folklore"]
albums.append("Fearless")
albums.append("Red")在结束之前,我想提一下数组的一些实用功能。
首先,你可以使用.count来获取数组中的元素数量,就像你对字符串做的那样:
print(albums.count)其次,你可以从数组中删除元素,使用remove(at:)方法删除特定索引处的一个元素,或者使用removeAll()方法删除所有元素:
var characters = ["Lana", "Pam", "Ray", "Sterling"]
print(characters.count)
characters.remove(at: 2)
print(characters.count)
characters.removeAll()
print(characters.count)这段代码会依次打印 4、3、0,因为元素被逐个删除了。
第三,你可以使用contains()方法检查数组是否包含某个特定元素,如下所示:
let bondMovies = ["Casino Royale", "Spectre", "No Time To Die"]
print(bondMovies.contains("Frozen"))第四,你可以使用sorted()方法对数组进行排序,如下所示:
let cities = ["London", "Tokyo", "Rome", "Budapest"]
print(cities.sorted())这会返回一个新的数组,其中的元素按升序排列(字符串按字母顺序,数字按数值大小),而原始数组保持不变。
最后,你可以通过调用reversed()方法来反转数组:
let presidents = ["Bush", "Obama", "Trump", "Biden"]
let reversedPresidents = presidents.reversed()
print(reversedPresidents)当你反转一个数组时,Swift 非常智能 —— 它实际上并不会重新排列所有元素,而只是记住你希望元素被反转。所以,当你打印reversedPresidents时,不要惊讶于它不再是一个简单的数组了!
数组在 Swift 中非常常见,随着你的学习深入,你会有很多机会更多地了解它们。更棒的是,sorted()、reversed()以及许多其他数组功能在字符串中也存在 —— 在字符串上使用sorted()会将字符串的字母按字母顺序排列,例如将 swift 变成 fistw。
【可选阅读】为什么 Swift 有数组?
作者:Paul Hudson 2020 年 5 月 21 日 已针对 Xcode 16.4 更新
Swift 中的字符串、整数、布尔值和双精度浮点数允许我们临时存储单个值,但如果你想存储多个值,通常会使用数组。
我们可以像创建其他数据类型的常量和变量一样创建数组的常量和变量,但不同之处在于数组内部可以容纳许多值。所以,如果你想存储工作日的名称、下周的温度预报或者电子游戏的高分记录,你会想要一个数组而不是单个值。
Swift 中的数组可以根据需要变得任意大或小。如果它们是变量,你可以自由地向其中添加元素以逐步构建数据,也可以根据需要删除甚至重新排列元素。
我们通过数值位置从数组中读取值,从 0 开始计数。这种 “从 0 开始计数” 有一个专业术语:我们可以说 Swift 的数组是零基的。如果你尝试使用无效的索引读取数组,Swift 会自动使你的程序崩溃。例如,创建一个包含三个元素的数组,然后尝试读取索引 10 的元素。
我知道你在想什么:应用程序崩溃是不好的,对吧?没错。但相信我:如果 Swift没有崩溃,那么你很可能会得到错误的数据,因为你尝试读取了数组所包含范围之外的值。
【练习题】数组
问题 1/6:以下哪些行创建了数组?
选项 1:
let height = "14.0"选项 2:
var temperatures = [32.0]问题 2/6:以下哪些行创建了数组?
选项 1:
var averages = [98.5, 97.1, 99.9]选项 2:
let status = false, true, true, true问题 3/6:以下哪些行创建了数组?
选项 1:
var cities: [String] = ["London", "Paris", "New York"]选项 2:
let playlistSizes = (100, 200, 300)问题 4/6:以下哪些行创建了数组?
选项 1:
let characters: [Int] = ["Doctor Who"]选项 2:
var readings: [Bool] = [false, false, true, false]问题 4/6:以下哪些行创建了数组?
选项 1:
let characters: [Int] = ["Doctor Who"]选项 2:
var readings: [Bool] = [false, false, true, false]问题 5/6:以下哪些行创建了数组?
选项 1:
var scores: [Int] = [10, 12, 9]选项 2:
let breeds = {"Labrador", "Chihuahua"}问题 6/6:以下哪些行创建了数组?
选项 1:
var singers = ["Taylor", "Adele", "Justin"]选项 2:
let age = 263.2 如何在字典中存储和查找数据
作者:Paul Hudson 2021 年 10 月 25 日 已针对 Xcode 16.4 更新
你已经了解到,数组是存储具有特定顺序的数据的好方法,例如一周中的几天或一个城市的温度。当项目需要按照添加的顺序存储,或者可能存在重复项目时,数组是一个很好的选择,但很多时候通过项目在数组中的位置来访问数据可能很麻烦,甚至很危险。
例如,下面是一个包含员工详细信息的数组:
var employee = ["泰勒·斯威夫特", "歌手", "纳什维尔"]我已经告诉你这些数据是关于一名员工的,所以你可能能猜到各个部分的含义:
print("姓名:\(employee[0])")
print("职位:\(employee[1])")
print("所在地:\(employee[2])")但这存在几个问题。首先,你不能确定employee[2]就是他们的所在地 —— 也许那是他们的密码。其次,不能保证索引 2 处一定有值,特别是因为我们将数组定义为了变量。下面这种代码会导致严重的问题:
print("姓名:\(employee[0])")
employee.remove(at: 1)
print("职位:\(employee[1])")
print("所在地:\(employee[2])")现在,代码会将纳什维尔打印为职位,这是错误的,并且当读取employee[2]时会导致程序崩溃,这非常糟糕。
Swift 为这两个问题提供了一个解决方案,叫做字典。字典不像数组那样根据位置存储项目,而是让我们决定项目应该存储在哪里。
例如,我们可以重写前面的例子,更明确地表示每个项目的含义:
let employee2 = ["name": "泰勒·斯威夫特", "job": "歌手", "location": "纳什维尔"]如果我们将其拆分成单独的行,你会更清楚这段代码的作用:
let employee2 = [
"name": "泰勒·斯威夫特",
"job": "歌手",
"location": "纳什维尔"
]如你所见,我们现在表达得非常清楚:姓名是泰勒・斯威夫特,职位是歌手,所在地是纳什维尔。Swift 将左边的字符串 ——name、job 和 location—— 称为字典的键,右边的字符串是值。
从字典中读取数据时,使用创建字典时所用的键:
print(employee2["name"])
print(employee2["job"])
print(employee2["location"])如果你在 playground 中尝试这段代码,会看到 Xcode 弹出各种警告,大致内容为 “表达式从 'String?' 隐式转换为 'Any'”。更糟的是,如果你查看 playground 的输出,会发现打印的是Optional("泰勒·斯威夫特"),而不仅仅是泰勒·斯威夫特—— 这是怎么回事?
好吧,想想这个:
print(employee2["password"])
print(employee2["status"])
print(employee2["manager"])所有这些都是有效的 Swift 代码,但我们尝试读取的是没有对应值的字典键。当然,Swift可以在这里崩溃,就像读取不存在的数组索引时会崩溃一样,但这会让使用变得非常困难 —— 至少如果一个数组有 10 个项目,你知道读取索引 0 到 9 是安全的。
所以,Swift 提供了另一种方式:当你访问字典中的数据时,它会告诉我们 “你可能会得到一个值,但也可能什么都得不到”。Swift 称之为可选类型,因为数据的存在是可选的 —— 可能存在,也可能不存在。
Swift 甚至会在你编写代码时发出警告,尽管方式相当晦涩 —— 它会说 “表达式从 'String?' 隐式转换为 'Any'”,但实际上是说 “这些数据可能并不存在 —— 你确定要打印它吗?”
可选类型是一个相当复杂的问题,我们稍后会详细介绍,但现在我会给你展示一个更简单的方法:从字典中读取数据时,你可以提供一个默认值,以便在键不存在时使用。
代码如下:
print(employee2["name", default: "未知"])
print(employee2["job", default: "未知"])
print(employee2["location", default: "未知"])所有示例的键和值都使用了字符串,但你也可以为它们使用其他数据类型。例如,我们可以使用字符串表示学生姓名,布尔值表示他们的毕业状态,来记录哪些学生已经从学校毕业:
let hasGraduated = [
"埃里克": false,
"梅芙": true,
"奥蒂斯": false,
]或者我们可以记录奥运会举办的年份及其举办地点:
let olympics = [
2012: "伦敦",
2016: "里约热内卢",
2021: "东京"
]
print(olympics[2012, default: "未知"])你也可以创建一个空字典,指定要存储的明确类型,然后逐个设置键值:
var heights = [String: Int]()
heights["姚明"] = 229
heights["沙奎尔·奥尼尔"] = 216
heights["勒布朗·詹姆斯"] = 206注意,现在我们需要写成[String: Int],表示这是一个键为字符串、值为整数的字典。
因为每个字典项必须存在于一个特定的键下,所以字典不允许存在重复的键。相反,如果你为一个已存在的键设置值,Swift 会覆盖之前的值。
例如,如果你和朋友聊起超级英雄和超级反派,你可能会像这样把他们存储在字典中:
var archEnemies = [String: String]()
archEnemies["蝙蝠侠"] = "小丑"
archEnemies["超人"] = "莱克斯·卢瑟"如果你的朋友不同意小丑是蝙蝠侠的主要敌人,你可以通过使用相同的键来重写该值:
archEnemies["蝙蝠侠"] = "企鹅人"最后,就像我们到目前为止看到的数组和其他数据类型一样,字典也有一些你将来会用到的有用功能 ——count和removeAll()在字典中都存在,并且它们的工作方式与在数组中相同。
【可选阅读】为什么 Swift 既有字典又有数组?
作者:Paul Hudson 2021 年 10 月 6 日 已针对 Xcode 16.4 更新
字典和数组都是在一个变量中存储大量数据的方式,但它们的存储方式不同:字典允许我们选择一个 “键” 来标识我们想要添加的项,而数组只是按顺序添加每个项。
因此,我们不必记住数组索引 7 代表用户的国家,只需编写 user["country"]—— 这要方便得多。
字典不会使用索引来存储我们的项,而是优化了存储方式以实现快速检索。所以,当我们使用 user["country"] 时,即使字典中有 100,000 个项,它也能立即返回该键对应的项(或 nil)。
请记住,不能保证字典中的某个键一定存在。这就是为什么从字典中读取值可能会返回空 —— 你可能请求了一个不存在的键!
【可选阅读】为什么 Swift 的字典有默认值?
作者:Paul Hudson 2020 年 5 月 21 日 已针对 Xcode 16.4 更新
每当你从字典中读取值时,你可能会得到一个值,也可能得到 nil—— 该键可能没有对应的值。没有值可能会给你的代码带来问题,尤其是因为你需要添加额外的功能来安全地处理缺失的值,这就是字典默认值的用武之地:它们允许你提供一个备用值,用于当你请求的键不存在时。
例如,这里有一个存储学生考试成绩的字典:
let results = [
"english": 100,
"french": 85,
"geography": 75
]如你所见,他们参加了三门考试,英语、法语和地理分别得了 100%、85% 和 75%。如果我们想读取他们的历史成绩,方法取决于我们的需求:
- 如果缺失的值意味着学生没有参加考试,那么我们可以使用默认值 0,这样我们总能得到一个整数。
- 如果缺失的值意味着学生尚未参加考试,那么我们应该跳过默认值,而是查找 nil 值。
所以,并不是在使用字典时总是需要默认值,但当你需要时,它很简单:
let historyResult = results["history", default: 0]【练习题】字典
问题 1/6:以下哪些行创建了字典?
选项 1:
var roles = ["captain": "Mal", "engineer": "Kaylee"]选项 2:
var place = ["road", "Park Lane", "city", "Cardiff"]问题 2/6:以下哪些行创建了字典?
选项 1:
let location = ("road": "Park Lane", "city": "Cardiff")选项 2:
let heights = ["Taylor Swift": 1.78]问题 3/6:以下哪些行创建了字典?
选项 1:
var address = ["road": "Park Lane", "city": "Cardiff"]选项 2:
var books = ["The Jungle Book"]问题 4/6:以下哪些行创建了字典?
选项 1:
var speed = 60.75选项 2:
var capitals = ["England": "London", "Scotland": "Edinburgh"]问题 5/6:以下哪些行创建了字典?
选项 1:
let forecast = ["Monday": "sunny", "Tuesday": "cloudy"]选项 2:
let isVisible = true问题 6/6:以下哪些行创建了字典?
选项 1:
let scores = ["Sophie": 100]选项 2:
let password = "fr0sti3s"【练习题】字典默认值
问题 1/12:这段代码是有效的 Swift 代码 —— 对还是错?
let ships = ["Star Trek", "Enterprise"]
let enterprise = ships["Star Trek"]问题 2/12:这段代码是有效的 Swift 代码 —— 对还是错?
let planets = [1: "Mercury", 2: "Venus"]
let venus = planets[2, default: "Planet X"]问题 3/12:这段代码是有效的 Swift 代码 —— 对还是错?
let ratings = [1: "Bad", 2: "OK", 3: "Good"]
let rating = ratings["2"]问题 4/12:这段代码是有效的 Swift 代码 —— 对还是错?
let capitals = ["England": "London", "Wales": "Cardiff"]
let scotlandCapital = capitals["Scotland"]问题 5/12:这段代码是有效的 Swift 代码 —— 对还是错?
let olympics = [2012: "London", 2016: "Rio", 2020: "Tokyo"]
let london = olympics[2012]问题 6/12:这段代码是有效的 Swift 代码 —— 对还是错?
let users = ["Taylor", "Taylor Swift"]
let taylor = users["Taylor", default: "Anonymous"]问题 7/12:这段代码是有效的 Swift 代码 —— 对还是错?
let books = ["Austen": "Pride and Prejudice"]
let dickens = books["Dickens", default: "Unknown"]问题 8/12:这段代码是有效的 Swift 代码 —— 对还是错?
let prices = ["Milk": 1, "Pepsi": 2]
let first = prices[0]问题 9/12:这段代码是有效的 Swift 代码 —— 对还是错?
let albums = ["Prince": "Purple Rain"]
let beatles = albums["Beatles"]问题 10/12:这段代码是有效的 Swift 代码 —— 对还是错?
let abbreviations = ["m": "meters", "km": "kilometers"]
let meters = abbreviations["m", default "m"]问题 11/12:这段代码是有效的 Swift 代码 —— 对还是错?
let characters = ["Captain": "Malcolm", "Engineer": "Kaylee"]
let captain = characters["Captain"]问题 12/12:这段代码是有效的 Swift 代码 —— 对还是错?
let scores = ["Paul": 80, "Sophie": 100]
let john = scores[john]3.3 如何使用集合进行快速数据查找
作者:Paul Hudson 2021 年 11 月 8 日 已针对 Xcode 16.4 更新
到目前为止,你已经学习了在 Swift 中收集数据的两种方式:数组和字典。还有第三种非常常见的组合数据的方式,叫做 “集合”(set)—— 它们类似于数组,不同之处在于你不能添加重复的项目,而且它们不会按特定顺序存储项目。
创建集合的方式与创建数组很相似:告诉 Swift 它将存储哪种类型的数据,然后就可以添加内容了。不过,有两个重要的区别,通过一些代码能最好地展示出来。
首先,以下是创建一个演员名字集合的方法:
let people = Set(["丹泽尔·华盛顿", "汤姆·克鲁斯", "尼古拉斯·凯奇", "塞缪尔·L·杰克逊"])注意到这里实际上是先创建一个数组,然后把这个数组放入集合中吗?这是有意为之的,也是从固定数据创建集合的标准方式。记住,集合会自动移除任何重复的值,而且不会记住数组中使用的确切顺序。
如果你好奇集合是如何对数据进行排序的,只需尝试打印出来:
print(people)你可能会看到名字按原始顺序排列,但也可能得到完全不同的顺序 —— 集合根本不关心其项目的顺序。
向集合中添加项目时,第二个重要区别在单独添加项目时会显现出来。代码如下:
var people = Set<String>()
people.insert("丹泽尔·华盛顿")
people.insert("汤姆·克鲁斯")
people.insert("尼古拉斯·凯奇")
people.insert("塞缪尔·L·杰克逊")注意到我们使用的是insert()吗?当我们有一个字符串数组时,我们通过调用append()来添加项目,但这个名称在这里并不合适 —— 我们不是把项目添加到集合的末尾,因为集合会按它自己的意愿存储项目。
现在,你可能会认为集合听起来就像简化版的数组 —— 毕竟,如果你不能有重复项,还会丢失项目的顺序,为什么不直接使用数组呢?好吧,这两个限制实际上反而成了优势。
首先,不存储重复项有时正是你想要的。我在前面的例子中选择演员是有原因的:美国演员工会要求其所有成员都有一个独特的艺名,以避免混淆,这意味着绝不能有重复的名字。例如,演员迈克尔・基顿(出演《蜘蛛侠:英雄归来》《玩具总动员 3》《蝙蝠侠》等)实际上本名叫迈克尔・道格拉斯,但因为工会里已经有一个迈克尔・道格拉斯(出演《复仇者联盟》《怒火风暴》《绿宝石》等),他就必须有一个独特的名字。
其次,集合不会按照你指定的确切顺序存储项目,而是以一种高度优化的顺序存储,这样可以非常快速地找到项目。而且这种差异还不小:如果你有一个包含 1000 个电影名称的数组,使用contains()来检查它是否包含《黑暗骑士》,Swift 需要遍历每一个项目,直到找到匹配的为止 —— 这可能意味着要检查所有 1000 个电影名称后才返回 false,因为《黑暗骑士》不在数组中。
相比之下,在集合上调用contains()的速度快到你几乎无法有意义地测量它。哎呀,即使你的集合中有一百万个项目,甚至一千万个项目,它仍然能瞬间运行,而数组可能需要几分钟甚至更长时间才能完成同样的工作。
大多数时候,你会发现自己使用数组而不是集合,但有时候 —— 只是有时候 —— 你会发现集合正是解决特定问题的正确选择,它能让原本缓慢的代码瞬间运行起来。
提示: 除了contains(),你还会发现count用于读取集合中的项目数量,sorted()用于返回一个包含集合项目的排序数组。
【可选阅读】在 Swift 中,集合与数组有何不同?
作者:Paul Hudson 2020 年 5 月 28 日 已针对 Xcode 16.4 更新
集合和数组在 Swift 中都很重要,了解它们的区别将帮助你明白在任何特定情况下应该选择哪一个。
集合和数组都是数据的集合,这意味着它们在一个变量中包含多个值。然而,它们存储值的 “方式” 才是关键:集合是无序的,不能包含重复项,而数组保留其顺序,并且可以包含重复项。
这两个区别可能看起来很小,但它们有一个有趣的副作用:因为集合不需要按照你添加它们的顺序存储对象,所以它们可以以一种看似随机但能优化快速检索的顺序存储。所以,当你说 “这个集合包含项目 X 吗”,无论集合有多大,你都会在瞬间得到答案。
相比之下,数组必须按照你给出的顺序存储项目,所以要检查包含 10,000 个项目的数组中是否存在项目 X,Swift 需要从第一个项目开始,检查每一个项目,直到找到它 —— 或者可能根本找不到。
这种差异意味着集合在你想判断 “这个项目是否存在” 时更有用。例如,如果你想检查一个单词是否出现在字典中,你应该使用集合:这里没有重复项,而且你希望进行快速查找。
【练习题】集合
问题 1/12:这将创建一个包含两个项目的集合 —— 对还是错?
var readings = Set([true, false, true, true])问题 2/12:这将创建一个包含两个项目的集合 —— 对还是错?
var attendees = Set([100, 100, 101, 100])问题 3/12:这将创建一个包含两个项目的集合 —— 对还是错?
let users = ["泰勒", "阿黛尔"]问题 4/12:这将创建一个包含两个项目的集合 —— 对还是错?
let earthquakeStrengths = Set(1, 1, 2, 2)问题 5/12:这将创建一个包含两个项目的集合 —— 对还是错?
let cats = ["缅甸猫", "暹罗猫", "波斯猫"]问题 6/12:这将创建一个包含两个项目的集合 —— 对还是错?
var names = Set(["肖恩", "保罗"])问题 7/12:这将创建一个包含两个项目的集合 —— 对还是错?
var colors = Set(["红色", "绿色", "红色"])问题 8/12:这将创建一个包含两个项目的集合 —— 对还是错?
let staffReviews = Set([1, 2, 1, 2, 3])问题 9/12:这将创建一个包含两个项目的集合 —— 对还是错?
var ratings = Set([1, 1, 1, 2, 2, 2])问题 10/12:这将创建一个包含两个项目的集合 —— 对还是错?
let playlistSizes = Set([1000])问题 11/12:这将创建一个包含两个项目的集合 —— 对还是错?
var scores = Set([9, 10])问题 12/12:这将创建一个包含两个项目的集合 —— 对还是错?
let averageHeights = Set([1.71, 1.72, 1.73])3.4 如何创建和使用枚举
作者:Paul Hudson 2021 年 11 月 1 日 已针对 Xcode 16.4 更新
枚举(enum,enumeration 的缩写)是我们可以在代码中创建和使用的一组命名值。它们对 Swift 来说没有什么特殊意义,但它们更高效、更安全,所以你会在代码中经常用到它们。
为了说明问题,假设你想编写一些代码让用户选择一周中的某一天。你可能会这样开始:
var selected = "Monday"之后在代码中你会修改它,像这样:
selected = "Tuesday"这在非常简单的程序中可能运行良好,但看看这段代码:
selected = "January"哎呀!你不小心输入了一个月份而不是一天 —— 你的代码会怎么做呢?好吧,幸运的话,你的同事在审查代码时可能会发现这个错误,但再看这个:
selected = "Friday "在 “Friday” 后面有一个空格,在 Swift 看来,带空格的 “Friday ” 和不带空格的 “Friday” 是不一样的。同样,你的代码会如何处理呢?
用字符串来处理这类事情需要非常细致的编程,但同时也相当低效 —— 我们真的需要存储 “Friday” 所有的字母来记录单独的一天吗?
这就是枚举的用武之地:它们让我们可以定义一种新的数据类型,并预先设定它可以拥有的一系列特定值。想想布尔值,它只能是 true 或 false—— 你不能把它设为 “maybe” 或 “probably”,因为这不在它能理解的值的范围内。枚举也是如此:我们预先列出它可能拥有的值的范围,Swift 会确保你在使用它们时不会出错。
所以,我们可以把工作日重写成一个新的枚举,像这样:
enum Weekday {
case monday
case tuesday
case wednesday
case thursday
case friday
}这定义了一个名为Weekday的新枚举,并提供了五个 case 来对应五个工作日。
现在,我们会使用这个枚举而不是字符串。在你的 playground 中试试这个:
var day = Weekday.monday
day = Weekday.tuesday
day = Weekday.friday有了这个改变,你就不会不小心使用带额外空格的 “Friday”,或者用月份名称来代替了 —— 你必须始终从枚举中列出的可能的日子里进行选择。当你输入Weekday.时,Swift 甚至会为你提供所有可能的选项,因为它知道你要选择其中一个 case。
Swift 做了两件事让枚举的使用更简单。第一,当枚举中有多个 case 时,你可以只写一次case,然后用逗号分隔每个 case:
enum Weekday {
case monday, tuesday, wednesday, thursday, friday
}第二,记住,当你给变量或常量赋值后,它的数据类型就固定了 —— 你不能先把一个变量设为字符串,然后又设为整数。对于枚举来说,这意味着在第一次赋值后,你可以省略枚举名称,像这样:
var day = Weekday.monday
day = .tuesday
day = .fridaySwift 知道.tuesday一定指的是Weekday.tuesday,因为day必须始终是某种Weekday类型。
虽然在这里看不到,但枚举的一个主要好处是 Swift 以优化的形式存储它们 —— 当我们说Weekday.monday时,Swift 可能会用一个整数(比如 0)来存储它,这比存储 “Monday” 的各个字母要高效得多,无论是存储还是检查。
【可选阅读】Swift 为什么需要枚举?
作者:Paul Hudson 2021 年 3 月 11 日 已针对 Xcode 16.4 更新
枚举是 Swift 的一个极其强大的特性,你会看到它们以各种各样的方式被用在很多地方。许多语言没有枚举也能正常运行,所以你可能会想,Swift 为什么到底需要枚举呢!
好吧,简单来说,枚举就是给一个值起的一个好听的名字。我们可以创建一个名为Direction的枚举,包含north、south、east和west这些 case,并在代码中引用它们。当然,我们也可以用整数来代替,这样我们就会引用 1、2、3 和 4,但你真的能记住 3 代表什么吗?而且如果你不小心输入了 5 又会怎样呢?
所以,枚举是我们用Direction.north来表示特定且安全的事物的一种方式。如果我们写了Direction.thatWay,而这样的 case 并不存在,Swift 就会拒绝编译我们的代码 —— 它不理解这个枚举 case。在幕后,Swift 可以非常简单地存储它的枚举值,所以它们比字符串之类的东西创建和存储起来快得多。
随着你的深入学习,你会了解到 Swift 如何让我们给枚举添加更多功能 —— 在 Swift 中,枚举比我所见过的任何其他语言中的枚举都更强大。
【练习题】枚举
问题 1/6:以下哪些最适合用枚举存储?
选项 1:方向(北、南等)。
选项 2:物体的高度。
问题 2/6:以下哪些最适合用枚举存储?
选项 1:你去过的城市列表。
选项 2:错误类型。
问题 3/6:以下哪些最适合用枚举存储?
选项 1:电影类型。
选项 2:电子邮件地址。
问题 4/6:以下哪些最适合用枚举存储?
选项 1:学生考试平均分。
选项 2:一副牌中的花色。
问题 5/6:以下哪些最适合用枚举存储?
选项 1:一年中的月份。
选项 2:密码。
问题 6/6:以下哪些最适合用枚举存储?
选项 1:一周中的日子。
选项 2:用户名。