53.计算字符
在字符串中获取字符值的数量, 可以使用字符串字符属性中的计数属性:
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.characters.count) characters")
// 打印 "unusualMenagerie has 40 characters"
注意 Swift 对字符值使用扩展字形集,意味着字符拼接和修改可能不会总是影响字符串的字符数。
例如, 如果你用四个字符的单词咖啡来初始化一个新字符串, 然后添加 COMBINING ACUTE ACCENT (U+0301) 到字符串的尾部, 最后字符的数量还是4, 第四个字符是 é, 而不是 e:
var word = "cafe"
print("the number of characters in \(word) is \(word.characters.count)")
// 打印 "the number of characters in cafe is 4"
word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301
print("the number of characters in \(word) is \(word.characters.count)")
// 打印 "the number of characters in café is 4"
54.访问和修改字符串
你可以通过方法和属性来访问和修改字符串, 或者用下标语法。
55.字符串索引
每个字符串值都有一个对应的索引类型, String.Index, 代表每个字符在字符串中的位置。
上面提到的,不同字符要求不等数量的内存, 所以为了决定哪个字符在一个特定的位置, 你必须从头到尾枚举每个 Unicode 标量。 因为这个原因, Swift 字符串不能通过整数值来索引。
使用 startIndex 属性访问字符串的首字符位置。 endIndex 属性是字符串中最后一个字符的位置。 因此, endIndex 属性不是一个字符串下标的有效参数。 如果一个字符串是空的, startIndex 和 endIndex 相等。
你可以用 index(before:) 和 index(after:) 字符串方法访问给定索引前后的索引, 你可以使用 index(_:offsetBy:) 方法代替多次调用这些方法。
你可以使用下标语法访问特定索引位置的字符。
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a
尝试访问越界的索引和字符都会引发运行时的错误。
greeting[greeting.endIndex] // Error
greeting.index(after: greeting.endIndex) // Error
用字符属性中的索引属性获取字符串中的所有字符。
for index in greeting.characters.indices {
print("\(greeting[index]) ", terminator: "")
}
// 打印 "G u t e n T a g ! "
56.插入和移除
在特定位置向字符串插入一个字符, 使用 insert(_:at:) 方法, 插入其他字符串内容到制定索引使用 insert(contentsOf:at:) 方法。
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 等于 "hello!"
welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there!"
从指定索引移除字符, 使用 remove(at:) 方法, 在指定范围移除一个字串, 用 removeSubrange(_:) 方法:
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..
备注
你可以使用 insert(:at:), insert(contentsOf:at:), remove(at:), 和 removeSubrange(:) 这些方法,只要符合 RangeReplaceableCollection 协议的任何类型。 这包括 String, 还有集合类型 Array, Dictionary, 和 Set.
57.比较字符串
Swift 提供三种方式来比较文本值: 字符串和字符等式, 前缀等式, 和后缀等式。
字符串和字符等式
字符串和字符等式用“equal to” 运算符 (==) 和 “not equal to” 运算符 (!=) 来判断, 在比较运算符中描述:
let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
print("These two strings are considered equal")
}
// 打印 "These two strings are considered equal"
两个字符串的值 (或者两个字符的值) 如果他们的扩展字形集相同,就被认为是相等的。 扩展字形集如果有相同的语义和表现形式,就是相等的, 即使他们背后是由不同 Unicode 标量组成。
比如, LATIN SMALL LETTER E WITH ACUTE (U+00E9) 常规等于 LATIN SMALL LETTER E (U+0065) 加上 COMBINING ACUTE ACCENT (U+0301). 两种扩展字形集都是有效的方式来表示字符 é, 隐藏它们被认为相等:
// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
// "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
if eAcuteQuestion == combinedEAcuteQuestion {
print("These two strings are considered equal")
}
// 打印 "These two strings are considered equal"
相反, LATIN CAPITAL LETTER A (U+0041, 或者 “A”), 英语中使用, 不等于俄语中使用的 CYRILLIC CAPITAL LETTER A (U+0410, 或者 “А”)。 这两个字符看上去相似, 但是语义不同:
let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"
if latinCapitalLetterA != cyrillicCapitalLetterA {
print("These two characters are not equivalent.")
}
// 打印 "These two characters are not equivalent."
字符串和字符比较在Swift中不是地区敏感的。
58.前缀和后缀等式
判断字符串是否有一个特定前缀或者后缀, 调用字符串的 hasPrefix(:) 和 hasSuffix(:) 方法, 两个方法都一个 String 类型,然后返回一个布尔值。
下面这个例子有一个字符串数组,用来表示场景的位置,它们来自莎士比亚的罗密欧与朱丽叶的前两个表演:
let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]
你可以用 hasPrefix(_:) 方法用 romeoAndJuliet 数组来技术表演1中的场景数:
var act1SceneCount = 0
for scene in romeoAndJuliet {
if scene.hasPrefix("Act 1 ") {
act1SceneCount += 1
}
}
print("There are \(act1SceneCount) scenes in Act 1")
// 打印 "There are 5 scenes in Act 1"
相似的, 用 hasSuffix(_:) 方法来计算发生在 Capulet’s mansion 和 Friar Lawrence’s cell 的场景数:
var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
if scene.hasSuffix("Capulet's mansion") {
mansionCount += 1
} else if scene.hasSuffix("Friar Lawrence's cell") {
cellCount += 1
}
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 打印 "6 mansion scenes; 2 cell scenes"
59.Unicode 字符串表示
当一个 Unicode 字符串写入文本文件或者别的存储时,字符串里的 Unicode 标量会以一些Unicode 定义的编码形式进行编码。 每种字符串编码在人们熟知的代码单元块中。 包括 UTF-8 编码 (把字符串编码成一个 8-位的代码单元), UTF-16 编码 (把字符串编码成一个16-位的代码单元), 和 UTF-32 编码 (把字符串编码成 32-位代码单元)
Swift 提供了几种不同的方式去访问字符串的Unicode 形式。你可以用for-in语句遍历字符串, 访问作为Unicode 扩展字形集的单个字符值。这个过程在 使用字符 中描述。
或者, 或者用以下三种形式来访问字符串的值:
UTF-8 代码单元集合 (用字符串的utf8属性来访问)
UTF-16 代码单元集合 (用字符串的utf16属性来访问)
21-位 Unicode 标量值的集合, 等于字符串的 UTF-32 编码方式 (用字符串的 unicodeScalars 属性访问)
下面的例子展示字符串的不同表现形式,字符串由字符 D, o, g, ‼ (DOUBLE EXCLAMATION MARK, 或者 Unicode scalar U+203C), 和 🐶 字符 (DOG FACE, 或者 Unicode scalar U+1F436)组成:
let dogString = “Dog‼🐶”
60.UTF-8 形式与UTF-16 形式
UTF-8:
你可以通过遍历字符串的utf8属性来访问它的 UTF-8 表现形式。 这是属性是 String.UTF8View, 这是无符号的 8-位 (UInt8)值的集合, 每个字节一个:
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182
上面这个例子, 前三个十进制 codeUnit 值 (68, 111, 103) 代表字符 D, o, 和 g, 它们的 UTF-8 形式和它们的 ASCII 表现形式一样。 接下来三个十进制 codeUnit 值 (226, 128, 188) 是双感叹号的三个字节的 UTF-8 形式。 最后四个 codeUnit 值 (240, 159, 144, 182) 是个四个字节的 UTF-8 形式,代表小狗的脸字符。
UTF-16:
你可以通过遍历字符串的utf16属性来访问它的 UTF-16 形式。 这个属性是 String.UTF16View, 它是无符号16-位 (UInt16)的值的集合, 每个16-位代码单元一个
for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 打印 "68 111 103 8252 55357 56374 "
同样, 前三个 codeUnit 值 (68, 111, 103) 代表字符 D, o, 和 g, 它们的 UTF-16 代码单元和它们的 UTF-8 形式一样(因为这些 Unicode 标量表示 ASCII 字符).
第四个 codeUnit 值 (8252) 是十六进制 203C的十进制数值, 用 Unicode 标量 U+203C表示双感叹号字符。 在 UTF-16 里表示为一个单独的编码单元。
第五六个 codeUnit 值 (55357 和 56374) 狗脸字符。 这些值是高八位 U+D83D (十进制值是 55357) 和一个第八位 U+DC36 (十进制值 56374)。
61.Unicode 标量形式
你可以通过遍历字符串的 unicodeScalars 属性来访问它的Unicode 标量形式。 这个是属性是 UnicodeScalarView,它是类型 UnicodeScalar 值的集合。
每个 UnicodeScalar 有一个值属性,它返回这个标量的 21-位值, 用一个 UInt32 值表示:
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
}
print("")
// 打印 "68 111 103 8252 128054 "
前三个 UnicodeScalar 值 (68, 111, 103) 再次代表 D, o, 和 g.
第四个 codeUnit 值 (8252) 是十六进制 203C 的十进制值, 是双感叹的 Unicode 标量 U+203C 。
第五个和最后一个 UnicodeScalar 值, 128054, 是十六进制 1F436的十进制值, 是狗脸字符的的 Unicode 标量 U+1F436。
作为查询它们值属性的替代, 每个 UnicodeScalar 值也可以用来构建新的字符串, 例如用做字符串插值:
for scalar in dogString.unicodeScalars {
print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶
62.集合类型
Swift 提供了三个主要的集合类型, 数组, 集合, 和字典, 用来存储值的集合。 数组是有序的。 集合是唯一值的无序集。 字典是健值匹配的无序集合。
数组, 集合, 和字典在 Swift 里总是清楚的知道存储的值的类型和能够存储的健。 这就意味着你不能往集合里插入错误类型的值。也意味着 你知道可以从集合里或者的值的类型。
备注
Swift 的数组, 集合, 和字典类型是用泛型集合实现的。更多泛型和集合, 参见泛型。
63.集合的不稳定性
如果你创建了一个数组, 一个集合, 或者一个字典, 然后赋给一个变量。创建的这个集合就是可变的。这就意味着你可以改变这个集合,方式是通过添加, 移除, 或者改变集合里的项。如果把它们赋给一个常量, 这个集合就是不可改变的, 它的大小和内容都不能改变。
备注
如果集合不需要变化,在所有情况下创建不可变的集合是个好的实践。这样做的好处是让你容易理解自己的代码,同时让编译器可以优化你创建的集合。
64.数组及数组类型缩写语法
数组
有序存储同类型的值。 相同的值可以多次出现在数组的不同位置。
Swift的数组类型跟 Foundation的 NSArray 类相桥接。
数组类型缩写语法
T数组类型的全写法是 Array\, 这里Element是数组运行存储的值类型。 你可以缩写数组类型为[Element]. 尽管这两种形式功能一样, 缩写是优先考虑的而且这个教程后面一直这么写。
创建空数组
你可以用初始化语法创建一个特定类的空数组:
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// 打印 "someInts is of type [Int] with 0 items."
注意 someInts 变量的类型经过初始化类型被推断为 [Int]
或者, 如果上下文已经提供类型信息, 例如一个函数参数或者一个确定类型的变量或者常量, 你可以用空的字面量来创建空数组, 这个数组通常写作 [] (一个空的中括号对):
someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]
用默认值创建数组
Swift的数组类型也提供了一构造器来创建相同默认值固定大小的数组。 给构造器传一个匹配类型的默认值 (调用 repeating): 然后是在新数组中需要重复的值的个数 (调用 count):
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles 是 [Double]类型, 等于 [0.0, 0.0, 0.0]
通过合并数组创建数组
你可以通过合并两个已存在的数组来创建新的数组,这两个数组只要类型匹配就可以通过加法运算符 (+)来合并。 新数组的类型从合并的数组可以推断出来:
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
用字面量创建数组
你也可以用字面量来初始化数组, 这是种快速写一个或者多个数组值的方式。 一个数组字面量写作一列值, 用逗号分开, 一对方括号包括起来:
[value 1, value 2, value 3]
下面的的例子创建了一个购物列表来存储字符串值:
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList 初始有两项
购物列表变量定义为 “字符串值的数组”, 写作 [String]. 因为这个数组已经指定一个字符串类型值, 它就值运行存储字符串值。 这里, 购物列表数组用两个字符串值 (“Eggs” 和 “Milk”)来初始化, 写在数字字面量里。
备注
这个数组被声明为一个变量而不是一个常量,是因为在下面的例子将有更多的项目加入这个购物列表。
这种情况, 数组字面量只包含两个字符串值。 这个匹配购物列表变量的声明类型 (只能包括字符串值的数组), 用数组字面量赋值来初始化购物列表是被允许的一种方式。
由于 Swift 的类型推断, 如果你用相同类型的字面量来初始化数组,就不需要写出数组的类型。购物列表的初始化可以简写成以下形式:
var shoppingList = ["Eggs", "Milk"]
因为数组里所有的值都是同样的类型, Swift 能够推断出 [String] 用作购物列表变量是正确的类型。
访问和修改数组
你可以通过数组的方法,属性或者下标语法来访问和修改一个数组。
为了确定数组的项数, 使用只读属性count:
print("The shopping list contains \(shoppingList.count) items.")
// 打印 "The shopping list contains 2 items."
使用布尔属性 isEmpty 来判断数量属性是否0:
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list is not empty.")
}
// 打印 "The shopping list is not empty."
通过调用 append(_:) 方法你可以在数组最后添加新项:
shoppingList.append("Flour")
// shoppingList 现在有3项
或者, 使用赋值运算 (+=) 添加一个或多个符合的项:
shoppingList += ["Baking Powder"]
// shoppingList 现在有4项
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList 现在有7项
用下标语法从数组中获取一个值, 在数组后的方括号里传入你想得到值的索引:
var firstItem = shoppingList[0]
// firstItem is equal to “Eggs”
备注
数组第一项的索引是0,不是1。 Swift 数组总是基于0开始索引。
你可以使用下标语法去改变给定索引下的值:
shoppingList[0] = "Six eggs"
// 第一项现在是 "Six eggs" 而不是 "Eggs"
你还可以通过下标语法一次改变一个范围的值, 尽管取代值跟被取代的长度不同。 下面的例子用 “Bananas” 和 “Apples” 取代了 “Chocolate Spread”, “Cheese”, 和 “Butter”:
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList 现在包含 6项
备注
你不能用下标语法添加新的项到数组最后。
在特定位置插入新项, 调用数组插入方法 insert(_:at:):
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list
调用 insert(_:at:) 方法在购物列表头插入一个新的项 “Maple Syrup”, 用索引0标明。
类似的, 移除一项使用 remove(at:) 方法。 这个方法移除指定索引的项然后返回这个移除项 (尽管你可能无视这个返回值):
let mapleSyrup = shoppingList.remove(at: 0)
// 位于索引0的项被移除
// 购物列表现在包含6项, 没有 Maple Syrup
// mapleSyrup 常量现在等于被移除的 "Maple Syrup" 字符串
备注
如果越界访问数组的值, 你会触发运行时错误。你可以用它跟数组个数属性对比来判断索引的有效性。除了个数为0的情况 (意味着数组是空的), 最大索引值通常是数组个数 - 1, 因为数组是从0开始计数的。
一个数据项被移除数组空隙就会闭合,所以索引 0 的值再次等于 “Six eggs”:
firstItem = shoppingList[0]
// firstItem 现在等于 "Six eggs"
如果你想移除最后一项, 使用 removeLast() 方法而不是 remove(at:) 方法,这样就避免需要查询数组的个数属性。和 remove(at:) 方法一样, removeLast() 返回移除的项:
let apples = shoppingList.removeLast()
// 最后一项被移除
// 购物列表现在包含5项, 没有苹果
// apples 常量现在等于被移除的 "Apples" 字符串
65.遍历数组
你可以用for-in循环来遍历数组中的所有值集:
for item in shoppingList {
print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
如果需要数据项的值和整数索引, 使用 enumerated() 方法来遍历数组。对于数组中的每一项, enumerated() 方法返回一个包含索引和值的元组。 整数以0开始每项递增; 如果你遍历整个数组, 这些整数匹配数据项的索引。你可以分解元组到临时的常量或者变量,来作为迭代的部分:
for (index, value) in shoppingList.enumerated() {
print("Item \(index + 1): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas
66.集合
集合无序存储同样类型的不同值。 如果排序不重要可以选择使用集合代替数组, 或者你需要确保每项只出现一次。
Swift 的 Set 类型跟 Foundation 的 NSSet 类桥接。
集合类型的哈希值
集合里一个类型为了存储就必须可哈希—就是说, 这个类型必须提供一个方式去计算自己的哈希值。 一个哈希值是一个整型值, 这对于相等的对象都是一样的, 如果 if a == b, 那么 a.hashValue == b.hashValue.
所有 Swift 的基本类型默认都是可哈希的 (比如 String, Int, Double, 和 Bool), 可以被用作集合值类型或者字典建类型。 枚举分支值没有相应的值默认也是可哈希的。
你可以使用自定义类型作为集合值类型或者字典健类型,通过让它们遵守Swift 标准库中的 Hashable 协议。 符合协议的类型必须提供一个可获取的整形属性 hashValue. 在相同程序中的不同执行,或者在不同程序中, 类型的哈希属性返回的值不要求相同。
因为 Hashable 协议符合 Equatable, 符合类型必须提等号运算符 (==)的实现。 Equatable 协议要求任何符合==实现的都是一个相等关系。 就是说, == 的实现必须满足下面的条件, 对于 a, b, 和 c 所有值:
a == a (自反性)
a == b 表示 b == a (对称性)
a == b && b == c 表示 a == c (传递性)
集合类型语法
集合类型写作 Set, 这里 Element 是集合允许存储的类型。 跟数组不同, 集合没有等价的缩写形式。
创建和初始化空集合
你可以通过使用构造器语法创建特定类型的空集合:
var letters = Set()
print("letters is of type Set with \(letters.count) items.")
// 打印 "letters is of type Set with 0 items."
备注
letters 变量的类型推断为 Set, 通过构造器的类型。
或者, 如果上下文已经提供了类型信息, 比如一个函数参数或者一个已经指定类型的变量和常量, 你可以用一个空的数组字面量来创建一个空集合:
letters.insert("a")
// letters 现在包含了一个字符类型的值
letters = []
// letters 现在是一个空集合, 但是依然是 Set 类型
用数组字面量创建集合
你可以用数组字面量初始化一个集合, 这是一个速写方式来给一个集合赋值。
下面的例子创建一个存储字符串值的集合 favoriteGenres:
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
// favoriteGenres 有了三个初始值
favoriteGenres 变量声明成 “字符串值的集合”, 写作 Set. 因为这个集合指定了一个字符串的值类型, 它只允许存储字符串值, favoriteGenres 集合用三个字符串 (“Rock”, “Classical”, 和 “Hip hop”)来初始化, 写在一个数组字面量里。
备注
favoriteGenres 集合声明成一个变量而不是一个常量, 这是因为添加和移除的项在下面的示例里。
集合通过数组字面量不能推断出类型, 所以类型必须显示声明。 不过, 由于Swift的类型推断, 如果数组字面量包含的值类型相同,你就不需要写集合的类型。favoriteGenres 初始化可以简写如下:
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
因为数组字面量里的值都是同样类型, Swift 可以推断 Set 用作favoriteGenres 变量是正确的类型。
访问和修改集合
你可以通过集合的方法和属性来访问和修改它。
为了得到集合的项数, 使用只读的 count 属性:
print("I have \(favoriteGenres.count) favorite music genres.")
// 打印 "I have 3 favorite music genres."
使用布尔属性 isEmpty 快速判断 count 属性是否等于 0:
if favoriteGenres.isEmpty {
print("As far as music goes, I'm not picky.")
} else {
print("I have particular music preferences.")
}
// 打印 "I have particular music preferences."
调用集合的 insert(_:) 方法插入一个新项:
favoriteGenres.insert("Jazz")
// favoriteGenres 现在包含 4 项
你可以用集合的 remove(_:) 方法从集合里移除项, 如果它是集合一员的话, 然后返回被移除的项, 如果集合不包含这项就返回nil。 或者, 移除所有项可以用 removeAll() 方法。
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
} else {
print("I never much cared for that.")
}
// 打印 "Rock? I'm over it."
检查集合是否包含特定项, 用 contains(_:) 方法。
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
// 打印 "It's too funky in here."
遍历集合
你可以用for-in循环遍历集合的值。
for genre in favoriteGenres {
print("\(genre)")
}
// Jazz
// Hip hop
// Classical
Swift 的集合类型是无序的。以特定顺序去遍历集合的值, 使用 sorted() 方法, 这个方法返回一个值数组,按照小于运算符排序。
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// Classical
// Hip hop
// Jazz
集合操作
你可以有效进行基础的集合操作, 比如合并两个集合, 判断两个集合是否有共同值, 或者判读两个集合是否包含所有几个或者没有任何相同值。
基本集合操作
下面的图描述了两个集合a 和 b, 共享区域表示集合操作结果。
用 intersection(:) 方法创建交集。
用 symmetricDifference(:) 方法创建交集之外的集合。
用 union(:) 方法创建并集。
用 subtracting(:) 方法创建去掉指定集合的集合。
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
# 集合和等式
下面的图描述了三个集合 a, b 和 c, 重叠区域表示集合共享的元素。 集合a是集合b的超集, 因为a包含b的所有元素。 相反, 集合b是集合a的子集, 因为b中的元素都包含在集合a中。 集合b与集合c不相交, 因为它们没有共同的元素。
用相等运算符 (==) 判断两个集合是否所有值都相同。
用 isSubset(of:) 方法判断一个集合是否是另外一个集合的字集。
用 isSuperset(of:) 方法判断一个集合是否是另外一个集合的超集。
用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法判断一个集合是否是一个子集或者超集, 但是不等于一个指定的集合。
用 isDisjoint(with:) 方法判断是否两个集合不相交。
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true
68.字典
字典无序存储匹配的同类型的健值。每个值对应一个唯一的健, 它作为值在字典里的标识符。 跟数组项不同, 字典里的项是无序的。如果基于标识符查找值你可以用字典, 很像真实世界字典的用法。
字典类型速记语法
Swift 字典类型完整写法是 Dictionary, Key 是可以用作字典值的值类型, Value 是字典为健存储的值类型。
你也可以字典类型的简写形式 [Key: Value]. 尽管两个形式功能一样, 简写形式优先使用并贯穿整个教程。
创建一个空字典
跟数组一样, 你可用用初始化语法创建一个特定类型的字典:
var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] dictionary
这个例子定义了一个空字典,类型是 [Int: String],用来存储人类可读的整数名字。健是整形, 值是字符串类型。
如果上下文已经提供了类型信息, 你可以用一个空的字典字面量来创建一个空字典, 写作 [:] (一对中括号中间一个冒号):
namesOfIntegers[16] = "sixteen"
// namesOfIntegers 现在包含 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers 再次成为空,类型是[Int: String]
用字面量创建字典
你还可以用字面量来初始化一个字典, 跟早前数组字面量语法相似。字典字面量是定义一个或者多个键值对集合的速写方法。
键值对是健和值的组合。 在一个字典字面量里, 健和值在键值对里用冒号分开。 键值对写作一个列表, 用逗号分开, 包含在一对中括号里:
[key 1: value 1, key 2: value 2, key 3: value 3]
下面的例子创建一个字典来存储国际机场名字。在这个字典里, 健是三个字母的国际航空公司编码, 值是机场名:
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports 字典类型是 [String: String], 意思是 “一个字典健值都是字符串类型”.
备注:airports 字典声明成一个变量而非常量, 因为下面的例子需要添加更多机场进去。
airports 用两个键值对字面量初始化。第一个键值对有一个健 “YYZ” 和一个值 “Toronto Pearson”. 第二个键值对有一个健 “DUB” 和一个值“Dublin”.
字面量包含两个 String: String 对。 简直类型匹配机场类型的变量声明 (一个字典只有字符串健和字符串值), 所以字面量赋值是允许的,可以用来初始化机场字典。
跟数组一样, 如果你用相同类型的键值字典初始化,你就不需要写出字典的声明类型。初始化方法可以简写如下:
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
因为字面量的所有健类型都是一样的, 同样所有的值也是, Swift 可以推断出来 [String: String] 是正确的类型声明。
访问和修改字典
你可以通过字典的方法和属性来访问和修改它, 或者使用下标语法。
跟数组一样, 使用count 属性获取字典的项数:
print("The airports dictionary contains \(airports.count) items.")
// 打印 "The airports dictionary contains 2 items."
用 isEmpty 属性判断count 属性是否等于 0:
if airports.isEmpty {
print("The airports dictionary is empty.")
} else {
print("The airports dictionary is not empty.")
}
// 打印 "The airports dictionary is not empty."
你可以用下标语法往字典添加新项。 使用对应的类型的键作为下标索引, 并赋一个对应的新值:
airports["LHR"] = "London"
// airports 现在包含 3 项
你可以用下标语法改变指定键对应的值:
airports["LHR"] = "London Heathrow"
// "LHR" 的值已经变成"London Heathrow"
作为下标的替代方案, 用字典的 updateValue(:forKey:) 方法设置或者更新指定键的值。 跟上面下标实例一样, updateValue(:forKey:) 方法在键不存在时设置值, 如果键存在则进行更新。和下标不像的是, updateValue(_:forKey:) 方法在执行更新后返回老的值。 这个可以让你知道更新是否生效。
updateValue(_:forKey:) 方法返回可选类型的值。对个存储字符串值的字典来说, 比如, 这个方法返回 String?, 或者 “可选的 String”. 可选值包括未更新前存在的键对应的老值, 如果值不存在则包括nil:
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// 打印 "The old value for DUB was Dublin."
你可以用下标语法获取特定键对应的值。因为有可能去请求不存在值的键, 字典的下标就会返回一个可选值。 如果字典包含请求键对应的值, 下标就返回一个可选值,这个值包含这个键对应的存在的值。否则返回 nil:
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
// 打印 "The name of the airport is Dublin Airport."
你可以通过把键对应的值赋空的方式,用下标语法从字典移除一个键值对:
airports["APL"] = "Apple International"
// "Apple International" is not the real airport for APL, so delete it
airports["APL"] = nil
// APL 现在已经从字典移除
或者, 用 removeValue(forKey:) 这个方法来移除。 这个方法移除的键值对如果存在返回被移除的值,否则返回nil:
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary does not contain a value for DUB.")
}
// 打印 "The removed airport's name is Dublin Airport."
遍历字典
你用for-in 循环来遍历字典。 字典中的每一项都以 (key, value) 元组形式返回, 你可以分解元组到临时的变量或者常量:
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// YYZ: Toronto Pearson
// LHR: London Heathrow
你可以通过访问字典的键值属性来获取键值的集合:
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
// Airport code: YYZ
// Airport code: LHR
for airportName in airports.values {
print("Airport name: \(airportName)")
}
// Airport name: Toronto Pearson
// Airport name: London Heathrow
如果你想使用字典的键值的数组实例, 用键值来初始化一个新的数组:
let airportCodes = [String](airports.keys)
// airportCodes is ["YYZ", "LHR"]
let airportNames = [String](airports.values)
// airportNames 是 ["Toronto Pearson", "London Heathrow"]
Swift的字典类型无序的。 为了用特定顺序去遍历字典的键值, 用这个 sorted() 方法。
79.控制流
Swift 提供了一系列控制流语句。 包括 while 循环来执行多次任务; if, guard, switch 语句来在不同条件执行不同代码; 还有 break 和 continue 来转移控制流。
Swift 同时提供了 for-in 循环使得遍历数组, 字典, 范围, 字符串, 和其他序列变得容易。
Swift 的 switch 语句比多数类 C 语言相同语句更加高效有力。 因为 Swift 的 switch 语句的分支不会落到下一分支, 这就避免了 C常见的错误。 分支可以匹配不同的模式, 包括区间匹配, 元组, 转换成指定类型。 switch 分支匹配的值可以绑定到临时的变量或者常量。每个分支可以用 where 族来表达符合匹配条件。
80.For-In 循环
你可以使用 for-in 循环遍历一个序列, 例如数组里的项, 数值的范围, 或者字符串中的字符。
下面的例子使用一个 for-in 循环遍历数组里的项目:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
你可以遍历字典的键值对。当字典被遍历时, 字典中的每一项以 (key, value) 元组返回, 你可以把键值对分解成显式的名字, 在循环中使用。在下面的代码里, 字典的键解析到一个常量 animalName, 字典的值分解到另外一个常量 legCount.
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// ants have 6 legs
// spiders have 8 legs
// cats have 4 legs
字典的内容本身是无序的, 遍历它们不能保证它们获取的顺序。特别情况下, 你插入项目的顺序不能定义它们遍历的顺序。
你也可以在数值范围中使用 for-in 循环。下面的例子打印 5倍乘法表的前几项:
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
这个序列遍历的数值范围是从 1 到 5, 包括它们, 使用闭合区间运算符表示 (…). 索引的值设置成数值范文的第一个 (1), 循环中的语句开始执行。在这个例子里, 循环体只有一条语句, 它打印 5 倍乘法表从当前索引值开始的一组值。在语句执行后, 索引值更新未数值范围的第二个 (2), 然后 print(_:separator:terminator:) 函数再次调用。这个过程直到遍历结束。
上面的例子里, 索引是一个常量, 它的值自动设置成循环遍历的开始。索引在使用前不必要声明。它会在循环声明中隐式的声明, 也不需要 let 关键字。
如果你不需要序列的每一项的值, 你可以在变量名的位置使用下划线来忽略这个值。
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 打印 "3 to the power of 10 is 59049"
上面的例子计算一个值的幂 (在这个例子里, 3的10次幂). 开始是3乘以1, 然后加倍, 使用一个从1 到 10 的闭合区间。针对这个计算, 每次循环并不需要单独的计算器值—代码只是简单执行当前数字倍数的循环。下滑用来忽略这个值, 因为遍历过程并不需要它。
在其他情景中, 你可能不想使用闭合区间, 它会包含两个端点值。想像一下绘制每一分钟的刻度。你想要绘制 60 个刻度。可以使用半开区间运算符 (..<) 包括较低的边界而不包括较高的边界。
let minutes = 60
for tickMark in 0..<minutes {
// 每分钟绘制一次刻度 (60 次)
}
有些用户想要更少的刻度。他们可能每五分钟选择一个刻度。使用 stride(from:to:by:) 函数跳过不想要的刻度。
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 每五分钟绘制一次刻度 (0, 5, 10, 15 ... 45, 50, 55)
}
80.While 循环
while 循环执行一组语句,直到条件失败。 首次遍历前,遍历次数不知道的时候使用这种方式是最好的。Swift 提供两种 while 循环:
while 每次循环前评估条件。
repeat-while 每次循环后评估条件。
while 循环从评估条件开始。 如果条件是真的, 一组语句会重复执行直到条件变成假的。
这是 while 循环的基本形式:
while condition {
statements
}
这个例子是个蛇与梯子的游戏:
游戏规则如下:
棋盘有25个方格, 目标是占据或者超过25。
每次, 摇一个6边的骰子然后移动对应数目方格, 沿着上图的点箭头的水平路径移动。
如果位于梯子底部, 往梯子上方移动。
如果位于蛇头, 往蛇下方移动。
游戏棋盘用整形值数组表示。大小基于 finalSquare 常量, 用来初始化数组并且判断取胜的条件。 棋盘用26个0值初始化, 而不是25 (每个索引从0到25)。
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
一些方格会为蛇和梯子设置更多的指定值。梯子方格是正数,蛇方格是负数。
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
方格 3 包含了梯子的底部,可以爬到方格 11。 为了表示这个, board[03] 等于 +08, 这等于整数值 8 (3和11的差值) 加号 (+i) 和减号 (-i) 相等, 数值低于 10 的用 0 填充, 这样所以棋盘都被定义了。
玩家开始方格是 “方格0”, 靠着棋盘的左下角。 第一次摇骰子总是让玩家进入棋盘。
var square = 0
var diceRoll = 0
while square < finalSquare {
// roll the dice
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// move by the rolled amount
square += diceRoll
if square < board.count {
// if we're still on the board, move up or down for a snake or a ladder
square += board[square]
}
}
print("Game over!")
上面的例子用了一个很简单方法去摇骰子。 不用随机数, 用一初始值为0的 diceRoll。 每次循环, diceRoll 加一然后判断它是否过大。 当返回值等于 7, 骰子值过大然后重置为 1。diceRoll 值总是 1, 2, 3, 4, 5, 6, 1, 2 然后不停循环。
摇完筛子, 往前移动 diceRoll 方格。 如果移动超过25, 游戏结束。考虑边界安全,在操作方格的时候。
备注 没有这个判断, board[square] 可能会越界访问棋盘数组的值, 这会触发一个错误。 如果方格等于 26, 代码会判断board[26] 的值, 这会超过数组的大小。
当前 while 循环执行然后结束, 然后循环的条件会被判断是否再次执行。如果玩家已经移动或者超过25, 循环的条件变成假然后游戏就结束。
while 循环适合这个例子, 因为开始循环的时候游戏的长度并不清楚。 相反, 一个特定条件满足后循环被执行了。
Repeat-While
另外一种 while 循环的变种, 就是 repeat-while 循环, 先执行一次循环, 然后再考虑循环的条件。 然后继续执行直到条件变成假。
备注 Swift 里的 repeat-while 循环类似其他语言里的 do-while 循环。
这里是 repeat-while 循环的基本样式:
repeat {
语句
} while 条件
这里还是蛇与梯子的例子, 用 repeat-while 循环来写而不是 while 循环。finalSquare, board, square, 和 diceRoll 的值初始化方式跟 while 循环一样。
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
这个游戏版本, 第一步是判断梯子和蛇。 没有梯子带着玩家直接去方格 25, 因为不太可能通过爬梯子去赢得游戏。因此, 判断有没有蛇和梯子是很安全的。
游戏开始, 玩家在 “square zero”. board[0] 总是等于0没有影响。
repeat {
// move up or down for a snake or ladder
square += board[square]
// roll the dice
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// move by the rolled amount
square += diceRoll
} while square < finalSquare
print("Game over!")
蛇与梯子判断后, 摇动筛子玩家往前移动 diceRoll 方格。 当前循环执行然后结束。
循环条件 (while square < finalSquare) 和以前一样, 不过这次开始不评估,直到第一次循环结束才评估。repeat-while 循环比 while loop 循环更适合这个游戏。上面的 repeat-while 循环, 当条件确认方格还在棋盘上后, square += board[square] 总是立即执行。 这就不需要像之前那样判断数组边界。
条件语句
基于特定条件执行不同代码片段经常用到。 在碰到错误时你可能想运行额外的代码片段, 或者在数值变得太高或者太低的时候也是。为了做这件事, 你让部分代码带有条件性。
Swift 提供两种方式给你的代码添加条件分支: if 语句和 switch 语句。你用 if 语句评估条件会有一些可能的结果。 switch 语句在比较复杂的多个排列时很有用, 在模式匹配可以帮助选择合适代码分支执行的情况也是。
If
最简单的形式, if 语句有一个单一条件。 条件为真执行一个语句集合。
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32="" {="" print("it's="" very="" cold.="" consider="" wearing="" a="" scarf.")="" }="" 打印="" "it's="" scarf."="" <="" code="">
上面的例子判断文档是否小于等于华氏32度 (冰点)。 如果是, 打印一条信息。 否则, 不打印消息, 代码在大括号后继续执行。
if 语句还可以提供一个替代语句集合, 就是 else 字句, 针对条件是假的情况。 这些语句用else 关键字表示。
temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32="" {="" print("it's="" very="" cold.="" consider="" wearing="" a="" scarf.")="" }="" else="" not="" that="" wear="" t-shirt.")="" 打印="" "it's="" t-shirt."="" <="" code="">
两个分支之一总会执行。 因为温度已经增长到华氏40度, 这不够冷到戴围巾,因此else分支被触发了。
你可以链接多个if语句来考虑额外的语句。
temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32="" {="" print("it's="" very="" cold.="" consider="" wearing="" a="" scarf.")="" }="" else="" if="" temperatureinfahrenheit="">= 86 {
print("It's really warm. Don't forget to wear sunscreen.")
} else {
print("It's not that cold. Wear a t-shirt.")
}
// 打印 "It's really warm. Don't forget to wear sunscreen."
在这个库, 额外的if语句用来响应特别温暖的温度。 最后的else语句保留, 它响应既不太热也不太冷的情况。
最后的else语句是可选的, 不过, 如果不需要执行可以去掉。
temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32="" {="" print("it's="" very="" cold.="" consider="" wearing="" a="" scarf.")="" }="" else="" if="" temperatureinfahrenheit="">= 86 {
print("It's really warm. Don't forget to wear sunscreen.")
}
因为这个温度既不太冷也不太热,所以不会触发if或者else语句,没有打印信息。
81.Switch
switch 语句设想一个值,然后跟一些可能的匹配模式比较。然后执行合适的代码, 基于第一次匹配成功。switch 语句提供了if语句的替代方法来响应多种潜在状态的情况。
最简单的形式, 一个 switch 语句用一个值跟一个或者多个同类型的值相比较。
switch some value to consider {
case value 1:
respond to value 1
case value 2,
value 3:
respond to value 2 or 3
default:
otherwise, do something else
}
每个 switch 语句由多个分支组成, 每个分支以case 关键字开始。 除了跟指定值比较外, Swift 为分支提供了指定更复杂匹配模式的方法。这些选项本章后面会描述。
跟if语句体很像, 每个分支是代码执行的单独分支。 switch 语句决定是否选择这个分支。这个过程叫分发。
每个switch 语句务必是详尽的。 就是说, 每个能想到的类型值都要分支匹配。 为每个可能值都提供分支是不合适的。你可以定义一个默认分支覆盖没有显示处理的值。 默认分支使用default关键字指明, 必须总是出现在最后。
这个例子使用一个 switch 语句来出处理一个小写字母someCharacter:
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The first letter of the alphabet")
case "z":
print("The last letter of the alphabet")
default:
print("Some other character")
}
// 打印 "The last letter of the alphabet"
switch 语句第一个分支匹配英文字母表第一个字母 a, 第二个分支匹配最后一个字母 z. 因为 switch 语句对于每个可能的字符都要有分支, 不是每个字母表字符都需要。这个switch 语句使用了一个default 分支 去处理a 和 z之外的其他所有字母。这保证了switch语句的详细性。
82.非隐式 Fallthrough
和C 与 Objective-C 的switch语句对比, Swift 中的switch语句不会 fall through 分支底部并且跳到下一个分支。 相反, 在第一次匹配到分支后整个switch语句执行就完成了。不要求显示的break语句。 这使得switch语句更安全,更容易使用,而且避免执行多个分支的错误。
备注
尽管Swift 不要求break, 你仍然可以在Swift中使用。
分支体必须包含至少一句可执行语句。下面的代码的无效的, 因为第一个分支是空的:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // Invalid, the case has an empty body
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// 这会报一个编译期错误
跟C中的switch语句不同, 这个 switch 语句不能同时匹配 “a” 和 “A”. 相反, 它会报一个编译期错误,因为case “a”: 没有任何可以执行的语句。这个方法避免了突然从一个分支跳入另外一个分支, 让代码意图清晰安全。
如果要用一个分支同时匹配“a” 和 “A”, 把这两个值合并到一个分支语句, 用逗号把它们分开。
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// 打印 "The letter A"
为了可读性, 复合分支也可以写作多行。
备注:为了显式fall through 到某个分支下面, 使用 fallthrough 关键字。
83.区间匹配
switch 分支里面的值可以包含在区间里判断。 这个例子使用数字区间来提供一个对任何数字大小的自然语言统计。
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5: naturalcount="a few" case="" 5..<12:="" 12..<100:="" 100..<1000:="" default:="" }="" print("there="" are="" \(naturalcount)="" \(countedthings).")="" 打印="" "there="" dozens="" of="" moons="" orbiting="" saturn."="" <="" code="">
上面的例子, approximateCount 在switch语句里估算。 每个分支把这个值跟一个数字或者区间进行比较。因为 approximateCount 值落在 12 和 100之间, naturalCount 被赋值为 “dozens of”, 然后代码执行跳出switch语句继续执行。
84.元组
你可以在switch语句中使用元组测试多值。元组元素可以跟一个不同值或者值区间进行测试。或者, 使用通配下划线 (_) 去匹配可能的值。
下面的例子有一个 (x, y) 点, 用一个简单的元组类型 (Int, Int) 表示, 并且在下面的图表中标示。
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
// 打印 "(1, 1) is inside the box"
switch 语句决定这个点是否位于原点 (0, 0), 红色的 x-轴, 橙色 y-轴, 在蓝色 4-by-4 方块里原点在中心, 或者在方块之外。
跟 C 不同, Swift 允许多个分支去处理相同的值。 事实上, (0, 0)点可以满足例子的四个分支。不过, 如果多个匹配都可能, 第一个匹配上的总是执行。 (0, 0) 点首先匹配case (0, 0), 其他所有匹配的分支都会被忽略。
84.值绑定
一个 switch 分支 可以绑定值到临时的常量或者变量, 用在分支体中。这个就是值绑定, 因为值绑定到分支体中的临时常量或者变量上。
下面这个例子有一个(x, y) 点, 是元组类型 (Int, Int), 在下面的图表中标示:
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("on the x-axis with an x value of \(x)")
case (0, let y):
print("on the y-axis with a y value of \(y)")
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
// 打印 "on the x-axis with an x value of 2"
switch 语句判断这个点是在红色的 x-轴, 橙色的 y-轴, 还是其他什么地方 (不在轴上)。
这三个 switch case声明了占位符常量 x 和 y, 临时存放来自 anotherPoint 的一个或者两个元组值。第一个分支 case (let x, 0), 匹配任何y值为0 然后把坐标点x的值赋给临时常量 x. 类似的, 第二个分支, case (0, let y), 匹配任何x值为0,然后把坐标点y值赋给临时常量y.
临时常量声明后, 它们就可以用在分支体中。在这里, 它们用来打印这个点的标示。
这个 switch 语句没有默认的default 分支. 最后一个分支, case let (x, y), 声明了带有两个占位符常量的元组,它可以匹配任何值。因为 anotherPoint 总是两个值的元组, 这个case 匹配所有可能的剩余值, 所以默认的default case 是不需要的。
85.Where
一个 switch 分支 可以用 where 字句来判断额外的条件。
下面的例子在下面的图表中标示一个(x, y) 点:
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// 打印 "(1, -1) is on the line x == -y"
switch 语句确定这个点是否在x == y这个绿色对角线上, 或者是x == -y这个紫色对角线上,或者两者都不在。
三个 switch 分支 都声明了占位符常量 x 和 y, 临时接受来自 yetAnotherPoint 两个元组值。这些常量用作where字句的一部分, 去创建一个动态的过滤器。分支匹配坐标点的当前值,如果where字句条件为真的话。
和前面一个例子一样, 最后的分支匹配所有剩余值, 不再需要 default 分支