From 4eeaeb06d62b9eb507f969d848ebf61b3b91db60 Mon Sep 17 00:00:00 2001 From: 949478479 <949478479@qq.com> Date: Mon, 16 Nov 2015 15:15:19 +0800 Subject: [PATCH 1/2] =?UTF-8?q?1.=E4=BF=AE=E6=AD=A3=E2=80=9C=E9=9B=86?= =?UTF-8?q?=E5=90=88=E7=9A=84=E5=8F=AF=E5=8F=98=E6=80=A7=E2=80=9D=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E9=94=99=E8=AF=AF=E6=8F=8F=E8=BF=B0=EF=BC=9B2.?= =?UTF-8?q?=E4=B8=A4=E5=A4=84=20Fundation=20=E6=94=B9=E4=B8=BA=20Foundatio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/chapter2/04_Collection_Types.md | 1348 ++++++++++++------------ 1 file changed, 674 insertions(+), 674 deletions(-) diff --git a/source/chapter2/04_Collection_Types.md b/source/chapter2/04_Collection_Types.md index b04f0670..fe4629dc 100755 --- a/source/chapter2/04_Collection_Types.md +++ b/source/chapter2/04_Collection_Types.md @@ -1,675 +1,675 @@ -# 集合类型 (Collection Types) ------------------ - -> 1.0 -> 翻译:[zqp](https://github.com/zqp) -> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) - -> 2.0 -> 翻译+校对:[JackAlan](https://github.com/AlanMelody) - +# 集合类型 (Collection Types) +----------------- + +> 1.0 +> 翻译:[zqp](https://github.com/zqp) +> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) + +> 2.0 +> 翻译+校对:[JackAlan](https://github.com/AlanMelody) + > 2.1 -> 校对:[shanks](http://codebuild.me) - -本页包含内容: - -- [集合的可变性(Mutability of Collections)](#mutability_of_collections) -- [数组(Arrays)](#arrays) -- [集合(Sets)](#sets) -- [字典(Dictionaries)](#dictionaries) - -Swift 语言提供`Arrays`、`Sets`和`Dictionaries`三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。 - -![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/CollectionTypes_intro_2x.png) - -Swift 语言中的`Arrays`、`Sets`和`Dictionaries`中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信。 - -> 注意: -Swift 的`Arrays`、`Sets`和`Dictionaries`类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.html)章节。 - - -## 集合的可变性 - -如果创建一个`Arrays`、`Sets`或`Dictionaries`并且把它分配成一个变量,这个集合将会是*可变的*。这意味着我们可以在创建之后添加更多或移除已存在的数据项来改变这个集合的大小。如果我们把`Arrays`、`Sets`或`Dictionaries`分配成常量,那么它就是*不可变的*,它的大小不能被改变。 - -> 注意: -在我们不需要改变集合大小的时候创建不可变集合是很好的习惯。如此 Swift 编译器可以优化我们创建的集合。 - - -## 数组(Arrays) - -数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 - -> 注意: - Swift 的`Array`类型被桥接到`Foundation`中的`NSArray`类。 - 更多关于在`Foundation`和`Cocoa`中使用`Array`的信息,参见 [*Using Swift with Cocoa and Obejective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 - - -### 数组的简单语法 - -写 Swift 数组应该遵循像`Array`这样的形式,其中`Element`是这个数组中唯一允许存在的数据类型。我们也可以使用像`[Element]`这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 - - -### 创建一个空数组 - -我们可以使用构造语法来创建一个由特定数据类型构成的空数组: - -```swift -var someInts = [Int]() -print("someInts is of type [Int] with \(someInts.count) items.") -// 打印 "someInts is of type [Int] with 0 items." -``` - -注意,通过构造函数的类型,`someInts`的值类型被推断为`[Int]`。 - -或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:`[]`(一对空方括号): - -```swift -someInts.append(3) -// someInts 现在包含一个 Int 值 -someInts = [] -// someInts 现在是空数组,但是仍然是 [Int] 类型的。 -``` - - -### 创建一个带有默认值的数组 - -Swift 中的`Array`类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeatedValue`)传入数组构造函数: - -```swift -var threeDoubles = [Double](count: 3, repeatedValue:0.0) -// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0] -``` - - -### 通过两个数组相加创建一个数组 - -我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来: - -```swift -var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5) -// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5] - -var sixDoubles = threeDoubles + anotherThreeDoubles -// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] -``` - - -### 用字面量构造数组 - -我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值: - -`[value 1, value 2, value 3]`。 - -下面这个例子创建了一个叫做`shoppingList`并且存储`String`的数组: - -```swift -var shoppingList: [String] = ["Eggs", "Milk"] -// shoppingList 已经被构造并且拥有两个初始项。 -``` - -`shoppingList`变量被声明为“字符串值类型的数组“,记作`[String]`。 因为这个数组被规定只有`String`一种数据结构,所以只有`String`类型可以在其中被存取。 在这里,`shoppinglist`数组由两个`String`值(`"Eggs"` 和`"Milk"`)构造,并且由字面量定义。 - -> 注意: -`Shoppinglist`数组被声明为变量(`var`关键字创建)而不是常量(`let`创建)是因为以后可能会有更多的数据项被插入其中。 - -在这个例子中,字面量仅仅包含两个`String`值。匹配了该数组的变量声明(只能包含`String`的数组),所以这个字面量的分配过程可以作为用两个初始项来构造`shoppinglist`的一种方式。 - -由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。 `shoppinglist`的构造也可以这样写: - -```swift -var shoppingList = ["Eggs", "Milk"] -``` - -因为所有字面量中的值都是相同的类型,Swift 可以推断出`[String]`是`shoppinglist`中变量的正确类型。 - - -### 访问和修改数组 - -我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。 - -可以使用数组的只读属性`count`来获取数组中的数据项数量: - -```swift -print("The shopping list contains \(shoppingList.count) items.") -// 输出 "The shopping list contains 2 items."(这个数组有2个项) -``` - -使用布尔值属性`isEmpty`作为检查`count`属性的值是否为 0 的捷径: - -```swift -if shoppingList.isEmpty { - print("The shopping list is empty.") -} else { - print("The shopping list is not empty.") -} -// 打印 "The shopping list is not empty."(shoppinglist 不是空的) -``` - -也可以使用`append(_:)`方法在数组后面添加新的数据项: - -```swift -shoppingList.append("Flour") -// shoppingList 现在有3个数据项,有人在摊煎饼 -``` - -除此之外,使用加法赋值运算符(`+=`)也可以直接在数组后面添加一个或多个拥有相同类型的数据项: - -```swift -shoppingList += ["Baking Powder"] -// shoppingList 现在有四项了 -shoppingList += ["Chocolate Spread", "Cheese", "Butter"] -// shoppingList 现在有七项了 -``` - -可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中: - -```swift -var firstItem = shoppingList[0] -// 第一项是 "Eggs" -``` - -> 注意: -第一项在数组中的索引值是`0`而不是`1`。 Swift 中的数组索引总是从零开始。 - -我们也可以用下标来改变某个已有索引值对应的数据值: - -```swift -shoppingList[0] = "Six eggs" -// 其中的第一项现在是 "Six eggs" 而不是 "Eggs" -``` - -还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把`"Chocolate Spread"`,`"Cheese"`,和`"Butter"`替换为`"Bananas"`和 `"Apples"`: - -```swift -shoppingList[4...6] = ["Bananas", "Apples"] -// shoppingList 现在有6项 -``` - -> 注意: -不可以用下标访问的形式去在数组尾部添加新项。 - - -调用数组的`insert(_:atIndex:)`方法来在某个具体索引值之前添加数据项: - -```swift -shoppingList.insert("Maple Syrup", atIndex: 0) -// shoppingList 现在有7项 -// "Maple Syrup" 现在是这个列表中的第一项 -``` - -这次`insert(_:atIndex:)`方法调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。 - -类似的我们可以使用`removeAtIndex(_:)`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): - -```swift -let mapleSyrup = shoppingList.removeAtIndex(0) -// 索引值为0的数据项被移除 -// shoppingList 现在只有6项,而且不包括 Maple Syrup -// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup" -``` -> 注意: -如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。 - -数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`0`的数据项的值再次等于`"Six eggs"`: - -```swift -firstItem = shoppingList[0] -// firstItem 现在等于 "Six eggs" -``` - -如果我们只想把数组中的最后一项移除,可以使用`removeLast()`方法而不是`removeAtIndex(_:)`方法来避免我们需要获取数组的`count`属性。就像后者一样,前者也会返回被移除的数据项: - -```swift -let apples = shoppingList.removeLast() -// 数组的最后一项被移除了 -// shoppingList 现在只有5项,不包括 cheese -// apples 常量的值现在等于 "Apples" 字符串 -``` - - -### 数组的遍历 - -我们可以使用`for-in`循环来遍历所有数组中的数据项: - -```swift -for item in shoppingList { - print(item) -} -// Six eggs -// Milk -// Flour -// Baking Powder -// Bananas -``` - -如果我们同时需要每个数据项的值和索引值,可以使用`enumerate()`方法来进行数组遍历。`enumerate()`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: - -```swift -for (index, value) in shoppingList.enumerate() { - print("Item \(String(index + 1)): \(value)") -} -// Item 1: Six eggs -// Item 2: Milk -// Item 3: Flour -// Item 4: Baking Powder -// Item 5: Bananas -``` - -更多关于`for-in`循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。 - - -## 集合(Sets) - -*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 - -> 注意: -> Swift的`Set`类型被桥接到`Fundation`中的`NSSet`类。 -> 关于使用`Fundation`和`Cocoa`中`Set`的知识,请看 [*Using Swift with Cocoa and Objective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。 - - -#### 集合类型的哈希值 - -一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是`Int`类型的,相等的对象哈希值必须相同,比如`a==b`,因此必须`a.hashValue == b.hashValue`。 - -Swift 的所有基本类型(比如`String`,`Int`,`Double`和`Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。 - -> 注意: -> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的`Hashable`协议。符合`Hashable`协议的类型需要提供一个类型为`Int`的可读属性`hashValue`。由类型的`hashValue`属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 - -> 因为`Hashable`协议符合`Equatable`协议,所以符合该协议的类型也必须提供一个"是否相等"运算符(`==`)的实现。这个`Equatable`协议要求任何符合`==`实现的实例间都是一种相等的关系。也就是说,对于`a,b,c`三个值来说,`==`的实现必须满足下面三种情况: - -> * `a == a`(自反性) -> * `a == b`意味着`b == a`(对称性) -> * `a == b && b == c`意味着`a == c`(传递性) - -关于符合协议的更多信息,请看[协议](./22_Protocols.html)。 - - -### 集合类型语法 - -Swift 中的`Set`类型被写为`Set`,这里的`Element`表示`Set`中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 - - -### 创建和构造一个空的集合 - -你可以通过构造器语法创建一个特定类型的空集合: - -```swift -var letters = Set() -print("letters is of type Set with \(letters.count) items.") -// 打印 "letters is of type Set with 0 items." -``` - -> 注意: -> 通过构造器,这里的`letters`变量的类型被推断为`Set`。 - -此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的`Set`: - -```swift -letters.insert("a") -// letters 现在含有1个 Character 类型的值 -letters = [] -// letters 现在是一个空的 Set, 但是它依然是 Set 类型 -``` - - -### 用数组字面量创建集合 - -你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。 - -下面的例子创建一个称之为`favoriteGenres`的集合来存储`String`类型的值: - -```swift -var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] -// favoriteGenres 被构造成含有三个初始值的集合 -``` - -这个`favoriteGenres`变量被声明为“一个`String`值的集合”,写为`Set`。由于这个特定的集合含有指定`String`类型的值,所以它只允许存储`String`类型值。这里的`favoriteGenres`变量有三个`String`类型的初始值(`"Rock"`,`"Classical"`和`"Hip hop"`),并以数组字面量的方式出现。 - -> 注意: -> `favoriteGenres`被声明为一个变量(拥有`var`标示符)而不是一个常量(拥有`let`标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 - -一个`Set`类型不能从数组字面量中被单独推断出来,因此`Set`类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个`Set`并且该数组字面量中的所有元素类型相同,那么你无须写出`Set`的具体类型。`favoriteGenres`的构造形式可以采用简化的方式代替: - -```swift -var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] -``` - -由于数组字面量中的所有元素类型相同,Swift 可以推断出`Set`作为`favoriteGenres`变量的正确类型。 - - -### 访问和修改一个集合 - -你可以通过`Set`的属性和方法来访问和修改一个`Set`。 - -为了找出一个`Set`中元素的数量,可以使用其只读属性`count`: - -```swift -print("I have \(favoriteGenres.count) favorite music genres.") -// 打印 "I have 3 favorite music genres." -``` - -使用布尔属性`isEmpty`作为一个缩写形式去检查`count`属性是否为`0`: - -```swift -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." -``` - -你可以通过调用`Set`的`insert(_:)`方法来添加一个新元素: - -```swift -favoriteGenres.insert("Jazz") -// favoriteGenres 现在包含4个元素 -``` - -你可以通过调用`Set`的`remove(_:)`方法去删除一个元素,如果该值是该`Set`的一个元素则删除该元素并且返回被删除的元素值,否则如果该`Set`不包含该值,则返回`nil`。另外,`Set`中的所有元素可以通过它的`removeAll()`方法删除。 - -```swift -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(_:)`方法去检查`Set`中是否包含一个特定的值: - -```swift -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`循环中遍历一个`Set`中的所有值。 - -```swift -for genre in favoriteGenres { - print("\(genre)") -} -// Classical -// Jazz -// Hip hop -``` - -更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 - -Swift 的`Set`类型没有确定的顺序,为了按照特定顺序来遍历一个`Set`中的值可以使用`sort()`方法,它将根据提供的序列返回一个有序集合. - -```swift -for genre in favoriteGenres.sort() { - print("\(genre)") -} -// prints "Classical" -// prints "Hip hop" -// prints "Jazz -``` - - -### 集合操作 - -你可以高效地完成`Set`的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 - - -#### 基本集合操作 - -下面的插图描述了两个集合-`a`和`b`-以及通过阴影部分的区域显示集合各种操作的结果。 - -![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png) - -* 使用`intersect(_:)`方法根据两个集合中都包含的值创建的一个新的集合。 -* 使用`exclusiveOr(_:)`方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 -* 使用`union(_:)`方法根据两个集合的值创建一个新的集合。 -* 使用`subtract(_:)`方法根据不在该集合中的值创建一个新的集合。 - -```swift -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).sort() -// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -oddDigits.intersect(evenDigits).sort() -// [] -oddDigits.subtract(singleDigitPrimeNumbers).sort() -// [1, 9] -oddDigits.exclusiveOr(singleDigitPrimeNumbers).sort() -// [1, 2, 9] -``` - - -#### 集合成员关系和相等 - -下面的插图描述了三个集合-`a`,`b`和`c`,以及通过重叠区域表述集合间共享的元素。集合`a`是集合`b`的父集合,因为`a`包含了`b`中所有的元素,相反的,集合`b`是集合`a`的子集合,因为属于`b`的元素也被`a`包含。集合`b`和集合`c`彼此不关联,因为它们之间没有共同的元素。 - -![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png) - -* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。 -* 使用`isSubsetOf(_:)`方法来判断一个集合中的值是否也被包含在另外一个集合中。 -* 使用`isSupersetOf(_:)`方法来判断一个集合中包含另一个集合中所有的值。 -* 使用`isStrictSubsetOf(_:)`或者`isStrictSupersetOf(_:)`方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 -* 使用`isDisjointWith(_:)`方法来判断两个集合是否不含有相同的值。 - -```swift -let houseAnimals: Set = ["🐶", "🐱"] -let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] -let cityAnimals: Set = ["🐦", "🐭"] - -houseAnimals.isSubsetOf(farmAnimals) -// true -farmAnimals.isSupersetOf(houseAnimals) -// true -farmAnimals.isDisjointWith(cityAnimals) -// true -``` - - -## 字典 - -*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 - -> 注意: -> Swift 的`Dictionary`类型被桥接到`Foundation`的`NSDictionary`类。 -> 更多关于在`Foundation`和`Cocoa`中使用`Dictionary`类型的信息,参见 [*Using Swift with Cocoa and Objective-C (Swift 2.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 - - -## 字典类型快捷语法 - -Swift 的字典使用`Dictionary`定义,其中`Key`是字典中键的数据类型,`Value`是字典中对应于这些键所存储值的数据类型。 - -> 注意: -> 一个字典的`Key`类型必须遵循`Hashable`协议,就像`Set`的值类型。 - -我们也可以用`[Key: Value]`这样快捷的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。 - - -### 创建一个空字典 - -我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典: - -```swift -var namesOfIntegers = [Int: String]() -// namesOfIntegers 是一个空的 [Int: String] 字典 -``` - -这个例子创建了一个`[Int: String]`类型的空字典来储存整数的英语命名。它的键是`Int`型,值是`String`型。 - -如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号): - -```swift -namesOfIntegers[16] = "sixteen" -// namesOfIntegers 现在包含一个键值对 -namesOfIntegers = [:] -// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 -``` - - -## 用字典字面量创建字典 - -我们可以使用字典字面量来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作`Dictionary`集合的快捷途径。 - -一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割: - -```swift -[key 1: value 1, key 2: value 2, key 3: value 3] -``` - -下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: - -```swift -var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] -``` - -`airports`字典被声明为一种`[String: String]`类型,这意味着这个字典的键和值都是`String`类型。 - -> 注意: -> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 - -`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`YYZ`,值是`Toronto Pearson`。第二对的键是`DUB`,值是`Dublin`。 - -这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的`airport`字典。 - -和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。 -`airports`字典也可以用这种简短方式定义: - -```swift -var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] -``` - -因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出`Dictionary`是`airports`字典的正确类型。 - - -### 访问和修改字典 - -我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 - -和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: - -```swift -print("The dictionary of airports contains \(airports.count) items.") -// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) -``` - -使用布尔属性`isEmpty`来快捷地检查字典的`count`属性是否等于0: - -```swift -if airports.isEmpty { - print("The airports dictionary is empty.") -} else { - print("The airports dictionary is not empty.") -} -// 打印 "The airports dictionary is not empty." -``` - -我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值: - -```swift -airports["LHR"] = "London" -// airports 字典现在有三个数据项 -``` - -我们也可以使用下标语法来改变特定键对应的值: - -```swift -airports["LHR"] = "London Heathrow" -// "LHR"对应的值 被改为 "London Heathrow -``` - -作为另一种下标方法,字典的`updateValue(_:forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)`方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)`这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。 - -`updateValue(_:forKey:)`方法会返回对应值的类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。 - -如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是`nil`。 - -```swift -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`: - -```swift -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." -``` - -我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: - -```swift -airports["APL"] = "Apple Internation" -// "Apple Internation" 不是真的 APL 机场, 删除它 -airports["APL"] = nil -// APL 现在被移除了 -``` - -此外,`removeValueForKey(_:)`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回`nil`: - -```swift -if let removedValue = airports.removeValueForKey("DUB") { - print("The removed airport's name is \(removedValue).") -} else { - print("The airports dictionary does not contain a value for DUB.") -} -// prints "The removed airport's name is Dublin Airport." -``` - - -### 字典遍历 - -我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都以`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: - -```swift -for (airportCode, airportName) in airports { - print("\(airportCode): \(airportName)") -} -// YYZ: Toronto Pearson -// LHR: London Heathrow -``` - -更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 - -通过访问`keys`或者`values`属性,我们也可以遍历字典的键或者值: - -```swift -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 -``` - -如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受`Array`实例的 API 的参数,可以直接使用`keys`或者`values`属性构造一个新数组: - -```swift -let airportCodes = [String](airports.keys) -// airportCodes 是 ["YYZ", "LHR"] - -let airportNames = [String](airports.values) -// airportNames 是 ["Toronto Pearson", "London Heathrow"] -``` - -Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的`keys`或`values`属性使用`sort()`方法。 +> 校对:[shanks](http://codebuild.me) + +本页包含内容: + +- [集合的可变性(Mutability of Collections)](#mutability_of_collections) +- [数组(Arrays)](#arrays) +- [集合(Sets)](#sets) +- [字典(Dictionaries)](#dictionaries) + +Swift 语言提供`Arrays`、`Sets`和`Dictionaries`三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。 + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/CollectionTypes_intro_2x.png) + +Swift 语言中的`Arrays`、`Sets`和`Dictionaries`中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信。 + +> 注意: +Swift 的`Arrays`、`Sets`和`Dictionaries`类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.html)章节。 + + +## 集合的可变性 + +如果创建一个`Arrays`、`Sets`或`Dictionaries`并且把它分配成一个变量,这个集合将会是*可变的*。这意味着我们可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把`Arrays`、`Sets`或`Dictionaries`分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。 + +> 注意: +在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。 + + +## 数组(Arrays) + +数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 + +> 注意: + Swift 的`Array`类型被桥接到`Foundation`中的`NSArray`类。 + 更多关于在`Foundation`和`Cocoa`中使用`Array`的信息,参见 [*Using Swift with Cocoa and Obejective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 + + +### 数组的简单语法 + +写 Swift 数组应该遵循像`Array`这样的形式,其中`Element`是这个数组中唯一允许存在的数据类型。我们也可以使用像`[Element]`这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 + + +### 创建一个空数组 + +我们可以使用构造语法来创建一个由特定数据类型构成的空数组: + +```swift +var someInts = [Int]() +print("someInts is of type [Int] with \(someInts.count) items.") +// 打印 "someInts is of type [Int] with 0 items." +``` + +注意,通过构造函数的类型,`someInts`的值类型被推断为`[Int]`。 + +或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:`[]`(一对空方括号): + +```swift +someInts.append(3) +// someInts 现在包含一个 Int 值 +someInts = [] +// someInts 现在是空数组,但是仍然是 [Int] 类型的。 +``` + + +### 创建一个带有默认值的数组 + +Swift 中的`Array`类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeatedValue`)传入数组构造函数: + +```swift +var threeDoubles = [Double](count: 3, repeatedValue:0.0) +// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0] +``` + + +### 通过两个数组相加创建一个数组 + +我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来: + +```swift +var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5) +// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5] + +var sixDoubles = threeDoubles + anotherThreeDoubles +// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] +``` + + +### 用字面量构造数组 + +我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值: + +`[value 1, value 2, value 3]`。 + +下面这个例子创建了一个叫做`shoppingList`并且存储`String`的数组: + +```swift +var shoppingList: [String] = ["Eggs", "Milk"] +// shoppingList 已经被构造并且拥有两个初始项。 +``` + +`shoppingList`变量被声明为“字符串值类型的数组“,记作`[String]`。 因为这个数组被规定只有`String`一种数据结构,所以只有`String`类型可以在其中被存取。 在这里,`shoppinglist`数组由两个`String`值(`"Eggs"` 和`"Milk"`)构造,并且由字面量定义。 + +> 注意: +`Shoppinglist`数组被声明为变量(`var`关键字创建)而不是常量(`let`创建)是因为以后可能会有更多的数据项被插入其中。 + +在这个例子中,字面量仅仅包含两个`String`值。匹配了该数组的变量声明(只能包含`String`的数组),所以这个字面量的分配过程可以作为用两个初始项来构造`shoppinglist`的一种方式。 + +由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。 `shoppinglist`的构造也可以这样写: + +```swift +var shoppingList = ["Eggs", "Milk"] +``` + +因为所有字面量中的值都是相同的类型,Swift 可以推断出`[String]`是`shoppinglist`中变量的正确类型。 + + +### 访问和修改数组 + +我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。 + +可以使用数组的只读属性`count`来获取数组中的数据项数量: + +```swift +print("The shopping list contains \(shoppingList.count) items.") +// 输出 "The shopping list contains 2 items."(这个数组有2个项) +``` + +使用布尔值属性`isEmpty`作为检查`count`属性的值是否为 0 的捷径: + +```swift +if shoppingList.isEmpty { + print("The shopping list is empty.") +} else { + print("The shopping list is not empty.") +} +// 打印 "The shopping list is not empty."(shoppinglist 不是空的) +``` + +也可以使用`append(_:)`方法在数组后面添加新的数据项: + +```swift +shoppingList.append("Flour") +// shoppingList 现在有3个数据项,有人在摊煎饼 +``` + +除此之外,使用加法赋值运算符(`+=`)也可以直接在数组后面添加一个或多个拥有相同类型的数据项: + +```swift +shoppingList += ["Baking Powder"] +// shoppingList 现在有四项了 +shoppingList += ["Chocolate Spread", "Cheese", "Butter"] +// shoppingList 现在有七项了 +``` + +可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中: + +```swift +var firstItem = shoppingList[0] +// 第一项是 "Eggs" +``` + +> 注意: +第一项在数组中的索引值是`0`而不是`1`。 Swift 中的数组索引总是从零开始。 + +我们也可以用下标来改变某个已有索引值对应的数据值: + +```swift +shoppingList[0] = "Six eggs" +// 其中的第一项现在是 "Six eggs" 而不是 "Eggs" +``` + +还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把`"Chocolate Spread"`,`"Cheese"`,和`"Butter"`替换为`"Bananas"`和 `"Apples"`: + +```swift +shoppingList[4...6] = ["Bananas", "Apples"] +// shoppingList 现在有6项 +``` + +> 注意: +不可以用下标访问的形式去在数组尾部添加新项。 + + +调用数组的`insert(_:atIndex:)`方法来在某个具体索引值之前添加数据项: + +```swift +shoppingList.insert("Maple Syrup", atIndex: 0) +// shoppingList 现在有7项 +// "Maple Syrup" 现在是这个列表中的第一项 +``` + +这次`insert(_:atIndex:)`方法调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。 + +类似的我们可以使用`removeAtIndex(_:)`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): + +```swift +let mapleSyrup = shoppingList.removeAtIndex(0) +// 索引值为0的数据项被移除 +// shoppingList 现在只有6项,而且不包括 Maple Syrup +// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup" +``` +> 注意: +如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。 + +数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`0`的数据项的值再次等于`"Six eggs"`: + +```swift +firstItem = shoppingList[0] +// firstItem 现在等于 "Six eggs" +``` + +如果我们只想把数组中的最后一项移除,可以使用`removeLast()`方法而不是`removeAtIndex(_:)`方法来避免我们需要获取数组的`count`属性。就像后者一样,前者也会返回被移除的数据项: + +```swift +let apples = shoppingList.removeLast() +// 数组的最后一项被移除了 +// shoppingList 现在只有5项,不包括 cheese +// apples 常量的值现在等于 "Apples" 字符串 +``` + + +### 数组的遍历 + +我们可以使用`for-in`循环来遍历所有数组中的数据项: + +```swift +for item in shoppingList { + print(item) +} +// Six eggs +// Milk +// Flour +// Baking Powder +// Bananas +``` + +如果我们同时需要每个数据项的值和索引值,可以使用`enumerate()`方法来进行数组遍历。`enumerate()`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: + +```swift +for (index, value) in shoppingList.enumerate() { + print("Item \(String(index + 1)): \(value)") +} +// Item 1: Six eggs +// Item 2: Milk +// Item 3: Flour +// Item 4: Baking Powder +// Item 5: Bananas +``` + +更多关于`for-in`循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。 + + +## 集合(Sets) + +*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 + +> 注意: +> Swift的`Set`类型被桥接到`Foundation`中的`NSSet`类。 +> 关于使用`Foundation`和`Cocoa`中`Set`的知识,请看 [*Using Swift with Cocoa and Objective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。 + + +#### 集合类型的哈希值 + +一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是`Int`类型的,相等的对象哈希值必须相同,比如`a==b`,因此必须`a.hashValue == b.hashValue`。 + +Swift 的所有基本类型(比如`String`,`Int`,`Double`和`Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。 + +> 注意: +> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的`Hashable`协议。符合`Hashable`协议的类型需要提供一个类型为`Int`的可读属性`hashValue`。由类型的`hashValue`属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 + +> 因为`Hashable`协议符合`Equatable`协议,所以符合该协议的类型也必须提供一个"是否相等"运算符(`==`)的实现。这个`Equatable`协议要求任何符合`==`实现的实例间都是一种相等的关系。也就是说,对于`a,b,c`三个值来说,`==`的实现必须满足下面三种情况: + +> * `a == a`(自反性) +> * `a == b`意味着`b == a`(对称性) +> * `a == b && b == c`意味着`a == c`(传递性) + +关于符合协议的更多信息,请看[协议](./22_Protocols.html)。 + + +### 集合类型语法 + +Swift 中的`Set`类型被写为`Set`,这里的`Element`表示`Set`中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 + + +### 创建和构造一个空的集合 + +你可以通过构造器语法创建一个特定类型的空集合: + +```swift +var letters = Set() +print("letters is of type Set with \(letters.count) items.") +// 打印 "letters is of type Set with 0 items." +``` + +> 注意: +> 通过构造器,这里的`letters`变量的类型被推断为`Set`。 + +此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的`Set`: + +```swift +letters.insert("a") +// letters 现在含有1个 Character 类型的值 +letters = [] +// letters 现在是一个空的 Set, 但是它依然是 Set 类型 +``` + + +### 用数组字面量创建集合 + +你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。 + +下面的例子创建一个称之为`favoriteGenres`的集合来存储`String`类型的值: + +```swift +var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +// favoriteGenres 被构造成含有三个初始值的集合 +``` + +这个`favoriteGenres`变量被声明为“一个`String`值的集合”,写为`Set`。由于这个特定的集合含有指定`String`类型的值,所以它只允许存储`String`类型值。这里的`favoriteGenres`变量有三个`String`类型的初始值(`"Rock"`,`"Classical"`和`"Hip hop"`),并以数组字面量的方式出现。 + +> 注意: +> `favoriteGenres`被声明为一个变量(拥有`var`标示符)而不是一个常量(拥有`let`标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 + +一个`Set`类型不能从数组字面量中被单独推断出来,因此`Set`类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个`Set`并且该数组字面量中的所有元素类型相同,那么你无须写出`Set`的具体类型。`favoriteGenres`的构造形式可以采用简化的方式代替: + +```swift +var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +``` + +由于数组字面量中的所有元素类型相同,Swift 可以推断出`Set`作为`favoriteGenres`变量的正确类型。 + + +### 访问和修改一个集合 + +你可以通过`Set`的属性和方法来访问和修改一个`Set`。 + +为了找出一个`Set`中元素的数量,可以使用其只读属性`count`: + +```swift +print("I have \(favoriteGenres.count) favorite music genres.") +// 打印 "I have 3 favorite music genres." +``` + +使用布尔属性`isEmpty`作为一个缩写形式去检查`count`属性是否为`0`: + +```swift +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." +``` + +你可以通过调用`Set`的`insert(_:)`方法来添加一个新元素: + +```swift +favoriteGenres.insert("Jazz") +// favoriteGenres 现在包含4个元素 +``` + +你可以通过调用`Set`的`remove(_:)`方法去删除一个元素,如果该值是该`Set`的一个元素则删除该元素并且返回被删除的元素值,否则如果该`Set`不包含该值,则返回`nil`。另外,`Set`中的所有元素可以通过它的`removeAll()`方法删除。 + +```swift +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(_:)`方法去检查`Set`中是否包含一个特定的值: + +```swift +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`循环中遍历一个`Set`中的所有值。 + +```swift +for genre in favoriteGenres { + print("\(genre)") +} +// Classical +// Jazz +// Hip hop +``` + +更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 + +Swift 的`Set`类型没有确定的顺序,为了按照特定顺序来遍历一个`Set`中的值可以使用`sort()`方法,它将根据提供的序列返回一个有序集合. + +```swift +for genre in favoriteGenres.sort() { + print("\(genre)") +} +// prints "Classical" +// prints "Hip hop" +// prints "Jazz +``` + + +### 集合操作 + +你可以高效地完成`Set`的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 + + +#### 基本集合操作 + +下面的插图描述了两个集合-`a`和`b`-以及通过阴影部分的区域显示集合各种操作的结果。 + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png) + +* 使用`intersect(_:)`方法根据两个集合中都包含的值创建的一个新的集合。 +* 使用`exclusiveOr(_:)`方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 +* 使用`union(_:)`方法根据两个集合的值创建一个新的集合。 +* 使用`subtract(_:)`方法根据不在该集合中的值创建一个新的集合。 + +```swift +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).sort() +// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +oddDigits.intersect(evenDigits).sort() +// [] +oddDigits.subtract(singleDigitPrimeNumbers).sort() +// [1, 9] +oddDigits.exclusiveOr(singleDigitPrimeNumbers).sort() +// [1, 2, 9] +``` + + +#### 集合成员关系和相等 + +下面的插图描述了三个集合-`a`,`b`和`c`,以及通过重叠区域表述集合间共享的元素。集合`a`是集合`b`的父集合,因为`a`包含了`b`中所有的元素,相反的,集合`b`是集合`a`的子集合,因为属于`b`的元素也被`a`包含。集合`b`和集合`c`彼此不关联,因为它们之间没有共同的元素。 + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png) + +* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。 +* 使用`isSubsetOf(_:)`方法来判断一个集合中的值是否也被包含在另外一个集合中。 +* 使用`isSupersetOf(_:)`方法来判断一个集合中包含另一个集合中所有的值。 +* 使用`isStrictSubsetOf(_:)`或者`isStrictSupersetOf(_:)`方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 +* 使用`isDisjointWith(_:)`方法来判断两个集合是否不含有相同的值。 + +```swift +let houseAnimals: Set = ["🐶", "🐱"] +let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] +let cityAnimals: Set = ["🐦", "🐭"] + +houseAnimals.isSubsetOf(farmAnimals) +// true +farmAnimals.isSupersetOf(houseAnimals) +// true +farmAnimals.isDisjointWith(cityAnimals) +// true +``` + + +## 字典 + +*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 + +> 注意: +> Swift 的`Dictionary`类型被桥接到`Foundation`的`NSDictionary`类。 +> 更多关于在`Foundation`和`Cocoa`中使用`Dictionary`类型的信息,参见 [*Using Swift with Cocoa and Objective-C (Swift 2.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 + + +## 字典类型快捷语法 + +Swift 的字典使用`Dictionary`定义,其中`Key`是字典中键的数据类型,`Value`是字典中对应于这些键所存储值的数据类型。 + +> 注意: +> 一个字典的`Key`类型必须遵循`Hashable`协议,就像`Set`的值类型。 + +我们也可以用`[Key: Value]`这样快捷的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。 + + +### 创建一个空字典 + +我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典: + +```swift +var namesOfIntegers = [Int: String]() +// namesOfIntegers 是一个空的 [Int: String] 字典 +``` + +这个例子创建了一个`[Int: String]`类型的空字典来储存整数的英语命名。它的键是`Int`型,值是`String`型。 + +如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号): + +```swift +namesOfIntegers[16] = "sixteen" +// namesOfIntegers 现在包含一个键值对 +namesOfIntegers = [:] +// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 +``` + + +## 用字典字面量创建字典 + +我们可以使用字典字面量来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作`Dictionary`集合的快捷途径。 + +一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割: + +```swift +[key 1: value 1, key 2: value 2, key 3: value 3] +``` + +下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: + +```swift +var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] +``` + +`airports`字典被声明为一种`[String: String]`类型,这意味着这个字典的键和值都是`String`类型。 + +> 注意: +> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 + +`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`YYZ`,值是`Toronto Pearson`。第二对的键是`DUB`,值是`Dublin`。 + +这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的`airport`字典。 + +和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。 +`airports`字典也可以用这种简短方式定义: + +```swift +var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] +``` + +因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出`Dictionary`是`airports`字典的正确类型。 + + +### 访问和修改字典 + +我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 + +和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: + +```swift +print("The dictionary of airports contains \(airports.count) items.") +// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) +``` + +使用布尔属性`isEmpty`来快捷地检查字典的`count`属性是否等于0: + +```swift +if airports.isEmpty { + print("The airports dictionary is empty.") +} else { + print("The airports dictionary is not empty.") +} +// 打印 "The airports dictionary is not empty." +``` + +我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值: + +```swift +airports["LHR"] = "London" +// airports 字典现在有三个数据项 +``` + +我们也可以使用下标语法来改变特定键对应的值: + +```swift +airports["LHR"] = "London Heathrow" +// "LHR"对应的值 被改为 "London Heathrow +``` + +作为另一种下标方法,字典的`updateValue(_:forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)`方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)`这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。 + +`updateValue(_:forKey:)`方法会返回对应值的类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。 + +如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是`nil`。 + +```swift +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`: + +```swift +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." +``` + +我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: + +```swift +airports["APL"] = "Apple Internation" +// "Apple Internation" 不是真的 APL 机场, 删除它 +airports["APL"] = nil +// APL 现在被移除了 +``` + +此外,`removeValueForKey(_:)`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回`nil`: + +```swift +if let removedValue = airports.removeValueForKey("DUB") { + print("The removed airport's name is \(removedValue).") +} else { + print("The airports dictionary does not contain a value for DUB.") +} +// prints "The removed airport's name is Dublin Airport." +``` + + +### 字典遍历 + +我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都以`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: + +```swift +for (airportCode, airportName) in airports { + print("\(airportCode): \(airportName)") +} +// YYZ: Toronto Pearson +// LHR: London Heathrow +``` + +更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 + +通过访问`keys`或者`values`属性,我们也可以遍历字典的键或者值: + +```swift +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 +``` + +如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受`Array`实例的 API 的参数,可以直接使用`keys`或者`values`属性构造一个新数组: + +```swift +let airportCodes = [String](airports.keys) +// airportCodes 是 ["YYZ", "LHR"] + +let airportNames = [String](airports.values) +// airportNames 是 ["Toronto Pearson", "London Heathrow"] +``` + +Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的`keys`或`values`属性使用`sort()`方法。 From 2c6079d4d4353d71e10f0f0d0e27fa16111d62c0 Mon Sep 17 00:00:00 2001 From: 949478479 <949478479@qq.com> Date: Mon, 16 Nov 2015 21:11:17 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=A0=A1=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/chapter2/14_Initialization.md | 173 ++++++++++++++------------- 1 file changed, 90 insertions(+), 83 deletions(-) diff --git a/source/chapter2/14_Initialization.md b/source/chapter2/14_Initialization.md index e06f97a2..df374c77 100755 --- a/source/chapter2/14_Initialization.md +++ b/source/chapter2/14_Initialization.md @@ -21,7 +21,7 @@ - [类的继承和构造过程](#class_inheritance_and_initialization) - [可失败构造器](#failable_initializers) - [必要构造器](#required_initializers) -- [通过闭包和函数来设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function) +- [通过闭包或函数设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function) 构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。 @@ -162,7 +162,7 @@ let veryGreen = Color(0.0, 1.0, 0.0) 下面是之前`Celsius`例子的扩展,跟之前相比添加了一个带有`Double`类型参数的构造器,其外部名用`_`代替: ```swift -struct Celsius {I +struct Celsius { var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 @@ -390,7 +390,7 @@ convenience init(parameters) { 便利构造器必须调用同一类中定义的其它构造器。 ##### 规则 3 -便利构造器必须最终以调用一个指定构造器结束。 +便利构造器必须最终导致一个指定构造器被调用。 一个更方便记忆的方法是: @@ -689,19 +689,19 @@ for item in breakfastList { ## 可失败构造器 -如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有用的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 +如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在`init`关键字后面加添问号`(init?)`。 -> 注意: -可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同。 +> 注意 +可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。 -可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过`return nil` 语句,来表明可失败构造器在何种情况下“失败”。 +可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过`return nil`语句来表明可失败构造器在何种情况下应该“失败”。 -> 注意: -严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了能确保对象自身能被正确构建。所以即使你在表明可失败构造器,失败的这种情况下,用到了`return nil`。也不要在表明可失败构造器成功的这种情况下,使用关键字 `return`。 +> 注意 +严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用`return nil`表明可失败构造器构造失败,而不要用关键字`return`来表明构造成功。 -下例中,定义了一个名为`Animal`的结构体,其中有一个名为`species`的,`String`类型的常量属性。同时该结构体还定义了一个,带一个`String`类型参数`species`的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。 +下例中,定义了一个名为`Animal`的结构体,其中有一个名为`species`的`String`类型的常量属性。同时该结构体还定义了一个接受一个名为`species`的`String`类型参数的可失败构造器。这个可失败构造器检查传入的参数是否为一个空字符串。如果为空字符串,则构造失败。否则,`species`属性被赋值,构造成功。 ```swift struct Animal { @@ -713,7 +713,7 @@ struct Animal { } ``` -你可以通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功。 +你可以通过该可失败构造器来构建一个`Animal`的实例,并检查构造过程是否成功: ```swift let someCreature = Animal(species: "Giraffe") @@ -725,7 +725,7 @@ if let giraffe = someCreature { // 打印 "An animal was initialized with a species of Giraffe" ``` -如果你给该可失败构造器传入一个空字符串作为其参数,则该可失败构造器失败。 +如果你给该可失败构造器传入一个空字符串作为其参数,则会导致构造失败: ```swift let anonymousCreature = Animal(species: "") @@ -737,15 +737,15 @@ if anonymousCreature == nil { // 打印 "The anonymous creature could not be initialized" ``` -> 注意: -空字符串(如`""`,而不是`"Giraffe"`)和一个值为`nil`的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们只所以让`Animal`的可失败构造器,构建对象失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。 +> 注意 +空字符串(如`""`,而不是`"Giraffe"`)和一个值为`nil`的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让`Animal`的可失败构造器构造失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。 -###枚举类型的可失败构造器 +### 枚举类型的可失败构造器 -你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足枚举成员期望的条件时,构造失败。 +你可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。 -下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(`Kelvin`,`Celsius`,和 `Fahrenheit`)和一个被用来找到`Character`值所对应的枚举成员的可失败构造器: +下例中,定义了一个名为`TemperatureUnit`的枚举类型。其中包含了三个可能的枚举成员(`Kelvin`,`Celsius`,和`Fahrenheit`),以及一个根据`Character`值找出所对应的枚举成员的可失败构造器: ```swift enum TemperatureUnit { @@ -765,7 +765,7 @@ enum TemperatureUnit { } ``` -你可以通过给该可失败构造器传递合适的参数来获取这三个枚举成员中相匹配的其中一个枚举成员。当参数的值不能与任意一枚举成员相匹配时,该枚举类型的构建过程失败: +你可以利用该可失败构造器在三个枚举成员中获取一个相匹配的枚举成员,当参数的值不能与任何枚举成员相匹配时,则构造失败: ```swift let fahrenheitUnit = TemperatureUnit(symbol: "F") @@ -782,11 +782,11 @@ if unknownUnit == nil { ``` -###带原始值的枚举类型的可失败构造器 +### 带原始值的枚举类型的可失败构造器 -带原始值的枚举类型会自带一个可失败构造器`init?(rawValue:)`,该可失败构造器有一个名为`rawValue`的默认参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构造一个带此原始值的枚举成员,否则构造失败。 +带原始值的枚举类型会自带一个可失败构造器`init?(rawValue:)`,该可失败构造器有一个名为`rawValue`的参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和某个枚举成员的原始值匹配,则该构造器会构造相应的枚举成员,否则构造失败。 -因此上面的 TemperatureUnit的例子可以重写为: +因此上面的`TemperatureUnit`的例子可以重写为: ```swift enum TemperatureUnit: Character { @@ -797,23 +797,23 @@ let fahrenheitUnit = TemperatureUnit(rawValue: "F") if fahrenheitUnit != nil { print("This is a defined temperature unit, so initialization succeeded.") } -// prints "This is a defined temperature unit, so initialization succeeded." +// 打印 "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(rawValue: "X") if unknownUnit == nil { print("This is not a defined temperature unit, so initialization failed.") } -// prints "This is not a defined temperature unit, so initialization failed." +// 打印 "This is not a defined temperature unit, so initialization failed." ``` -###类的可失败构造器 +### 类的可失败构造器 -值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,结构体`Animal`的可失败构造器触发失败的行为,甚至发生在`species`属性的值被初始化以前。 +值类型(也就是结构体或枚举)的可失败构造器,可以在构造过程中的任意时间点触发构造失败。比如在前面的例子中,结构体`Animal`的可失败构造器在构造过程一开始就触发了构造失败,甚至在`species`属性被初始化前。 -而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。 +而对类而言,可失败构造器只能在类引入的所有存储型属性被初始化后,以及构造器代理调用完成后,才能触发构造失败。 -下面例子展示了如何使用隐式解析可选类型来实现这个类的可失败构造器的要求: +下面例子展示了如何在类的可失败构造器中使用隐式解包可选类型来满足上述要求: ```swift class Product { @@ -824,33 +824,37 @@ class Product { } } ``` -上面定义的`Product`类,其内部结构和之前`Animal`结构体很相似。`Product`类有一个不能为空字符串的`name`常量属性。为了强制满足这个要求,`Product`类使用了可失败构造器来确保这个属性的值在构造器成功时不为空。 -毕竟,`Product`是一个类而不是结构体,也就不能和`Animal`一样了。`Product`类的所有可失败构造器必须在自己失败前给`name`属性一个初始值。 +上面定义的`Product`类和之前的`Animal`结构体很相似。`Product`类有一个不能为空字符串的常量属性`name`。为了强制这个要求,`Product`类使用了可失败构造器确保这个属性的值不是空字符串后,才允许构造成功。 -上面的例子中,`Product`类的`name`属性被定义为隐式解析可选字符串类型(`String!`)。因为它是一个可选类型,所以在构造过程里的赋值前,`name`属性有个默认值`nil`。用默认值`nil`意味着`Product`类的所有属性都有一个合法的初始值。因而,在构造器中给`name`属性赋一个特定的值前,可失败构造器能够在传入一个空字符串时触发构造过程的失败。 +毕竟,`Product`是一个类而不是结构体,这意味着不同于`Animal`,`Product`类的所有可失败构造器必须给`name`属性一个初始值,然后才能触发构造失败。 -因为`name`属性是一个常量,所以一旦`Product`类构造成功,`name`属性肯定有一个非`nil`的值。即使它被定义为隐式解析可选类型,也完全可以放心大胆地直接访问,而不用考虑`name`属性是否有值。 +上面的例子中,`Product`类的`name`属性被定义为隐式解包可选字符串类型(`String!`)。因为它是一个可选类型,所以它在构造过程中被赋值前,具有默认值`nil`。这个默认值`nil`意味着`Product`类引入的所有存储型属性都有一个有效的初始值。因此,一旦传入一个空字符串,该可失败构造器可以在`name`属性被赋值前触发构造失败。 + +> 译者注 +> 上面的示例代码和描述并不相符,根据描述,`if name.isEmpty { return nil }`这句代码应该在`self.name = name`之前,而这却会导致编译错误`error: all stored properties of a class instance must be initialized before returning nil from an initializer`,除非将`let name: String!`改为`var name: String!`。 + +因为`name`属性是一个常量,所以一旦构造成功,`name`属性肯定有一个非`nil`的值。即使它被定义为隐式解包可选类型,也完全可以放心大胆地直接访问,而不用检查`name`属性的值是否为`nil`: ```swift if let bowTie = Product(name: "bow tie") { - // 不需要检查 bowTie.name == nil + // 不需要检查 bowTie.name 是否为 nil print("The product's name is \(bowTie.name)") } // 打印 "The product's name is bow tie" ``` -###构造失败的传递 +### 构造失败的传递 -可失败构造器允许在同一类,结构体和枚举中横向代理其他的可失败构造器。类似的,子类的可失败构造器也能向上代理基类的可失败构造器。 +类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。 -无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。 +无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。 ->注意: -可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。 +> 注意 +可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。 -下面这个例子,定义了一个名为`CartItem`的`Product`类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为`quantity`的常量参数,用来表示该物品的数量至少为1: +下面这个例子,定义了一个名为`CartItem`的`Product`类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为`quantity`的常量存储型属性,并确保该属性的值至少为`1`: ```swift class CartItem: Product { @@ -859,18 +863,20 @@ class CartItem: Product { self.quantity = quantity super.init(name: name) if quantity < 1 { return nil } - } } ``` -和`Product`类中的`name`属性相类似的,`CartItem`类中的`quantity`属性的类型也是一个隐式解析可选类型,只不过由(`String!`)变为了(`Int!`)。这样做都是为了确保在构造过程中,该属性在被赋予特定的值之前能有一个默认的初始值nil。 +和`Product`类中的`name`属性类似,`CartItem`类中的`quantity`属性也是隐式解包可选类型。这意味着在构造过程中,该属性在被赋予特定的值之前能有一个默认的初始值`nil`。 -可失败构造器总是先向上代理调用基类,`Product`的构造器 `init(name:)`。这满足了可失败构造器在触发构造失败这个行为前必须总是执行构造代理调用这个条件。 +该可失败构造器以向上代理到父类的可失败构造器`init(name:)`开始。这满足了可失败构造器在触发构造失败前必须总是完成构造器代理调用这个条件。 -如果由于`name`的值为空而导致基类的构造器在构造过程中失败。则整个`CartIem`类的构造过程都将失败,后面的子类的构造过程都将不会被执行。如果基类构建成功,则继续运行子类的构造器代码。 +如果由于`name`的值为空字符串而导致父类的可失败构造器构造失败,则`CartIem`类的整个构造过程都将立即失败,之后的构造代码将不会再被执行。如果父类构造成功,`CartIem`的可失败构造器会进一步验证`quantity`的值是否不小于`1`。 -如果你构造了一个`CartItem`对象,并且该对象的`name`属性不为空以及`quantity`属性为 1 或者更多,则构造成功: +> 译者注 +> 上面的示例代码和描述也不相符,根据描述,`self.quantity = quantity`这句代码应该放在最后一行,而这却会导致编译错误`error: property 'self.quantity' not initialized at super.init call`,除非将`let quantity: Int!`改为`var quantity: Int!`。 + +如果你构造一个`name`的值为非空字符串,`quantity`的值不小于`1`的`CartItem`实例,则可成功构造: ```swift if let twoSocks = CartItem(name: "sock", quantity: 2) { @@ -878,7 +884,8 @@ if let twoSocks = CartItem(name: "sock", quantity: 2) { } // 打印 "Item: sock, quantity: 2" ``` -如果你构造一个`CartItem`对象,其`quantity`的值`0`, 则`CartItem`的可失败构造器触发构造失败的行为: + +如果你试图构造一个`quantity`的值为`0`的`CartItem`实例, 则`CartItem`的可失败构造器会触发构造失败: ```swift if let zeroShirts = CartItem(name: "shirt", quantity: 0) { @@ -889,7 +896,7 @@ if let zeroShirts = CartItem(name: "shirt", quantity: 0) { // 打印 "Unable to initialize zero shirts" ``` -类似的, 如果你构造一个`CartItem`对象,但其`name`的值为空, 则基类`Product`的可失败构造器将触发构造失败的行为,整个`CartItem`的构造行为同样为失败: +类似的,如果你试图构造一个`name`的值为空字符串的`CartItem`实例,则父类`Product`的可失败构造器会触发构造失败: ```swift if let oneUnnamed = CartItem(name: "", quantity: 1) { @@ -903,29 +910,29 @@ if let oneUnnamed = CartItem(name: "", quantity: 1) { ### 重写一个可失败构造器 -就如同其它构造器一样,你也可以用子类的可失败构造器重写基类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。 +如同其它的构造器,你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。 -注意当你用一个子类的非可失败构造器重写了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。 +注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。 ->注意: -你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。 +> 注意 +你可以用非可失败构造器重写可失败构造器,但反过来却不行。 -下例定义了一个名为`Document`的类,这个类中的`name`属性允许为`nil`和一个非空字符串,但不能是一个空字符串: +下例定义了一个名为`Document`的类,`name`属性的值必须为一个非空字符串或`nil`,但不能是一个空字符串: ```swift class Document { var name: String? - // 该构造器构建了一个name属性值为nil的document对象 + // 该构造器创建了一个 name 属性的值为 nil 的 document 实例 init() {} - // 该构造器构建了一个name属性值为非空字符串的document对象 + // 该构造器创建了一个 name 属性的值为非空字符串的 document 实例 init?(name: String) { - if name.isEmpty { return nil } self.name = name + if name.isEmpty { return nil } } } ``` -下面这个例子,定义了一个`Document`类的子类`AutomaticallyNamedDocument`。这个子类重写了父类的两个指定构造器,确保不论是通过没有 name 参数的构造器,还是通过传一个空字符串给`init(name:)`构造器,生成的实例中的`name`属性总有初始值`"[Untitled]"`。 +下面这个例子,定义了一个`Document`类的子类`AutomaticallyNamedDocument`。这个子类重写了父类的两个指定构造器,确保了无论是使用`init()`构造器,还是使用`init(name:)`构造器并为参数传递空字符串,生成的实例中的`name`属性总有初始`"[Untitled]"`: ```swift class AutomaticallyNamedDocument: Document { @@ -944,9 +951,9 @@ class AutomaticallyNamedDocument: Document { } ``` -`AutomaticallyNamedDocument`用一个非可失败构造器`init(name:)`,重写了父类的可失败构造器`init?(name:)`。因为子类用不同的方法处理了`name`属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败的构造器,用一个非可失败版本代替了父类的版本。 +`AutomaticallyNamedDocument`用一个非可失败构造器`init(name:)`重写了父类的可失败构造器`init?(name:)`。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败构造器,因此子类用一个非可失败构造器代替了父类的可失败构造器。 -你可以在构造器中调用父类的可失败构造器强制解包,以实现子类的非可失败构造器。比如,下面的`UntitledDocument `子类总有值为`"[Untitled]"`的 name 属性,它在构造过程中用了父类的可失败的构造器`init(name:)`。 +你可以在子类的非可失败构造器中使用强制解包来调用父类的可失败构造器。比如,下面的`UntitledDocument`子类的`name`属性的值总是`"[Untitled]"`,它在构造过程中使用了父类的可失败构造器`init?(name:)`: ```swift class UntitledDocument: Document { @@ -955,50 +962,50 @@ class UntitledDocument: Document { } } ``` -在这个例子中,如果在调用父类的构造器`init(name:)`时传给 name 的是空字符串,那么强制解绑操作会造成运行时错误。不过,因为这里是通过字符串常量来调用它,所以并不会发生运行时错误。 + +在这个例子中,如果在调用父类的可失败构造器`init?(name:)`时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过非空的字符串常量来调用它,所以并不会发生运行时错误。 ### 可失败构造器 init! -通常来说我们通过在`init`关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以使用通过在`init`后面添加惊叹号的方式来定义一个可失败构造器`(init!)`,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。 +通常来说我们通过在`init`关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以通过在`init`后面添加惊叹号的方式来定义一个可失败构造器(`(init!)`),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。 -你可以在 `init? `构造器中代理调用 `init!`构造器,反之亦然。 -你也可以用 `init?`重写 `init!`,反之亦然。 -你还可以用 `init`代理调用`init!`,但这会触发一个断言: `init!` 构造器是否会触发构造失败? +你可以在`init?`中代理到`init!`,反之亦然。你也可以用`init?`重写`init!`,反之亦然。你还可以用`init`代理到`init!`,不过,一旦`init!`构造失败,则会触发一个断言。 -##必要构造器 +## 必要构造器 -在类的构造器前添加 `required` 修饰符表明所有该类的子类都必须实现该构造器: +在类的构造器前添加`required`修饰符表明所有该类的子类都必须实现该构造器: ```swift class SomeClass { required init() { - // 在这里添加该必要构造器的实现代码 + // 构造器的实现代码 } } ``` -在子类重写父类的必要构造器时,必须在子类的构造器前也添加`required`修饰符,这是为了保证继承链上子类的构造器也是必要构造器。在重写父类的必要构造器时,不需要添加`override`修饰符: + +在子类重写父类的必要构造器时,必须在子类的构造器前也添加`required`修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加`override`修饰符: ```swift class SomeSubclass: SomeClass { required init() { - // 在这里添加子类必要构造器的实现代码 + // 构造器的实现代码 } } ``` ->注意: -如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。 +> 注意 +如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。 -## 通过闭包和函数来设置属性的默认值 +## 通过闭包或函数设置属性的默认值 -如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。 +如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。 -这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。 +这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。 -下面列举了闭包如何提供默认值的代码概要: +下面介绍了如何用闭包为属性提供默认值: ```swift class SomeClass { @@ -1006,22 +1013,22 @@ class SomeClass { // 在这个闭包中给 someProperty 创建一个默认值 // someValue 必须和 SomeType 类型相同 return someValue - }() + }() } ``` -注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。 +注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。 ->注意: -如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的`self`属性,或者调用其它的实例方法。 +> 注意 +如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的`self`属性,或者调用任何实例方法。 下面例子中定义了一个结构体`Checkerboard`,它构建了西洋跳棋游戏的棋盘: -![西洋跳棋棋盘](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/checkersBoard_2x.png) +![西洋跳棋棋盘](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/checkersBoard_2x.png) -西洋跳棋游戏在一副黑白格交替的 10x10 的棋盘中进行。为了呈现这副游戏棋盘,`Checkerboard`结构体定义了一个属性`boardColors`,它是一个包含 100 个布尔值的数组。数组中的某元素布尔值为`true`表示对应的是一个黑格,布尔值为`false`表示对应的是一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。 +西洋跳棋游戏在一副黑白格交替的`10x10`的棋盘中进行。为了呈现这副游戏棋盘,`Checkerboard`结构体定义了一个属性`boardColors`,它是一个包含`100`个`Bool`值的数组。在数组中,值为`true`的元素表示一个黑格,值为`false`的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。 -`boardColor`数组是通过一个闭包来初始化和组装颜色值的: +`boardColor`数组是通过一个闭包来初始化并设置颜色值的: ```swift struct Checkerboard { @@ -1036,19 +1043,19 @@ struct Checkerboard { isBlack = !isBlack } return temporaryBoard - }() + }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { return boardColors[(row * 10) + column] } } ``` -每当一个新的`Checkerboard`实例创建时,对应的赋值闭包会执行,一系列颜色值会被计算出来作为默认值赋值给`boardColors`。上面例子中描述的闭包将计算出棋盘中每个格子合适的颜色,将这些颜色值保存到一个临时数组`temporaryBoard`中,并在构建完成时将此数组作为闭包返回值返回。这个返回的值将保存到`boardColors`中,并可以通`squareIsBlackAtRow`这个工具函数来查询。 +每当一个新的`Checkerboard`实例被创建时,赋值闭包会被执行,`boardColors`的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每个格子对应的颜色,并将这些值保存到一个临时数组`temporaryBoard`中,最后在构建完成时将此数组作为闭包返回值返回。这个返回的数组会保存到`boardColors`中,并可以通过工具函数`squareIsBlackAtRow`来查询: ```swift let board = Checkerboard() print(board.squareIsBlackAtRow(0, column: 1)) -// 输出 "true" +// 打印 "true" print(board.squareIsBlackAtRow(9, column: 9)) -// 输出 "false" +// 打印 "false" ```