Merge pull request #420 from AlanMelody/develop

04_Collection Types && 05_Control Flow翻译完成
This commit is contained in:
梁杰
2015-07-20 21:06:26 +08:00
2 changed files with 406 additions and 338 deletions

View File

@ -1,42 +1,99 @@
> 翻译:[zqp](https://github.com/zqp) > 翻译:[zqp](https://github.com/zqp), [JackAlan](https://github.com/AlanMelody)
> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) > 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin), [JackAlan](https://github.com/AlanMelody)
# 集合类型 (Collection Types) # 集合类型 (Collection Types)
----------------- -----------------
本页包含内容: 本页包含内容:
- [集合的可变性Mutability of Collections](#mutability_of_collections)
- [数组Arrays](#arrays) - [数组Arrays](#arrays)
- [集合Sets](#sets) - [集合Sets](#sets)
- [字典Dictionaries](#dictionaries) - [字典Dictionaries](#dictionaries)
- [集合的可变性Mutability of Collections](#mutability_of_collections)
Swift 语言提供经典的数组和字典两种集合类型来存储集合数据。数组用来按顺序存储相同类型的数据。字典虽然无序存储相同类型数据值但是需要由独有的标识符引用和寻址(就是键值对 Swift 语言提供`Arrays``Sets``Dictionaries`三种基本的集合类型来存储集合数据。数组是有序数据的集。集合是无序无重复数据的集。字典是无序的键值对的集
Swift 语言里的数组和字典中存储的数据值类型必须明确。 这意味着我们不能把不正确的数据类型插入其中。 同时这也说明我们完全可以对获取出的值类型非常自信。 Swift 对显式类型集合的使用确保了我们的代码对工作所需要的类型非常清楚,也让我们在开发中可以早早地找到任何的类型不匹配错误。 ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/CollectionTypes_intro_2x.png)
Swift 语言中的`Arrays``Sets``Dictionaries`中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信。
> 注意: > 注意:
Swift 的数组结构在被声明成常量和变量或者被传入函数与方法中时会相对于其他类型展现出不同的特性。 获取更多信息请参见[集合的可变性](#mutability_of_collections)与[集合在赋值和复制中的行为](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)章节。 Swift 的`Arrays``Sets``Dictionaries`类型被实现为泛型集合。更多关于泛型类型和集合,参见 [泛型](23_Generics.html)章节。
<a name="mutability_of_collections"></a>
## 集合的可变性
如果创建一个`Arrays``Sets``Dictionaries`并且把它分配成一个变量,这个集合将会是可变的。这意味着我们可以在创建之后添加更多或移除已存在的数据项来改变这个集合的大小。如果我们把`Arrays``Sets``Dictionaries`分配成常量,那么它就是不可变的,它的大小不能被改变。
> 注意:
在我们不需要改变集合大小的时候创建不可变集合是很好的习惯。如此 Swift 编译器可以优化我们创建的集合。
<a name="arrays"></a> <a name="arrays"></a>
## 数组 ## 数组(Arrays)
数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
Swift 数组特定于它所存储元素的类型。这与 Objective-C 的 NSArray 和 NSMutableArray 不同,这两个类可以存储任意类型的对象,并且不提供所返回对象的任何特别信息。在 Swift 中,数据值在被存储进入某个数组之前类型必须明确,方法是通过显式的类型标注或类型推断,而且不是必须是`class`类型。例如: 如果我们创建了一个`Int`值类型的数组,我们不能往其中插入任何不是`Int`类型的数据。 Swift 中的数组是类型安全的,并且它们中包含的类型必须明确。 > 注意:
Swift 的`Array`类型被桥接到`Foundation`中的`NSArray`类。
更多关于在`Foundation``Cocoa`中使用`Array`的信息,参见 *Using Swift with Cocoa and Obejective-C* 一书。
<a name="array_type_shorthand_syntax"></a> <a name="array_type_shorthand_syntax"></a>
### 数组的简单语法 ### 数组的简单语法
写 Swift 数组应该遵循像`Array<SomeType>`这样的形式,其中`SomeType`是这个数组中唯一允许存在的数据类型。 我们也可以使用像`[SomeType]`这样的简单语法。 尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 写 Swift 数组应该遵循像`Array<T>`这样的形式,其中`T`是这个数组中唯一允许存在的数据类型。我们也可以使用像`[T]`这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。
<a name="array_literals"></a> <a name="creating_an_empty_array"></a>
### 数组构造语句 ###创建一个空数组
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
```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]类型的。
```
<a name="creating_an_array_with_a_default_value"></a>
###创建一个带有默认值的数组
Swift 中的`Array`类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeatedValue`)传入数组构造函数:
```swift
var threeDoubles = [Double](count: 3, repeatedValue:0.0)
// threeDoubles 是一种 [Double]数组, 等于 [0.0, 0.0, 0.0]
```
<a name="creating_an_array_by_adding_two_arrays_together"></a>
###通过两个数组相加创建一个数组
我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:
```swift
var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
// anotherThreeDoubles is inferred as [Double], and equals [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推断为 [Double], 等于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
```
<a name="creating_an_array_with_an_array_literals"></a>
### 用字面量构造数组
我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值。 我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值。
`[value 1, value 2, value 3]` `[value 1, value 2, value 3]`
下面这个例子创建了一个叫做`shoppingList`并且存储字符串的数组: 下面这个例子创建了一个叫做`shoppingList`并且存储`String`的数组:
```swift ```swift
var shoppingList: [String] = ["Eggs", "Milk"] var shoppingList: [String] = ["Eggs", "Milk"]
@ -65,7 +122,7 @@ var shoppingList = ["Eggs", "Milk"]
还可以使用数组的只读属性`count`来获取数组中的数据项数量。 还可以使用数组的只读属性`count`来获取数组中的数据项数量。
```swift ```swift
println("The shopping list contains \(shoppingList.count) items.") print("The shopping list contains \(shoppingList.count) items.")
// 输出"The shopping list contains 2 items."这个数组有2个项 // 输出"The shopping list contains 2 items."这个数组有2个项
``` ```
@ -73,9 +130,9 @@ println("The shopping list contains \(shoppingList.count) items.")
```swift ```swift
if shoppingList.isEmpty { if shoppingList.isEmpty {
println("The shopping list is empty.") print("The shopping list is empty.")
} else { } else {
println("The shopping list is not empty.") print("The shopping list is not empty.")
} }
// 打印 "The shopping list is not empty."shoppinglist不是空的 // 打印 "The shopping list is not empty."shoppinglist不是空的
``` ```
@ -120,9 +177,10 @@ shoppingList[4...6] = ["Bananas", "Apples"]
``` ```
> 注意: > 注意:
>我们不能使用下标语法在数组尾部添加新项。如果我们试着用这种方法对索引越界的数据进行检索或者设置新值的操作,我们会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引 > 不可以用下表访问的形式去在数组尾部添加新项
调用数组的`insert(atIndex:)`方法来在某个具体索引值之前添加数据项:
调用数组的`insert(_:atIndex:)`方法来在某个具体索引值之前添加数据项:
```swift ```swift
shoppingList.insert("Maple Syrup", atIndex: 0) shoppingList.insert("Maple Syrup", atIndex: 0)
@ -140,6 +198,8 @@ let mapleSyrup = shoppingList.removeAtIndex(0)
// shoppingList 现在只有6项而且不包括Maple Syrup // shoppingList 现在只有6项而且不包括Maple Syrup
// mapleSyrup常量的值等于被移除数据项的值 "Maple Syrup" // mapleSyrup常量的值等于被移除数据项的值 "Maple Syrup"
``` ```
> 注意:
> 如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。
数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`0`的数据项的值再次等于`"Six eggs"`: 数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`0`的数据项的值再次等于`"Six eggs"`:
@ -148,7 +208,7 @@ firstItem = shoppingList[0]
// firstItem 现在等于 "Six eggs" // firstItem 现在等于 "Six eggs"
``` ```
如果我们只想把数组中的最后一项移除,可以使用`removeLast`方法而不是`removeAtIndex`方法来避免我们需要获取数组的`count`属性。就像后者一样,前者也会返回被移除的数据项: 如果我们只想把数组中的最后一项移除,可以使用`removeLast`方法而不是`removeAtIndex(_:)`方法来避免我们需要获取数组的`count`属性。就像后者一样,前者也会返回被移除的数据项:
```swift ```swift
let apples = shoppingList.removeLast() let apples = shoppingList.removeLast()
@ -164,7 +224,7 @@ let apples = shoppingList.removeLast()
```swift ```swift
for item in shoppingList { for item in shoppingList {
println(item) print(item)
} }
// Six eggs // Six eggs
// Milk // Milk
@ -173,11 +233,11 @@ for item in shoppingList {
// Bananas // Bananas
``` ```
如果我们同时需要每个数据项的值和索引值,可以使用全局`enumerate`函数来进行数组遍历。`enumerate`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: 如果我们同时需要每个数据项的值和索引值,可以使用`enumerate()`方法来进行数组遍历。`enumerate()`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
```swift ```swift
for (index, value) in enumerate(shoppingList) { for (index, value) in shoppingList.enumerate() {
println("Item \(String(index + 1)): \(value)") print("Item \(String(index + 1)): \(value)")
} }
// Item 1: Six eggs // Item 1: Six eggs
// Item 2: Milk // Item 2: Milk
@ -188,146 +248,122 @@ for (index, value) in enumerate(shoppingList) {
更多关于`for-in`循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。 更多关于`for-in`循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。
<a name="creating_and_initializing_an_array"></a>
### 创建并且构造一个数组
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
```swift
var someInts = [Int]()
println("someInts is of type [Int] with \(someInts.count) items。")
// 打印 "someInts is of type [Int] with 0 items。"someInts是0数据项的Int[]数组)
```
注意`someInts`被设置为一个`[Int]`构造函数的输出所以它的变量类型被定义为`[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 is inferred as [Double], and equals [2.5, 2.5, 2.5]
```
最后,我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:
```swift
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推断为 [Double], 等于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
```
<a name="sets"></a> <a name="sets"></a>
## 集合 ## 集合
集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以把集合当做是数组另一形式。 集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以把集合当做是数组另一形式。
> 注意: > 注意:
> Swift的<code>Set</code>类型被桥接到<code>Fundation</code>中的<code>NSSet</code>类 > Swift的`Set`类型被桥接到`Fundation`中的`NSSet`类。
> 关于使用<code>Fundation</code>和<code>Cocoa</code>中集合的知识,请看<a link="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216">Swift与Cocoa和Objective-C使用</a> > 关于使用`Fundation`和`Cocoa`中`Set`的知识,请看 *Using Swift with Cocoa and Objective-C*。
<a name="hash_values_for_set_types"></a>
#### Set类型的哈希值
为了存储在集合中,该类型必须是可哈希化的-也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是```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)
<a name="set_type_syntax"></a> <a name="set_type_syntax"></a>
### Set类型语法 ### Set类型语法
Swift中的<code>Set</code>类型被写为```Set<SomeType>```,这里的```SomeType```表示```Set```中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 Swift中的`Set`类型被写为`Set<T>`, 这里的`T`表示`Set`中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
<a name="create_and_initializing_a_set"></a> <a name="creating_and_initalizing_an_empty_set"></a>
### 创建和构造一个Set ### 创建和构造一个空的Set
你可以通过构造器语法创建一个特定类型的空集合: 你可以通过构造器语法创建一个特定类型的空集合:
```swift ```swift
var letters = Set<Character>() var letters = Set<Character>()
println("letters is of type Set<Character> with \(letters.count) items.") print("letters is of type Set<Character> with \(letters.count) items.")
// 打印 "letters is of type Set<Character> with 0 items." // 打印 "letters is of type Set<Character> with 0 items."
``` ```
注意这里的```letters```变量的类型来自于构造器的类型,其为```Set<Character>```。 > 注意:
> 通过构造器,这里的`letters`变量的类型被推断为`Set<Character>`。
外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,可以通过一个空的数组字面量创建一个空的```Set``` 外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的`Set`
```swift ```swift
letters.insert("a") letters.insert("a")
// letters现在含有1个Character类型的值 // letters 现在含有1个Character类型的值
letters = [] letters = []
// letters现在是一个空的Set, 但是它依然是Set<Character>类型 // letters 现在是一个空的Set, 但是它依然是 Set<Character> 类型
``` ```
<a name="sets_with_arrays_literals"></a> <a name="creating_a_set_with_an_array_literal"></a>
### 集合与数组字面量 ### 数组字面量创建集合
你可以使用一个数组字面量来构造一个集合,并且可以使用简化形式写一个或者多个值作为集合元素。 你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。
下面的例子创建一个称之为```favoriteGenres```的集合来存储```String```类型的值: 下面的例子创建一个称之为`favoriteGenres`的集合来存储`String`类型的值:
```swift ```swift
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"] var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres被构造成含有三个初始值的集合 // favoriteGenres被构造成含有三个初始值的集合
``` ```
这个```favoriteGenres```变量被声明为“一个```String```值的集合”,写为```Set<String>```。由于这个特定的集合含有指定```String```类型的值,所以它只允许存储```String```类型值。这里的```favoriteGenres```变量有三个```String```类型的初始值("```Rock```","```Classical```"和"```Hip hop```"),并以数组字面量的方式出现。 这个`favoriteGenres`变量被声明为“一个`String`值的集合”,写为`Set<String>`。由于这个特定的集合含有指定`String`类型的值,所以它只允许存储`String`类型值。这里的`favoriteGenres`变量有三个`String`类型的初始值("`Rock`","`Classical`"和"`Hip hop`")并以数组字面量的方式出现。
> 注意: > 注意:
> ```favoriteGenres```被声明为一个变量(拥有```var```标示符)而不是一个常量(拥有```let```标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 > `favoriteGenres`被声明为一个变量(拥有`var`标示符)而不是一个常量(拥有`let`标示符),因为它里面的元素将会在下面的例子中被增加或者移除。
一个```Set```类型不能从数组中字面量中独立地被推断出来,因此```Set```类型必须显式声明。然而由于Swift的类型推导功能如果你想使用一个数组字面量构造一个Set并且该数组字面量中的所有元素类型相同那么你无须写出```Set```的具体类型。```favoriteGenres```的构造形式可以采用简化的方式代替: 一个`Set`类型不能从数组中字面量中独立地被推断出来,因此`Set`类型必须显式声明。然而由于Swift的类型推导功能如果你想使用一个数组字面量构造一个Set并且该数组字面量中的所有元素类型相同那么你无须写出`Set`的具体类型。`favoriteGenres`的构造形式可以采用简化的方式代替:
```swift ```swift
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
``` ```
由于数组字面量中的所有元素类型相同Swift可以推断出```Set<String>```作为```favoriteGenres```变量的正确类型。 由于数组字面量中的所有元素类型相同Swift可以推断出`Set<String>`作为`favoriteGenres`变量的正确类型。
<a name="accesing_and_modifying_a_set"></a> <a name="accesing_and_modifying_a_set"></a>
### 访问和修改一个Set ### 访问和修改一个Set
你可以通过```Set```的属性和方法来访问和修改一个```Set```. 你可以通过`Set`的属性和方法来访问和修改一个`Set`.
为了找出一个```Set```中元素的数量,可以使用其只读属性```count```: 为了找出一个`Set`中元素的数量,可以使用其只读属性`count`:
```swift ```swift
println("I have \(favoriteGenres.count) favorite music genres.") print("I have \(favoriteGenres.count) favorite music genres.")
// 打印 ""I have 3 favorite music genres."" // 打印 "I have 3 favorite music genres."
``` ```
使用布尔属性```isEmpty```作为一个缩写形式去检查```count```属性是否为```0```: 使用布尔属性`isEmpty`作为一个缩写形式去检查`count`属性是否为`0`:
```swift ```swift
if favoriteGenres.isEmpty { if favoriteGenres.isEmpty {
println("As far as music goes, I'm not picky.") print("As far as music goes, I'm not picky.")
} else { } else {
println("I have particular music preferences.") print("I have particular music preferences.")
} }
// 打印 "I have particular music preferences." // 打印 "I have particular music preferences."
``` ```
你可以通过调用```Set```的``` insert(_:) ```方法添加一个新元素: 你可以通过调用`Set`的` insert(_:) `方法添加一个新元素:
```swift ```swift
favoriteGenres.insert("Jazz") favoriteGenres.insert("Jazz")
// favoriteGenres 现在包含4个元素 // favoriteGenres 现在包含4个元素
``` ```
你可以通过调用```Set```的```remove(_:)```方法去删除一个元素,如果该值是该```Set```的一个元素则删除该元素并且返回被删除的元素值,否认如果该```Set```不包含该值,则返回```nil```。另外,```Set```中的所有元素可以通过它的```removeAll()```方法删除。 你可以通过调用`Set`的`remove(_:)`方法去删除一个元素,如果该值是该`Set`的一个元素则删除该元素并且返回被删除的元素值,否认如果该`Set`不包含该值,则返回`nil`。另外,`Set`中的所有元素可以通过它的`removeAll()`方法删除。
```swift ```swift
if let removedGenre = favoriteGenres.remove("Rock") { if let removedGenre = favoriteGenres.remove("Rock") {
println("\(removedValue)? I'm over it.") print("\(removedValue)? I'm over it.")
} else { } else {
println("I never much cared for that.") print("I never much cared for that.")
} }
// 打印 "Rock? I'm over it." // 打印 "Rock? I'm over it."
``` ```
@ -336,9 +372,9 @@ if let removedGenre = favoriteGenres.remove("Rock") {
```swift ```swift
if favoriteGenres.contains("Funk") { if favoriteGenres.contains("Funk") {
println("I get up on the good foot.") print("I get up on the good foot.")
} else { } else {
println("It's too funky in here.") print("It's too funky in here.")
} }
// 打印 "It's too funky in here." // 打印 "It's too funky in here."
``` ```
@ -350,20 +386,20 @@ if favoriteGenres.contains("Funk") {
```swift ```swift
for genre in favoriteGenres { for genre in favoriteGenres {
println("\(genre)") print("\(genre)")
} }
// Classical // Classical
// Jazz // Jazz
// Hip hop // Hip hop
``` ```
更多关于```for-in```循环信息,请看<a link="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID121">For循环</a> 更多关于`for-in`循环信息,参见[For循环](05_Control_Flow.html#for_loops)。
Swift的```Set```类型没有确定的顺序,为了按照特定顺序来遍历一个```Set```中值可以使用全局```sorted```函数,它将根据提供的序列返回一个排序的集合. Swift 的`Set`类型没有确定的顺序,为了按照特定顺序来遍历一个`Set`中值可以使用`sorted()`方法,它将根据提供的序列返回一个排序的集合.
```swift ```swift
-> for genre in sorted(favoriteGenres) { for genre in sorted(favoriteGenres) {
println("\(genre)") print("\(genre)")
} }
// prints "Classical" // prints "Classical"
// prints "Hip hop" // prints "Hip hop"
@ -373,19 +409,19 @@ Swift的```Set```类型没有确定的顺序,为了按照特定顺序来遍历
<a name="performing_set_operations"></a> <a name="performing_set_operations"></a>
### 完成集合操作 ### 完成集合操作
你可以高效的完成```Set```的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 你可以高效的完成`Set`的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
<a name="constructing_sets"></a> <a name="fundamental_set_operations"></a>
#### 构造集合 #### 基本集合操作
下面的插图描述了两个集合-```a```和```b```-以及通过阴影部分的区域显示集合各种操作的结果。 下面的插图描述了两个集合-`a`和`b`-以及通过阴影部分的区域显示集合各种操作的结果。
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png) ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png)
* 使用```union(_:)```方法根据两个集合的值创建一个新的集合。 * 使用`intersect(_:)`方法根据两个集合中都包含的值创建一个新的集合。
* 使用```subtract(_:)```方法根据不在该集合中的值创建一个新的集合。 * 使用`exclusiveOr(_:)`方法根据值在一个集合中但不在两个集合中的值创建一个新的集合。
* 使用```intersect(_:)```方法根据两个集合中都包含的值创建一个新的集合。 * 使用`union(_:)`方法根据两个集合的值创建一个新的集合。
* 使用```exclusiveOr(_:)```方法根据值在一个集合中但不在两个集合中的值创建一个新的集合。 * 使用`subtract(_:)`方法根据不在该集合中的值创建一个新的集合。
```swift ```swift
let oddDigits: Set = [1, 3, 5, 7, 9] let oddDigits: Set = [1, 3, 5, 7, 9]
@ -401,18 +437,18 @@ sorted(oddDigits.exclusiveOr(singleDigitPrimeNumbers))
// [1, 2, 9] // [1, 2, 9]
``` ```
<a name="comparing_sets"></a> <a name="set_membership_and_equality"></a>
#### 集合比较 #### 集合成员关系和相等
下面的插图描述了三个集合-```a```,```b```和```c```,以及通过悬浮区域表述集合间共享的元素。Set ```a```是Set ```b```的父集合,因为```a```包含了```b```中所有的元素相反的Set ```b```是```a```的子集合,因为属于```b```的元素也被```a```包含。Set ```b```和Set ```c```彼此不关联,因为它们之间没有共同的元素。 下面的插图描述了三个集合-`a`,`b`和`c`,以及通过悬浮区域表述集合间共享的元素。Set `a`是Set`b`的父集合,因为`a`包含了`b`中所有的元素相反的Set `b`是`a`的子集合,因为属于`b`的元素也被`a`包含。Set `b`和Set `c`彼此不关联,因为它们之间没有共同的元素。
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png) ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png)
* 使用“是否等”运算符(```==```)来判断两个集合是否包含相同的值。 * 使用“是否等”运算符(`==`)来判断两个集合是否包含全部相同的值。
* 使用```isSubsetOf(_:)```方法来判断一个集合中的值是否也被包含在另外一个集合中。 * 使用`isSubsetOf(_:)`方法来判断一个集合中的值是否也被包含在另外一个集合中。
* 使用```isSupersetOf(_:)```方法来判断一个集合中包含的值是另一个集合中所有的值。 * 使用`isSupersetOf(_:)`方法来判断一个集合中包含的值是另一个集合中所有的值。
* 使用```isStrictSubsetOf(_:)```或者```isStrictSupersetOf(_:)```方法来判断一个集合是否是另外一个集合的子集合或者父集合并且和特定集合不相等。 * 使用`isStrictSubsetOf(_:)`或者`isStrictSupersetOf(_:)`方法来判断一个集合是否是另外一个集合的子集合或者父集合并且和特定集合不相等。
* 使用```isDisjointWith(_:)```方法来判断两个结合是否不含有相同的值。 * 使用`isDisjointWith(_:)`方法来判断两个结合是否不含有相同的值。
```swift ```swift
let houseAnimals: Set = ["🐶", "🐱"] let houseAnimals: Set = ["🐶", "🐱"]
@ -426,22 +462,6 @@ farmAnimals.isDisjointWith(cityAnimals)
// true // true
``` ```
<a name="hash_values_for_set_types"></a>
#### Set类型的哈希值
为了存储在集合中,该类型必须是可哈希化的-也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是```Int```类型的,它和其他的对象相同,其被用来比较相等与否,比如```a==b```,它遵循的是``` a.hashValue == b.hashValue```。
Swift的所有基本类型(比如```String```,```Int```,```Double```和```Bool```)默认都是可哈希化的,它可以作为集合的值或者字典的键值类型。没有关联值的枚举成员值(在[枚举部分](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145)有讲述)默认也是可哈希化的。
>注意
>你可以使用你自定义的类型作为集合的值或者是字典的键值类型但你需要使你的自定义类型服从Swift标准库中的```Hashable```协议。服从```Hashable```协议的类型需要提供一个类型为```Int```的取值访问器属性```hashValue```。这个由类型的```hashValue```返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。
>因为```hashable```协议服从于```Equatable```协议,所以遵循该协议的类型也必须提供一个"是否等"运算符(```==```)的实现。这个```Equatable```协议需要任何遵循的```==```的实现都是一种相等的关系。也就是说,对于```a,b,c```三个值来说,```==```的实现必须满足下面三种情况:
* ```a==a```(自反性)
* ```a==b```意味着```b==a```(对称性)
* ```a==b&&b==c```意味着```a==c```(传递性)
关于协议遵循的更多信息,请看[协议](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267)
<a name="dictionaries"></a> <a name="dictionaries"></a>
@ -449,18 +469,54 @@ Swift的所有基本类型(比如```String```,```Int```,```Double```和```Bool``
字典是一种存储多个相同类型的值的容器。每个值value都关联唯一的键key键作为字典中的这个值数据的标识符。和数组中的数据项不同字典中的数据项并没有具体顺序。我们在需要通过标识符访问数据的时候使用字典这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 字典是一种存储多个相同类型的值的容器。每个值value都关联唯一的键key键作为字典中的这个值数据的标识符。和数组中的数据项不同字典中的数据项并没有具体顺序。我们在需要通过标识符访问数据的时候使用字典这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。
> 注意:
> Swiftly 的`Dictionary` 类型被桥接到Foundation的`NSDictionary`类。
> 更多关于在`Foundation`和`Cocoa`中使用`Dictionary`类型的信息,参见 *Using Swift with Cocoa and Obejective-C* 一书。
<a name="dictionary_type_shorthand_syntax"></a>
## 字典类型快捷语法
<!--more
Swift 的字典使用时需要具体规定可以存储键和值类型。不同于 Objective-C 的`NSDictionary`和`NSMutableDictionary` 类可以使用任何类型的对象来作键和值并且不提供任何关于这些对象的本质信息。在 Swift 中,在某个特定字典中可以存储的键和值必须提前定义清楚,方法是通过显性类型标注或者类型推断。 Swift 的字典使用时需要具体规定可以存储键和值类型。不同于 Objective-C 的`NSDictionary`和`NSMutableDictionary` 类可以使用任何类型的对象来作键和值并且不提供任何关于这些对象的本质信息。在 Swift 中,在某个特定字典中可以存储的键和值必须提前定义清楚,方法是通过显性类型标注或者类型推断。
-->
Swift 的字典使用`Dictionary<Key, Value>`定义,其中`Key`是字典中键的数据类型,`Value`是字典中对应于这些键所存储值的数据类型。
Swift 的字典使用`Dictionary<KeyType, ValueType>`定义,其中`KeyType`是字典中键的数据类型,`ValueType`是字典中对应于这些键所存储值的数据类型。 > 注意:
> 一个字典的`Key`类型必须遵循`Hashable`协议,就像`Set`的值类型。
我们也可以用`[Key: Value]`这样快捷的形式去创建一个字典类型。虽然这俩种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。
<!--more
`KeyType`的唯一限制就是可哈希的,这样可以保证它是独一无二的,所有的 Swift 基本类型(例如`String``Int` `Double`和`Bool`)都是默认可哈希的,并且所有这些类型都可以在字典中当做键使用。未关联值的枚举成员(参见[枚举](08_Enumerations.html))也是默认可哈希的。 `KeyType`的唯一限制就是可哈希的,这样可以保证它是独一无二的,所有的 Swift 基本类型(例如`String``Int` `Double`和`Bool`)都是默认可哈希的,并且所有这些类型都可以在字典中当做键使用。未关联值的枚举成员(参见[枚举](08_Enumerations.html))也是默认可哈希的。
-->
<a name="creating_an_empty_dictionary"></a>
### 创建一个空字典
我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典:
```swift
var namesOfIntegers = [Int: String]
// namesOfIntegers 是一个空的 [Int: String] 字典
```
这个例子创建了一个`[Int: String]`类型的空字典来储存英语对整数的命名。它的键是`Int`型,值是`String`型。
如果上下文已经提供了信息类型,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号):
```swift
namesOfIntegers[16] = "sixteen"
// namesOfIntegers 现在包含一个键值对
namesOfIntegers = [:]
// namesOfIntegers 又成为了一个 Int, String类型的空字典
```
<a name="creating_a_dictionary_with_a_dictionary_literal"></a>
<a name="dictionary_literals"></a>
## 字典字面量 ## 字典字面量
我们可以使用字典字面量来构造字典,它们和我们刚才介绍过的数组字面量拥有相似语法。一个字典字面量是一个定义拥有一个或多个键值对的字典集合的简单语句 我们可以使用字典字面量来构造字典,和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将写一个或多个键值对作`Dictionary`集合的快捷途径
一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含并且由逗号分割: 一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含由逗号分割:
```swift ```swift
[key 1: value 1, key 2: value 2, key 3: value 3] [key 1: value 1, key 2: value 2, key 3: value 3]
@ -469,48 +525,51 @@ Swift 的字典使用`Dictionary<KeyType, ValueType>`定义,其中`KeyType`是
下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: 下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称:
```swift ```swift
var airports: [String:String] = ["TYO": "Tokyo", "DUB": "Dublin"] var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
``` ```
`airports`字典被定义为一种 `[String: String]`,它意味着这个字典的键和值都是`String`类型。 `airports`字典被声明为一种 `[String: String]`类型,这意味着这个字典的键和值都是`String`类型。
> 注意: > 注意:
> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 > `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。
`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`TYO`,值是`Tokyo`。第二对的键是`DUB`,值是`Dublin`。 `airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`YYZ`,值是`Toronto Pearson`。第二对的键是`DUB`,值是`Dublin`。
这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量是构造两个初始数据项的`airport`字典。 这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的`airport`字典。
和数组一样,如果我们使用字面量构造字典就不用把类型定义清楚。`airports`的也可以用这种方法简短定义: 和数组一样,我们用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。
`airports`的也可以用这种方法简短定义:
```swift ```swift
var airports = ["TYO": "Tokyo", "DUB": "Dublin"] var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
``` ```
因为这个语句中所有的键和值都分别是相同的数据类型Swift 可以推断出`Dictionary<String, String>`是`airports`字典的正确类型。 因为这个语句中所有的键和值都各自拥有相同的数据类型Swift 可以推断出`Dictionary<String, String>`是`airports`字典的正确类型。
<a name="accessing_and_modifying_a_dictionary"></a> <a name="accessing_and_modifying_a_dictionary"></a>
### 读取和修改字典 ### 读取和修改字典
我们可以通过字典的方法和属性来读取和修改字典,或者使用下标语法。和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: 我们可以通过字典的方法和属性来读取和修改字典,或者通过使用下标语法。
和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量:
```swift ```swift
println("The dictionary of airports contains \(airports.count) items.") print("The dictionary of airports contains \(airports.count) items.")
// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) // 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项)
``` ```
可以使用布尔属性`isEmpty`来快捷的检查字典的`count`属性是否等于0。 使用布尔属性`isEmpty`来快捷的检查字典的`count`属性是否等于0。
```swift ```swift
if airports.isEmpty { if airports.isEmpty {
println("The airports dictionary is empty.") print("The airports dictionary is empty.")
} else { } else {
println("The airports dictionary is not empty.") print("The airports dictionary is not empty.")
} }
// 打印 "The airports dictionary is not empty.(这个字典不为空)" // 打印 "The airports dictionary is not empty."
``` ```
我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个合适类型的 key 作为下标索引,并且分配新的合适类型的值: 我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个合适类型的键值作为下标索引,并且分配新的合适类型的值:
```swift ```swift
airports["LHR"] = "London" airports["LHR"] = "London"
@ -524,26 +583,28 @@ airports["LHR"] = "London Heathrow"
// "LHR"对应的值 被改为 "London Heathrow // "LHR"对应的值 被改为 "London Heathrow
``` ```
作为另一种下标方法,字典的`updateValue(forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的示例,`updateValue(forKey:)`方法在这个键不存在对应值的时候设置值或者在存在时更新已存在的值。和上面的下标方法不一样,这个方法返回更新值之前的原值。这样方便我们检查更新是否成功。 作为另一种下标方法,字典的`updateValue(_:forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)`方法在这个键不存在对应值的时候设置值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)`这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。
`updateValue(forKey:)`函数会返回包含一个字典值类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。如果值存在,则这个可选值值等于被替换的值,否则将会是`nil`。 `updateValue(_:forKey:)`函数会返回包含一个字典值类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。
如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是`nil`。
```swift ```swift
if let oldValue = airports.updateValue("Dublin Internation", forKey: "DUB") { if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
println("The old value for DUB was \(oldValue).") print("The old value for DUB was \(oldValue).")
} }
// 输出 "The old value for DUB was Dublin."DUB原值是dublin // 输出 "The old value for DUB was Dublin."
``` ```
我们也可以使用下标语法来在字典中检索特定键对应的值。由于使用一个没有值的键这种情况是有可能发生的,可选类型返回这个存在的相关值,否则返回`nil` 我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回一个字典值类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则返回`nil`
```swift ```swift
if let airportName = airports["DUB"] { if let airportName = airports["DUB"] {
println("The name of the airport is \(airportName).") print("The name of the airport is \(airportName).")
} else { } else {
println("That airport is not in the airports dictionary.") print("That airport is not in the airports dictionary.")
} }
// 打印 "The name of the airport is Dublin Internation."(机场的名字是都柏林国际) // 打印 "The name of the airport is Dublin Airport."
``` ```
我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: 我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对:
@ -555,45 +616,45 @@ airports["APL"] = nil
// APL现在被移除了 // APL现在被移除了
``` ```
外,`removeValueForKey`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的value或者在没有值的情况下返回`nil` 外,`removeValueForKey(_:)`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的或者在没有值的情况下返回`nil`
```swift ```swift
if let removedValue = airports.removeValueForKey("DUB") { if let removedValue = airports.removeValueForKey("DUB") {
println("The removed airport's name is \(removedValue).") print("The removed airport's name is \(removedValue).")
} else { } else {
println("The airports dictionary does not contain a value for DUB.") print("The airports dictionary does not contain a value for DUB.")
} }
// prints "The removed airport's name is Dublin International." // prints "The removed airport's name is Dublin Airport."
``` ```
<a name="iterating_over_a_dictionary"></a> <a name="iterating_over_a_dictionary"></a>
### 字典遍历 ### 字典遍历
我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: 我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组:
```swift ```swift
for (airportCode, airportName) in airports { for (airportCode, airportName) in airports {
println("\(airportCode): \(airportName)") print("\(airportCode): \(airportName)")
} }
// TYO: Tokyo // YYZ: Toronto Pearson
// LHR: London Heathrow // LHR: London Heathrow
``` ```
`for-in`循环参见[For 循环](05_Control_Flow.html#for_loops)。 更多关于`for-in`循环的信息,参见[For 循环](05_Control_Flow.html#for_loops)。
我们也可以通过访问它的`keys`或者`values`属性(都是可遍历集合)检索一个字典的键或者值 通过访问`keys`或者`values`属性,我们也可以遍历字典的键或者值
```swift ```swift
for airportCode in airports.keys { for airportCode in airports.keys {
println("Airport code: \(airportCode)") print("Airport code: \(airportCode)")
} }
// Airport code: TYO // Airport code: YYZ
// Airport code: LHR // Airport code: LHR
for airportName in airports.values { for airportName in airports.values {
println("Airport name: \(airportName)") print("Airport name: \(airportName)")
} }
// Airport name: Tokyo // Airport name: Toronto Pearson
// Airport name: London Heathrow // Airport name: London Heathrow
``` ```
@ -601,47 +662,10 @@ for airportName in airports.values {
```swift ```swift
let airportCodes = Array(airports.keys) let airportCodes = Array(airports.keys)
// airportCodes is ["TYO", "LHR"] // airportCodes is ["YYZ", "LHR"]
let airportNames = Array(airports.values) let airportNames = Array(airports.values)
// airportNames is ["Tokyo", "London Heathrow"] // airportNames is ["Toronto Pearson", "London Heathrow"]
``` ```
> 注意: Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的`keys`或`values`属性使用`sort()`方法。
> Swift 的字典类型是无序集合类型。其中字典键,值,键值对在遍历的时候会重新排列,而且其中顺序是不固定的。
<a name="creating_an_empty_dictionary"></a>
### 创建一个空字典
我们可以像数组一样使用构造语法创建一个空字典:
```swift
var namesOfIntegers = Dictionary<Int, String>()
// namesOfIntegers 是一个空的 Dictionary<Int, String>
```
这个例子创建了一个`Int, String`类型的空字典来储存英语对整数的命名。它的键是`Int`型,值是`String`型。
如果上下文已经提供了信息类型,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号):
```swift
namesOfIntegers[16] = "sixteen"
// namesOfIntegers 现在包含一个键值对
namesOfIntegers = [:]
// namesOfIntegers 又成为了一个 Int, String类型的空字典
```
> 注意:
> 在后台Swift 的数组和字典都是由泛型集合来实现的,想了解更多泛型和集合信息请参见[泛型](22_Generics.html)。
<a name="mutability_of_collections"></a>
## 集合的可变性
数组和字典都是在单个集合中存储可变值。如果我们创建一个数组或者字典并且把它分配成一个变量,这个集合将会是可变的。这意味着我们可以在创建之后添加更多或移除已存在的数据项来改变这个集合的大小。与此相反,如果我们把数组或字典分配成常量,那么它就是不可变的,它的大小不能被改变。
相反,如果你给常量赋值一个数组、集合或者字典,那它就是不可变的,大小和内容都不能修改。
Swift 数组的可变性行为同时影响了数组实例如何被分配和修改,想获取更多信息,请参见[集合在赋值和复制中的行为](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)。
> 注意:
> 在我们不需要改变数组大小的时候创建不可变数组是很好的习惯。如此 Swift 编译器可以优化我们创建的集合。

View File

@ -1,5 +1,5 @@
> 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao) > 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao), [JackAlan](https://github.com/AlanMelody)
> 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai) > 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai), [JackAlan](https://github.com/AlanMelody)
# 控制流 # 控制流
----------------- -----------------
@ -11,19 +11,19 @@
- [条件语句](#conditional_statement) - [条件语句](#conditional_statement)
- [控制转移语句Control Transfer Statements](#control_transfer_statements) - [控制转移语句Control Transfer Statements](#control_transfer_statements)
Swift提供了类似 C 语言的流程控制结构,包括可以多次执行任务的`for``while`循环,基于特定条件选择执行不同代码分支的`if``switch`语句,还有控制流程跳转到其他代码的`break``continue`语句。 Swift提供了类似 C 语言的流程控制结构,包括可以多次执行任务的`for``while`循环,基于特定条件选择执行不同代码分支的`if``guard``switch`语句,还有控制流程跳转到其他代码的`break``continue`语句。
除了 C 语言里面传统的 for 条件递增(`for-condition-increment`循环Swift 还增加了`for-in`循环用来更简单地遍历数组array字典dictionary区间range字符串string和其他序列类型。 除了 C 语言里面传统的 for 循环Swift 还增加了`for-in`循环用来更简单地遍历数组array字典dictionary区间range字符串string和其他序列类型。
Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿fallthrough至下一个 caseSwift 无需写`break`,所以不会发生这种贯穿fallthrough的情况。case 还可以匹配更多的类型模式包括区间匹配range matching元组tuple和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。 Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 caseSwift 无需写`break`所以不会发生这种贯穿的情况。case 还可以匹配更多的类型模式包括区间匹配range matching元组tuple和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。
<a name="for_loops"></a> <a name="for_loops"></a>
## For 循环 ## For 循环
`for`循环来按照指定的次数多次执行一系列语句。Swift 提供两种`for`循环形式 Swift 提供两种`for`循环形式以来按照指定的次数多次执行一系列语句:
* `for-in`用来遍历一个区间range序列sequence集合collection系列progression里面所有的元素执行一系列语句。 * `for-in`循环对一个集合里面的每个元素执行一系列语句。
* for条件递增(`for-condition-increment`)语句,用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。 * for循环,用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。
<a name="for_in"></a> <a name="for_in"></a>
### For-In ### For-In
@ -34,7 +34,7 @@ Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某
```swift ```swift
for index in 1...5 { for index in 1...5 {
println("\(index) times 5 is \(index * 5)") print("\(index) times 5 is \(index * 5)")
} }
// 1 times 5 is 5 // 1 times 5 is 5
// 2 times 5 is 10 // 2 times 5 is 10
@ -43,13 +43,10 @@ for index in 1...5 {
// 5 times 5 is 25 // 5 times 5 is 25
``` ```
例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1``5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`println`方法会再执行一次。整个过程会进行到闭区间结尾为止。 例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1``5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。
上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。 上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。
>注意:
`index`常量只存在于循环的生命周期里。如果你想在循环完成后访问`index`的值,又或者想让`index`成为一个变量而不是常量,你必须在循环之前自己进行声明。
如果你不需要知道区间内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问: 如果你不需要知道区间内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问:
```swift ```swift
@ -59,7 +56,7 @@ var answer = 1
for _ in 1...power { for _ in 1...power {
answer *= base answer *= base
} }
println("\(base) to the power of \(power) is \(answer)") print("\(base) to the power of \(power) is \(answer)")
// 输出 "3 to the power of 10 is 59049" // 输出 "3 to the power of 10 is 59049"
``` ```
@ -70,7 +67,7 @@ println("\(base) to the power of \(power) is \(answer)")
```swift ```swift
let names = ["Anna", "Alex", "Brian", "Jack"] let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names { for name in names {
println("Hello, \(name)!") print("Hello, \(name)!")
} }
// Hello, Anna! // Hello, Anna!
// Hello, Alex! // Hello, Alex!
@ -78,41 +75,27 @@ for name in names {
// Hello, Jack! // Hello, Jack!
``` ```
你也可以通过遍历一个字典来访问它的键值对key-value pairs。遍历字典时,字典的每项元素会以`(key, value)`元组的形式返回,你可以在`for-in`循环中使用显式的常量名称来解读`(key, value)`元组。下面的例子中字典的键key解读为常量`animalName`,字典的值会被解读为常量`legCount` 你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以`(key, value)`元组的形式返回,你可以在`for-in`循环中使用显式的常量名称来解读`(key, value)`元组。下面的例子中字典的键key解读为常量`animalName`,字典的值会被解读为常量`legCount`
```swift ```swift
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs { for (animalName, legCount) in numberOfLegs {
println("\(animalName)s have \(legCount) legs") print("\(animalName)s have \(legCount) legs")
} }
// spiders have 8 legs
// ants have 6 legs // ants have 6 legs
// cats have 4 legs // cats have 4 legs
// spiders have 8 legs
``` ```
字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](../chapter2/04_Collection_Types.html)。 字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](04_Collection_Types.html)。
除了数组和字典,你也可以使用`for-in`循环来遍历字符串中的字符(`Character` <a name="for"></a>
```swift
for character in "Hello" {
println(character)
}
// H
// e
// l
// l
// o
```
<a name="for_condition_increment"></a>
### For条件递增for-condition-increment
除了`for-in`循环Swift 提供使用条件判断和递增方法的标准 C 样式`for`循环: 除了`for-in`循环Swift 提供使用条件判断和递增方法的标准 C 样式`for`循环:
```swift ```swift
for var index = 0; index < 3; ++index { for var index = 0; index < 3; ++index {
println("index is \(index)") print("index is \(index)")
} }
// index is 0 // index is 0
// index is 1 // index is 1
@ -129,30 +112,23 @@ for var index = 0; index < 3; ++index {
这个循环执行流程如下: 这个循环执行流程如下:
1. 循环首次启动时,初始化表达式_initialization expression_被调用一次,用来初始化循环所需的所有常量和变量。 1. 循环首次启动时,初始化表达式被调用一次,用来初始化循环所需的所有常量和变量。
2. 条件表达式_condition expression_被调用,如果表达式调用结果为`false`,循环结束,继续执行`for`循环关闭大括号 2. 条件表达式被调用,如果表达式调用结果为`false`,循环结束,继续执行`for`循环关闭大括号
`}`)之后的代码。如果表达式调用结果为`true`,则会执行大括号内部的代码_statements_ `}`)之后的代码。如果表达式调用结果为`true`,则会执行大括号内部的代码。
3. 执行所有语句_statements_之后执行递增表达式_increment expression_。通常会增加或减少计数器的值,或者根据语句_statements_输出来修改某一个初始化的变量。当递增表达式运行完成后,重复执行第 2 步,条件表达式会再次执行。 3. 执行所有语句之后,执行递增表达式。通常会增加或减少计数器的值,或者根据语句输出来修改某一个初始化的变量。当递增表达式运行完成后,重复执行第 2 步,条件表达式会再次执行。
上述描述和循环格式等同于:
> `initialization`
> while `condition` {
> `statements`
> `increment`
> }
在初始化表达式中声明的常量和变量(比如`var index = 0`)只在`for`循环的生命周期里有效。如果想在循环结束后访问`index`的值,你必须要在循环生命周期开始前声明`index` 在初始化表达式中声明的常量和变量(比如`var index = 0`)只在`for`循环的生命周期里有效。如果想在循环结束后访问`index`的值,你必须要在循环生命周期开始前声明`index`
```swift ```swift
var index: Int var index: Int
for index = 0; index < 3; ++index { for index = 0; index < 3; ++index {
println("index is \(index)") print("index is \(index)")
} }
// index is 0 // index is 0
// index is 1 // index is 1
// index is 2 // index is 2
println("The loop statements were executed \(index) times") print("The loop statements were executed \(index) times")
// 输出 "The loop statements were executed 3 times // 输出 "The loop statements were executed 3 times
``` ```
@ -177,7 +153,7 @@ println("The loop statements were executed \(index) times")
> `statements` > `statements`
> } > }
下面的例子来玩一个叫做_蛇和梯子Snakes and Ladders_的小游戏,也叫做_滑道和梯子Chutes and Ladders_ 下面的例子来玩一个叫做蛇和梯子的小游戏,也叫做滑道和梯子:
![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) ![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png)
@ -219,7 +195,7 @@ while square < finalSquare {
square += board[square] square += board[square]
} }
} }
println("Game over!") print("Game over!")
``` ```
本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。`++diceRoll`调用完成_后_返回值等于`diceRoll`自增后的值。任何时候如果`diceRoll`的值等于7时就超过了骰子的最大值会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1``2``3``4``5``6``1``2` 本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。`++diceRoll`调用完成_后_返回值等于`diceRoll`自增后的值。任何时候如果`diceRoll`的值等于7时就超过了骰子的最大值会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1``2``3``4``5``6``1``2`
@ -233,18 +209,21 @@ println("Game over!")
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。 `while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。
<a name="do_while"></a> <a name="repeat_while"></a>
###Do-While ###Repeat-While
`while`循环的另外一种形式是`do-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false` `while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false`
下面是一般情况下 `do-while`循环的格式 > 注意
> Swift语言的`repeat-while`循环合其他语言中的`do-while`循环是类似的。
> do { 下面是一般情况下 `repeat-while`循环的格式:
> repeat {
> `statements` > `statements`
> } while `condition` > } while `condition`
还是蛇和梯子的游戏,使用`do-while`循环来替代`while`循环。`finalSquare``board``square``diceRoll`的值初始化同`while`循环一样: 还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare``board``square``diceRoll`的值初始化同`while`循环一样:
``` swift ``` swift
let finalSquare = 25 let finalSquare = 25
@ -255,12 +234,12 @@ var square = 0
var diceRoll = 0 var diceRoll = 0
``` ```
`do-while`的循环版本循环中_第一步_就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。 `repeat-while`的循环版本循环中_第一步_就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。
游戏开始时,玩家在第 0 个方格上,`board[0]`一直等于 0 不会有什么影响: 游戏开始时,玩家在第 0 个方格上,`board[0]`一直等于 0 不会有什么影响:
```swift ```swift
do { repeat {
// 顺着梯子爬上去或者顺着蛇滑下去 // 顺着梯子爬上去或者顺着蛇滑下去
square += board[square] square += board[square]
// 掷骰子 // 掷骰子
@ -268,12 +247,12 @@ do {
// 根据点数移动 // 根据点数移动
square += diceRoll square += diceRoll
} while square < finalSquare } while square < finalSquare
println("Game over!") print("Game over!")
``` ```
检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。 检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。
循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`do-while`表现得比`while`循环更好。`do-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断。 循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while`表现得比`while`循环更好。`repeat-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断。
<a name="conditional_statement"></a> <a name="conditional_statement"></a>
## 条件语句 ## 条件语句
@ -290,7 +269,7 @@ Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常
```swift ```swift
var temperatureInFahrenheit = 30 var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 { if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.") print("It's very cold. Consider wearing a scarf.")
} }
// 输出 "It's very cold. Consider wearing a scarf." // 输出 "It's very cold. Consider wearing a scarf."
``` ```
@ -302,9 +281,9 @@ if temperatureInFahrenheit <= 32 {
```swift ```swift
temperatureInFahrenheit = 40 temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 { if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.") print("It's very cold. Consider wearing a scarf.")
} else { } else {
println("It's not that cold. Wear a t-shirt.") print("It's not that cold. Wear a t-shirt.")
} }
// 输出 "It's not that cold. Wear a t-shirt." // 输出 "It's not that cold. Wear a t-shirt."
``` ```
@ -316,11 +295,11 @@ if temperatureInFahrenheit <= 32 {
```swift ```swift
temperatureInFahrenheit = 90 temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 { if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.") print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 { } else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.") print("It's really warm. Don't forget to wear sunscreen.")
} else { } else {
println("It's not that cold. Wear a t-shirt.") print("It's not that cold. Wear a t-shirt.")
} }
// 输出 "It's really warm. Don't forget to wear sunscreen." // 输出 "It's really warm. Don't forget to wear sunscreen."
``` ```
@ -330,11 +309,11 @@ if temperatureInFahrenheit <= 32 {
实际上,最后的`else`语句是可选的: 实际上,最后的`else`语句是可选的:
```swift ```swift
temperatureInFahrenheit = 72 temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 { if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.") print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 { } else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.") print("It's really warm. Don't forget to wear sunscreen.")
} }
``` ```
@ -361,7 +340,7 @@ if temperatureInFahrenheit <= 32 {
每一个 case 都是代码执行的一条分支,这与`if`语句类似。与之不同的是,`switch`语句会决定哪一条分支应该被执行。 每一个 case 都是代码执行的一条分支,这与`if`语句类似。与之不同的是,`switch`语句会决定哪一条分支应该被执行。
`switch`语句必须是_完备的_。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。 `switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。
下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符: 下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符:
@ -369,12 +348,12 @@ if temperatureInFahrenheit <= 32 {
let someCharacter: Character = "e" let someCharacter: Character = "e"
switch someCharacter { switch someCharacter {
case "a", "e", "i", "o", "u": case "a", "e", "i", "o", "u":
println("\(someCharacter) is a vowel") print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
println("\(someCharacter) is a consonant") print("\(someCharacter) is a consonant")
default: default:
println("\(someCharacter) is not a vowel or a consonant") print("\(someCharacter) is not a vowel or a consonant")
} }
// 输出 "e is a vowel" // 输出 "e is a vowel"
``` ```
@ -389,7 +368,7 @@ default:
与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。 与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。
> 注意: > 注意:
你依然可以在 case 分支中的代码执行完毕前跳出,详情请参[Switch 语句中的 break](#break_in_a_switch_statement)。 虽然在Swift中`break`不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用`break`跳出,详情请参[Switch 语句中的 break](#break_in_a_switch_statement)。
每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的: 每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
@ -398,9 +377,9 @@ let anotherCharacter: Character = "a"
switch anotherCharacter { switch anotherCharacter {
case "a": case "a":
case "A": case "A":
println("The letter A") print("The letter A")
default: default:
println("Not the letter A") print("Not the letter A")
} }
// this will report a compile-time error // this will report a compile-time error
``` ```
@ -418,39 +397,42 @@ default:
> 注意: > 注意:
如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿Fallthrough](#fallthrough)。 如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿Fallthrough](#fallthrough)。
<a name="range_matching"></a> <a name="interval_matching"></a>
#### 区间匹配Range Matching #### 区间匹配
case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式: case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
```swift ```
let count = 3_000_000_000_000 let approximateCount = 62
let countedThings = "stars in the Milky Way" let countedThings = "moons orbiting Saturn"
var naturalCount: String var naturalCount: String
switch count { switch approximateCount {
case 0: case 0:
naturalCount = "no" naturalCount = "no"
case 1...3: case 1..<5:
naturalCount = "a few" naturalCount = "a few"
case 4...9: case 5..<12:
naturalCount = "several" naturalCount = "several"
case 10...99: case 12..<100:
naturalCount = "tens of" naturalCount = "dozens of"
case 100...999: case 100..<1000:
naturalCount = "hundreds of" naturalCount = "hundreds of"
case 1000...999_999:
naturalCount = "thousands of"
default: default:
naturalCount = "millions and millions of" naturalCount = "many"
} }
println("There are \(naturalCount) \(countedThings).") print("There are \(naturalCount) \(countedThings).")
// 输出 "There are millions and millions of stars in the Milky Way." // 输出 "There are dozens of moons orbiting Saturn."
``` ```
在上例中,`approximateCount`在一个`switch`声明中被估值。每一个`case`都与之进行比较。因为`approximateCount`落在了12到100的区间所以`naturalCount`等于`"dozens of"`值,并且此后这段执行跳出了`switch`声明。
> 注意:
> 闭区间操作符(`...`)以及半开区间操作符(`..<`)功能被重载去返回`IntervalType`或`Range`。一个区间可以决定他是否包含特定的元素,就像当匹配一个`switch`声明的`case`一样。区间是一个连续值的集合,可以用`for-in`语句遍历它。
<a name="tuples"></a> <a name="tuples"></a>
#### 元组Tuple #### 元组Tuple
可以使用元组在同一个`switch`语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。 我们可以使用元组在同一个`switch`语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。
下面的例子展示了如何使用一个`(Int, Int)`类型的元组来分类下图中的点(x, y) 下面的例子展示了如何使用一个`(Int, Int)`类型的元组来分类下图中的点(x, y)
@ -458,15 +440,15 @@ println("There are \(naturalCount) \(countedThings).")
let somePoint = (1, 1) let somePoint = (1, 1)
switch somePoint { switch somePoint {
case (0, 0): case (0, 0):
println("(0, 0) is at the origin") print("(0, 0) is at the origin")
case (_, 0): case (_, 0):
println("(\(somePoint.0), 0) is on the x-axis") print("(\(somePoint.0), 0) is on the x-axis")
case (0, _): case (0, _):
println("(0, \(somePoint.1)) is on the y-axis") print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2): case (-2...2, -2...2):
println("(\(somePoint.0), \(somePoint.1)) is inside the box") print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default: default:
println("(\(somePoint.0), \(somePoint.1)) is outside of the box") print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
} }
// 输出 "(1, 1) is inside the box" // 输出 "(1, 1) is inside the box"
``` ```
@ -489,11 +471,11 @@ case 分支的模式允许将匹配的值绑定到一个临时的常量或变量
let anotherPoint = (2, 0) let anotherPoint = (2, 0)
switch anotherPoint { switch anotherPoint {
case (let x, 0): case (let x, 0):
println("on the x-axis with an x value of \(x)") print("on the x-axis with an x value of \(x)")
case (0, let y): case (0, let y):
println("on the y-axis with a y value of \(y)") print("on the y-axis with a y value of \(y)")
case let (x, y): case let (x, y):
println("somewhere else at (\(x), \(y))") print("somewhere else at (\(x), \(y))")
} }
// 输出 "on the x-axis with an x value of 2" // 输出 "on the x-axis with an x value of 2"
``` ```
@ -504,7 +486,7 @@ case let (x, y):
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。 这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`println`的书写。 一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`print(_:)`的书写。
请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。 请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。
@ -521,11 +503,11 @@ case 分支的模式可以使用`where`语句来判断额外的条件。
let yetAnotherPoint = (1, -1) let yetAnotherPoint = (1, -1)
switch yetAnotherPoint { switch yetAnotherPoint {
case let (x, y) where x == y: case let (x, y) where x == y:
println("(\(x), \(y)) is on the line x == y") print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y: case let (x, y) where x == -y:
println("(\(x), \(y)) is on the line x == -y") print("(\(x), \(y)) is on the line x == -y")
case let (x, y): case let (x, y):
println("(\(x), \(y)) is just some arbitrary point") print("(\(x), \(y)) is just some arbitrary point")
} }
// 输出 "(1, -1) is on the line x == -y" // 输出 "(1, -1) is on the line x == -y"
``` ```
@ -543,20 +525,20 @@ case let (x, y):
控制转移语句改变你代码的执行顺序通过它你可以实现代码的跳转。Swift有四种控制转移语句。 控制转移语句改变你代码的执行顺序通过它你可以实现代码的跳转。Swift有四种控制转移语句。
- continue - `continue`
- break - `break`
- fallthrough - `fallthrough`
- return - `return`
我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](../chapter2/06_Functions.html)章节讨论。 我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](06_Functions.html)章节讨论。
<a name="continue"></a> <a name="continue"></a>
### Continue ### Continue
`continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。 `continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。
>注意: > 注意:
在一个for条件递增`for-condition-increment`循环体中,调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。 > 在一个带有条件递增的for循环体中,调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句: 下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:
@ -571,7 +553,7 @@ for character in puzzleInput {
puzzleOutput.append(character) puzzleOutput.append(character)
} }
} }
println(puzzleOutput) print(puzzleOutput)
// 输出 "grtmndsthnklk" // 输出 "grtmndsthnklk"
``` ```
@ -615,9 +597,9 @@ default:
break break
} }
if let integerValue = possibleIntegerValue { if let integerValue = possibleIntegerValue {
println("The integer value of \(numberSymbol) is \(integerValue).") print("The integer value of \(numberSymbol) is \(integerValue).")
} else { } else {
println("An integer value could not be found for \(numberSymbol).") print("An integer value could not be found for \(numberSymbol).")
} }
// 输出 "The integer value of 三 is 3." // 输出 "The integer value of 三 is 3."
``` ```
@ -633,7 +615,7 @@ if let integerValue = possibleIntegerValue {
Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下C 语言要求你显示的插入`break`语句到每个`switch`分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。 Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下C 语言要求你显示的插入`break`语句到每个`switch`分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
如果你确实需要 C 风格的贯穿fallthrough的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。 如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。
```swift ```swift
let integerToDescribe = 5 let integerToDescribe = 5
@ -645,7 +627,7 @@ case 2, 3, 5, 7, 11, 13, 17, 19:
default: default:
description += " an integer." description += " an integer."
} }
println(description) print(description)
// 输出 "The number 5 is a prime number, and also an integer." // 输出 "The number 5 is a prime number, and also an integer."
``` ```
@ -653,13 +635,13 @@ println(description)
如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。 如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。
当`switch`代码块执行完后,使用`println`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。 当`switch`代码块执行完后,使用`print`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。
>注意: > 注意:
`fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。 > `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。
<a name="labeled_statements"></a> <a name="labeled_statements"></a>
### 带标签的语句Labeled Statements ### 带标签的语句
在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显示地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明`continue`语句想要影响哪一个循环体也会非常有用。 在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显示地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明`continue`语句想要影响哪一个循环体也会非常有用。
@ -671,9 +653,9 @@ println(description)
> `statements` > `statements`
> } > }
下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中_蛇和梯子_的改编版本。这次,游戏增加了一条额外的规则: 下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中*蛇和梯子*的改编版本。这次,游戏增加了一条额外的规则:
- 为了获胜,你必须_刚好_落在第 25 个方块中。 - 为了获胜,你必须*刚好*落在第 25 个方块中。
如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。 如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。
@ -681,7 +663,7 @@ println(description)
![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) ![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png)
`finalSquare`、`board`、`square`和`diceRoll`的初始化也和之前一样 `finalSquare`、`board`、`square`和`diceRoll`值被和之前一样的方式初始化
```swift ```swift
let finalSquare = 25 let finalSquare = 25
@ -712,7 +694,7 @@ gameLoop: while square != finalSquare {
square += board[square] square += board[square]
} }
} }
println("Game over!") print("Game over!")
``` ```
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。 每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
@ -724,3 +706,65 @@ println("Game over!")
>注意: >注意:
如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。 如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。
同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。 同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
<a name="early_exit"></a>
### 提前退出
像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`分句,如果条件不为真则执行`else`分局中的代码。
```swift
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(["name": "John"])
// prints "Hello John!"
// prints "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// prints "Hello Jane!"
// prints "I hope the weather is nice in Cupertino."
```
如果`guard`语句的条件被满足,则在保护语句的封闭大括号结束后继续执行代码。任何使用了可选绑定作为条件的一部分并被分配了值的变量或常量对于剩下的保护语句出现的代码段是可用的。
如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`或`continue`做这件事,或者它调用了一个不返回的方法或函数,例如`fatalError()`。
相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可靠性。
它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你处理违反要求的代码接近要求。
<a name="checking_api_availability"></a>
### 检测API是否可用
Swift 有内置支持去检查接口的可用性的这可以确保我们不会不小心地使用对于当前部署目标不可用的API。
编译器使用SDK中的可用信息来验证在我们在可用部署目标指定项目的代码中所有的API调用。如果我们尝试使用一个不可用的APISwift会在编译期报错。
我们使用一个可用性条件在一个`if`或`guard`语句中去有条件的执行一段代码这取决于我们想要使用的API是否在运行时是可用的。编译器使用从可用性条件语句中获取的信息这时它会去验证在代码块中调用的API是否都可用。
```swift
if #available(iOS 9, OSX 10.10, *) {
// 在 iOS 使用 iOS 9 APIs , 并且在 OS X 使用 OS X v10.10 APIs
} else {
// 回滚至早前 iOS and OS X 的API
}
```
以上可用性条件指定在iOS`if`段的代码仅仅在iOS9及更高可运行在OS X仅在OS X v10.10及更高可运行。最后一个参数,`*`,是必须的并且指定在任何其他平台上,`if`段的代码在最小可用部署目标指定项目中执行。
在它普遍的形式中,可用性条件获取了平台名字和版本的清单。平台名字可以是`iOS``OSX`或`watchOS`。除了特定的主板本号像iOS8我们可以指定较小的版本号像iOS8.3以及 OS X v10.10.3。
```swift
if #available(`platform name` `version`, `...`, *) {
`statements to execute if the APIs are available`
} else {
`fallback statements to execute if the APIs are unavailable`
}
```