第4天 类型注解
今天我们将通过学习类型注解来完成对数据类型的探讨,类型注解是 Swift 中让我们能够精确指定每个变量和常量应为何种数据类型的方式。学完之后,我们会总结所学内容,然后进行另一个 checkpoint(检查点)练习,以便你评估到目前为止的学习成果。
我知道,此时你可能已经对数据类型感到厌烦了,但就像埃里克・雷蒙德所说:“好的数据结构搭配糟糕的代码,比反过来要好得多。”
今天你将学习类型注解,然后浏览总结内容并完成检查点练习。 还有一些可选的拓展阅读内容我推荐你看看,另外还有一个简短的测试,帮助你确认自己是否理解了所学知识。
4.1 如何使用类型注解
作者:Paul Hudson 2021 年 12 月 28 日 已针对 Xcode 16.4 更新
Swift 能够根据我们赋给常量或变量的值,推断出它们所存储的数据类型。然而,有时我们不想立即赋值,或者有时我们想覆盖 Swift 对类型的选择,这时候类型注解就派上用场了。
到目前为止,我们创建常量和变量的方式是这样的:
let surname = "Lasso"
var score = 0这用到了类型推断:Swift 推断surname是字符串,因为我们给它赋了文本;然后推断score是整数,因为我们给它赋了一个整数。
类型注解让我们可以明确指定想要的数据类型,格式如下:
let surname: String = "Lasso"
var score: Int = 0现在我们明确了:surname必须是字符串,score必须是整数。这正是 Swift 的类型推断本来会做的事,但有时并非如此 —— 有时你会想选择不同的类型。
例如,也许score是一个小数,因为用户可能获得半分,所以你会这样写:
var score: Double = 0如果没有: Double这部分,Swift 会推断它是整数,但我们在这里覆盖了这个推断,表明它肯定是一个小数。
到目前为止,我们已经了解了几种数据类型,知道它们的名称很重要,这样在需要时你才能使用正确的类型注解。
String存储文本:
let playerName: String = "Roy"Int存储整数:
var luckyNumber: Int = 13Double存储小数:
let pi: Double = 3.141Bool存储true(真)或false(假):
var isAuthenticated: Bool = trueArray(数组)存储许多不同的值,所有值都按照添加的顺序排列。数组必须指定具体类型,例如[String](字符串数组):
var albums: [String] = ["Red", "Fearless"]Dictionary(字典)存储许多不同的值,你可以决定如何访问这些数据。字典也必须指定具体类型,例如[String: Int](键为字符串、值为整数的字典):
var user: [String: String] = ["id": "@twostraws"]Set(集合)存储许多不同的值,但它以一种便于检查包含内容的优化顺序来存储这些值。集合同样必须指定具体类型,例如Set<String>(字符串集合):
var books: Set<String> = Set(["The Bluest Eye", "Foundation", "Girl, Woman, Other"])了解所有这些类型对于不需要提供初始值的情况很重要。例如,下面创建了一个字符串数组:
var soda: [String] = ["Coke", "Pepsi", "Irn-Bru"]这里不需要类型注解,因为 Swift 能看出你正在赋值一个字符串数组。但是,如果你想创建一个空的字符串数组,你就需要知道它的类型:
var teams: [String] = [String]()同样,类型注解不是必需的,但你仍然需要知道字符串数组写作[String],这样才能创建它。记住,创建空数组、空字典和空集合时,需要加上圆括号,因为这是 Swift 允许我们自定义它们创建方式的地方。
有些人喜欢先使用类型注解,然后给它赋一个空数组,像这样:
var cities: [String] = []我更喜欢尽可能使用类型推断,所以我会这样写:
var clues = [String]()除了这些类型,还有枚举(enums)。枚举与其他类型略有不同,因为它们允许我们创建自己的新类型,例如包含一周中各天的枚举、包含用户想要的 UI 主题的枚举,甚至包含我们的应用当前显示的屏幕的枚举。
枚举的值与枚举本身具有相同的类型,所以我们可以这样写:
enum UIStyle {
case light, dark, system
}
var style = UIStyle.light这使得 Swift 在后续赋值时可以省略枚举名称,所以我们可以写style = .dark—— 它知道style的任何新值都必须是某种UIStyle类型。
现在,你很可能会问什么时候应该使用类型注解,所以让你知道我的偏好可能会有帮助:我尽可能使用类型推断,也就是说,我给常量或变量赋值,然后让 Swift 自动选择正确的类型。有时这意味着使用像var score = 0.0这样的写法,这样我就能得到一个Double类型。
最常见的例外情况是对于那些暂时没有值的常量。你看,Swift 非常智能:你可以创建一个暂时没有值的常量,稍后再提供这个值,而 Swift 会确保我们在值被赋予之前不会意外使用它。它还会确保你只给这个值赋值一次,以保持它的常量特性。
例如:
let username: String
// 许多复杂的逻辑
username = "@twostraws"
// 更多复杂的逻辑
print(username)这段代码是合法的:我们表示username在某个时候会包含一个字符串,并且我们在使用它之前提供了一个值。如果缺少赋值那一行 ——username = "@twostraws",那么 Swift 会拒绝编译我们的代码,因为username没有值;同样,如果我们试图给username第二次赋值,Swift 也会报错。
这种代码需要类型注解,因为如果没有初始值,Swift 就不知道username将包含哪种类型的数据。
无论你使用类型推断还是类型注解,都有一个黄金法则:Swift 必须始终知道你的常量和变量包含的数据类型。这是 Swift 作为类型安全语言的核心,它防止我们做一些无意义的事情,比如5 + true之类的操作。
重要提示: 尽管类型注解在一定程度上可以让我们覆盖 Swift 的类型推断,但我们最终的代码必须是可行的。例如,下面的写法是不被允许的:
let score: Int = "Zero"Swift 无法为我们把 “Zero” 转换为整数,即使有类型注解要求这样做,所以这段代码也无法编译。
【可选阅读】Swift 为什么要有类型注解?
作者:Paul Hudson 2020 年 5 月 21 日 已针对 Xcode 16.4 更新
学习 Swift 时,人们经常会问 “Swift 为什么要有类型注解?”,通常接下来还会问 “在 Swift 中应该什么时候使用类型注解?”
第一个问题的答案主要有三个原因:
- Swift 无法确定应该使用哪种类型。
- 你希望 Swift 使用与默认类型不同的类型。
- 你不想马上赋值。
第一个原因通常只在更高级的代码中出现。例如,如果你从互联网上加载一些数据,你知道这些数据是当地政客的名字,但 Swift 无法提前知道这一点,所以你需要告诉它。
第二个场景在你深入学习 Swift 时会很常见,但现在举一个简单的例子:创建一个双精度浮点型变量,而不必到处写 .0:
var percentage: Double = 99这使得percentage成为一个值为 99.0 的双精度浮点型变量。是的,我们给它赋了一个整数,但我们的类型注解明确表示我们想要的实际数据类型是双精度浮点型。
第三个选项适用于你想告诉 Swift 某个变量将要存在,但不想立即设置它的值的情况。这在 Swift 中很多地方都会出现,写法如下:
var name: String之后你可以给name赋一个字符串,但不能赋其他类型,因为 Swift 知道那是无效的。
当然,第二个问题 “在 Swift 中应该什么时候使用类型注解?” 的答案更为主观,因为这通常取决于个人风格。
在我自己的代码中,我更喜欢尽可能使用类型推断。这意味着我会省略类型注解,让 Swift 根据我存储的数据来推断事物的类型。我这样做的原因是:
- 这使我的代码更简短,更易于阅读。
- 这让我只需改变初始值就能改变某个事物的类型。
有些人更喜欢总是使用显式的类型注解,这也没问题 —— 这确实是一个风格问题。
【可选阅读】为什么要创建空集合?
作者:Paul Hudson 2020 年 5 月 28 日 已针对 Xcode 16.4 更新
刚开始学习 Swift 时,经常会看到这样的例子:
let names = ["Eleanor", "Chidi", "Tahani", "Jianyu", "Michael", "Janet"]这是一个包含 6 个字符串的常量数组,因为它是常量,所以我们不能向这个数组添加更多元素 —— 在创建数组时我们就知道了所有元素,所以程序的其余部分只使用这些固定数据。
但有时你不能预先知道所有数据,在这种情况下,更常见的做法是创建一个空集合,然后在计算数据时添加元素。
例如,我们有上面那个固定的names数组,如果我们想找出哪些名字以 "J" 开头,我们会:
- 创建一个空的字符串数组,比如叫做
jNames。 - 遍历原始
names数组中的每个名字,检查它是否以 "J" 开头。 - 如果是,就把它添加到
jNames数组中。
遍历完所有名字后,jNames数组中最终会有两个字符串:Jianyu 和 Janet。当然,如果我们的检查条件是名字以 “X” 开头,那么数组最终会是空的 —— 这也没关系。它开始是空的,最后也是空的。
在本书的后面部分,我们将学习实现所有这些功能所需的 Swift 代码。
【练习题】类型注解
问题 1/6:以下哪些行创建了 Double 类型?
选项 1:
var average = 32.0选项 2:
let size = "14.0"问题 2/6:以下哪些行创建了 Double 类型?
选项 1:
var sales: Double = 100_000选项 2:
let city = "Tokyo"问题 3/6:以下哪些行创建了 Double 类型?
选项 1:
let tweeted = false选项 2:
var populationMillions = 12.5问题 4/6:以下哪些行创建了 Double 类型?
选项 1:
var distance: Double = 320选项 2:
let mode = "writing"问题 5/6:以下哪些行创建了 Double 类型?
选项 1:
var depth = 10.2选项 2:
let speed = 50问题 6/6:以下哪些行创建了 Double 类型?
选项 1:
var score: Double = 1000选项 2:
let month = 64.2 摘要:复杂数据
作者:Paul Hudson 2021 年 10 月 3 日 已针对 Xcode 16.4 更新
我们现在已经超越了简单的数据类型,开始研究将它们组合在一起的方法,甚至使用枚举来创建我们自己的数据类型。所以,让我们回顾一下:
- 数组让我们可以将许多值存储在一个地方,然后使用整数索引读取它们。数组必须始终是特定类型的,即只能包含一种特定类型的数据,并且它们具有有用的功能,如
count、append()和contains()。 - 字典也让我们可以将许多值存储在一个地方,但允许我们使用自己指定的键来读取它们。字典必须指定一种特定的键类型和另一种特定的值类型,并且具有与数组类似的功能,如
contains()和count。 - 集合是第三种将许多值存储在一个地方的方式,但我们无法选择它们存储这些项目的顺序。集合在查找是否包含特定项目方面非常高效。
- 枚举让我们可以在 Swift 中创建自己的简单类型,这样我们就可以指定一系列可接受的值,例如用户可以执行的操作列表、我们能够写入的文件类型或要发送给用户的通知类型。
- Swift 必须始终知道常量或变量内部的数据类型,并且通常使用类型推断根据我们分配的数据来确定类型。不过,也可以使用类型注解来强制指定特定类型。
在数组、字典和集合中,可以肯定地说,你使用数组的次数会多得多。其次是字典,集合则远远排在第三位。这并不意味着集合没有用,而是说当你需要它们时,你会知道的!
4.3 检查点 2
作者:Paul Hudson 2021 年 10 月 25 日 已针对 Xcode 16.4 更新
既然你已经了解了数组、字典和集合,我想暂停一下,给你一个机会来解决一个小的编码挑战。这个挑战不是为了难住你,而是为了鼓励你停下来思考一下你所学到的知识。
这次的挑战是创建一个字符串数组,然后编写一些代码来打印数组中的项目数量以及数组中唯一项目的数量。
下面我会提供一些提示,但请在阅读提示之前花时间思考解决方案。相信我:忘记所学的知识然后重新学习,实际上会让它更深刻地印在你的脑海中!
以下是一些提示:
- 你应该先创建一个字符串数组,比如
let albums = ["Red", "Fearless"]。 - 你可以使用
albums.count来读取数组中的项目数量。 count功能在集合中也存在。;- 可以使用
Set(someArray)从数组创建集合。 - 集合从不包含重复项。
提示: 尽管这个检查点要求不高,但如果你花了一些时间盯着屏幕不知道该做什么,也不要感到惊讶。这并不是坏事 —— 事实上,我认为这是个好迹象。我坚信没有挣扎就没有学习,所以不要害怕为之努力!