From 51a2f82749f708e122467522227236946278c5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E6=9D=B0?= Date: Sun, 17 May 2015 17:13:28 +0800 Subject: [PATCH] new version --- source/chapter1/02_a_swift_tour.md | 48 +- source/chapter2/04_Collection_Types.md | 1295 ++++++++++---------- source/chapter2/05_Control_Flow.md | 1452 +++++++++++------------ source/chapter2/18_Type_Casting.md | 2 +- source/chapter2/22_Generics.md | 1036 ++++++++-------- source/theme/style/css/xcode.css | 159 +++ source/theme/style/js/highlight.pack.js | 1 + 7 files changed, 2076 insertions(+), 1917 deletions(-) create mode 100644 source/theme/style/css/xcode.css create mode 100644 source/theme/style/js/highlight.pack.js diff --git a/source/chapter1/02_a_swift_tour.md b/source/chapter1/02_a_swift_tour.md index 0d5e34f9..366d5d77 100755 --- a/source/chapter1/02_a_swift_tour.md +++ b/source/chapter1/02_a_swift_tour.md @@ -1,4 +1,4 @@ -> 翻译:[numbbbbb](https://github.com/numbbbbb) +> 翻译:[numbbbbb](https://github.com/numbbbbb) > 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai) # Swift 初见 @@ -25,8 +25,8 @@ println("Hello, world") 这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。 -> 注意: -> 为了获得最好的体验,在 Xcode 当中使用代码预览功能。代码预览功能可以让你编辑代码并实时看到运行结果。 +> 注意: +> 为了获得最好的体验,在 Xcode 当中使用代码预览功能。代码预览功能可以让你编辑代码并实时看到运行结果。 > 打开Playground @@ -50,7 +50,7 @@ let implicitDouble = 70.0 let explicitDouble: Double = 70 ``` -> 练习: +> 练习: > 创建一个常量,显式指定类型为`Float`并指定初始值为4。 值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。 @@ -60,7 +60,7 @@ let label = "The width is" let width = 94 let widthLabel = label + String(width) ``` -> 练习: +> 练习: > 删除最后一行中的`String`,错误提示是什么? 有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。例如: @@ -72,7 +72,7 @@ let appleSummary = "I have \(apples) apples." let fruitSummary = "I have \(apples + oranges) pieces of fruit." ``` -> 练习: +> 练习: > 使用`\()`来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。 使用方括号`[]`来创建数组和字典,并使用下标或者键(key)来访问元素。 @@ -137,7 +137,7 @@ if let name = optionalName { } ``` -> 练习: +> 练习: > 把`optionalName`改成`nil`,greeting会是什么?添加一个`else`语句,当`optionalName`是`nil`时给greeting赋一个不同的值。 如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。 @@ -158,7 +158,7 @@ default: } ``` -> 练习: +> 练习: > 删除`default`语句,看看会有什么错误? 运行`switch`中匹配到的子句之后,程序会退出`switch`语句,并不会继续向下运行,所以不需要在每个子句结尾写`break`。 @@ -182,7 +182,7 @@ for (kind, numbers) in interestingNumbers { largest ``` -> 练习: +> 练习: > 添加另一个变量来记录哪种类型的数字是最大的。 使用`while`来重复运行一段代码直到不满足条件。循环条件可以在开头也可以在结尾。 @@ -231,7 +231,7 @@ func greet(name: String, day: String) -> String { greet("Bob", "Tuesday") ``` -> 练习: +> 练习: > 删除`day`参数,添加一个参数来表示今天吃了什么午饭。 使用元组来让一个函数返回多个值。该元组的元素可以用名称或数字来表示。 @@ -241,7 +241,7 @@ func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) { var min = scores[0] var max = scores[0] var sum = 0 - + for score in scores { if score > max { max = score @@ -250,7 +250,7 @@ func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) { } sum += score } - + return (min, max, sum) } let statistics = calculateStatistics([5, 3, 100, 3, 9]) @@ -272,7 +272,7 @@ sumOf() sumOf(42, 597, 12) ``` -> 练习: +> 练习: > 写一个计算参数平均值的函数。 函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。 @@ -330,7 +330,7 @@ numbers.map({ }) ``` -> 练习: +> 练习: > 重写闭包,对所有奇数返回0。 有很多种创建闭包的方法。如果一个闭包的类型已知,比如作为一个回调函数,你可以忽略参数的类型和返回值。单个语句闭包会把它语句的值当做结果返回。 @@ -361,7 +361,7 @@ class Shape { } ``` -> 练习: +> 练习: > 使用`let`添加一个常量属性,再添加一个接收一个参数的方法。 要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。 @@ -420,7 +420,7 @@ test.area() test.simpleDescription() ``` -> 练习: +> 练习: > 创建`NamedShape`的另一个子类`Circle`,构造器接收两个参数,一个是半径一个是名称,实现`area`和`describe`方法。 属性可以有 getter 和 setter 。 @@ -539,7 +539,7 @@ let ace = Rank.Ace let aceRawValue = ace.rawValue ``` -> 练习: +> 练习: > 写一个函数,通过比较它们的原始值来比较两个`Rank`值。 在上面的例子中,枚举原始值的类型是`Int`,所以你只需要设置第一个原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。 @@ -575,7 +575,7 @@ let hearts = Suit.Hearts let heartsDescription = hearts.simpleDescription() ``` -> 练习: +> 练习: > 给`Suit`添加一个`color`方法,对`spades`和`clubs`返回“black”,对`hearts`和`diamonds`返回“red”。 注意,有两种方式可以引用`Hearts`成员:给`hearts`常量赋值时,枚举成员`Suit.Hearts`需要用全名来引用,因为常量没有显式指定类型。在`switch`里,枚举成员使用缩写`.Hearts`来引用,因为`self`的值已经知道是一个`suit`。已知变量类型的情况下你可以使用缩写。 @@ -595,7 +595,7 @@ let threeOfSpades = Card(rank: .Three, suit: .Spades) let threeOfSpadesDescription = threeOfSpades.simpleDescription() ``` -> 练习: +> 练习: > 给`Card`添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。 一个枚举成员的实例可以有实例值。相同枚举成员的实例可以有不同的值。创建实例的时候传入值即可。实例值和原始值是不同的:枚举成员的原始值对于所有实例都是相同的,而且你是在定义枚举的时候设置原始值。 @@ -619,7 +619,7 @@ case let .Error(error): } ``` -> 练习: +> 练习: > 给`ServerResponse`和`switch`添加第三种情况。 注意如何从`ServerResponse`中提取日升和日落时间。 @@ -661,12 +661,12 @@ b.adjust() let bDescription = b.simpleDescription ``` -> 练习: +> 练习: > 写一个实现这个协议的枚举。 注意声明`SimpleStructure`时候`mutating`关键字用来标记一个会修改结构体的方法。`SimpleClass`的声明不需要标记任何方法因为类中的方法经常会修改类。 -使用`extension`来为现有的类型添加功能,比如新的方法和参数。你可以使用扩展来改造定义在别处,甚至是从外部库或者框架引入的一个类型,使得这个类型遵循某个协议。 +使用`extension`来为现有的类型添加功能,比如新的方法和参数。你可以使用扩展在别处修改定义,甚至是从外部库或者框架引入的一个类型,使得这个类型遵循某个协议。 ```swift extension Int: ExampleProtocol { @@ -680,7 +680,7 @@ extension Int: ExampleProtocol { 7.simpleDescription ``` -> 练习: +> 练习: > 给`Double`类型写一个扩展,添加`absoluteValue`功能。 你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。 @@ -737,7 +737,7 @@ func anyCommonElements 练习: +> 练习: > 修改`anyCommonElements`函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。 简单起见,你可以忽略`where`,只在冒号后面写协议或者类名。` `和``是等价的。 diff --git a/source/chapter2/04_Collection_Types.md b/source/chapter2/04_Collection_Types.md index 92845e1b..f3970833 100755 --- a/source/chapter2/04_Collection_Types.md +++ b/source/chapter2/04_Collection_Types.md @@ -1,648 +1,647 @@ -> 翻译:[zqp](https://github.com/zqp) -> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) - -# 集合类型 (Collection Types) ------------------ - -本页包含内容: - -- [数组(Arrays)](#arrays) -- [集合(Sets)](#sets) -- [字典(Dictionaries)](#dictionaries) -- [集合的可变性(Mutability of Collections)](#mutability_of_collections) - -Swift 语言提供经典的数组和字典两种集合类型来存储集合数据。数组用来按顺序存储相同类型的数据。字典虽然无序存储相同类型数据值但是需要由独有的标识符引用和寻址(就是键值对)。 - -Swift 语言里的数组和字典中存储的数据值类型必须明确。 这意味着我们不能把不正确的数据类型插入其中。 同时这也说明我们完全可以对获取出的值类型非常自信。 Swift 对显式类型集合的使用确保了我们的代码对工作所需要的类型非常清楚,也让我们在开发中可以早早地找到任何的类型不匹配错误。 - -> 注意: -Swift 的数组结构在被声明成常量和变量或者被传入函数与方法中时会相对于其他类型展现出不同的特性。 获取更多信息请参见[集合的可变性](#mutability_of_collections)与[集合在赋值和复制中的行为](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)章节。 - - -## 数组 - -数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 - -Swift 数组特定于它所存储元素的类型。这与 Objective-C 的 NSArray 和 NSMutableArray 不同,这两个类可以存储任意类型的对象,并且不提供所返回对象的任何特别信息。在 Swift 中,数据值在被存储进入某个数组之前类型必须明确,方法是通过显式的类型标注或类型推断,而且不是必须是`class`类型。例如: 如果我们创建了一个`Int`值类型的数组,我们不能往其中插入任何不是`Int`类型的数据。 Swift 中的数组是类型安全的,并且它们中包含的类型必须明确。 - - -### 数组的简单语法 - -写 Swift 数组应该遵循像`Array`这样的形式,其中`SomeType`是这个数组中唯一允许存在的数据类型。 我们也可以使用像`[SomeType]`这样的简单语法。 尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 - - -### 数组构造语句 - -我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值。 -`[value 1, value 2, value 3]`。 - -下面这个例子创建了一个叫做`shoppingList`并且存储字符串的数组: - -```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 -println("The shopping list contains \(shoppingList.count) items.") -// 输出"The shopping list contains 2 items."(这个数组有2个项) -``` - -使用布尔项`isEmpty`来作为检查`count`属性的值是否为 0 的捷径。 - -```swift -if shoppingList.isEmpty { - println("The shopping list is empty.") -} else { - println("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 现在有六项 -``` - -> 注意: ->我们不能使用下标语法在数组尾部添加新项。如果我们试着用这种方法对索引越界的数据进行检索或者设置新值的操作,我们会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。 - -调用数组的`insert(atIndex:)`方法来在某个具体索引值之前添加数据项: - -```swift -shoppingList.insert("Maple Syrup", atIndex: 0) -// shoppingList 现在有7项 -// "Maple Syrup" 现在是这个列表中的第一项 -``` - -这次`insert`函数调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。 - -类似的我们可以使用`removeAtIndex`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): - -```swift -let mapleSyrup = shoppingList.removeAtIndex(0) -// 索引值为0的数据项被移除 -// shoppingList 现在只有6项,而且不包括Maple Syrup -// mapleSyrup常量的值等于被移除数据项的值 "Maple Syrup" -``` - -数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`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 { - println(item) -} -// Six eggs -// Milk -// Flour -// Baking Powder -// Bananas -``` - -如果我们同时需要每个数据项的值和索引值,可以使用全局`enumerate`函数来进行数组遍历。`enumerate`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: - -```swift -for (index, value) in enumerate(shoppingList) { - println("Item \(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)。 - - -### 创建并且构造一个数组 - -我们可以使用构造语法来创建一个由特定数据类型构成的空数组: - -```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] -``` - -## 集合 - -集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以把集合当做是数组另一形式。 - -> 注意: -> Swift的Set类型被桥接到Fundation中的NSSet类 -> 关于使用FundationCocoa中集合的知识,请看Swift与Cocoa和Objective-C使用 - - -### Set类型语法 - -Swift中的Set类型被写为```Set```,这里的```SomeType```表示```Set```中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 - - -### 创建和构造一个Set - -你可以通过构造器语法创建一个特定类型的空集合: - -```swift -var letters = Set() -println("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```. - -为了找出一个```Set```中元素的数量,可以使用其只读属性```count```: - -```swift -println("I have \(favoriteGenres.count) favorite music genres.") -// 打印 ""I have 3 favorite music genres."" -``` - -使用布尔属性```isEmpty```作为一个缩写形式去检查```count```属性是否为```0```: - -```swift -if favoriteGenres.isEmpty { - println("As far as music goes, I'm not picky.") -} else { - println("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") { - println("\(removedValue)? I'm over it.") -} else { - println("I never much cared for that.") -} -// 打印 "Rock? I'm over it." -``` - -使用```contains(_:)```方法去检查```Set```中是否包含一个特定的值。 - -```swift -if favoriteGenres.contains("Funk") { - println("I get up on the good foot.") -} else { - println("It's too funky in here.") -} -// 打印 "It's too funky in here." -``` - - -### 遍历一个Set - -你可以在一个```for-in```循环中遍历一个```Set```中的所有值。 - -```swift -for genre in favoriteGenres { - println("\(value)") -} -// Classical -// Jazz -// Hip hop -``` - -更多关于```for-in```循环信息,请看For循环 - -Swift的```Set```类型没有确定的顺序,为了按照特定顺序来遍历一个```Set```中值可以使用全局```sorted```函数,它将根据提供的序列返回一个排序的集合. - -```swift --> for genre in sorted(favoriteGenres) { - println("\(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) - -* 使用```union(_:)```方法根据两个集合的值创建一个新的集合。 -* 使用```subtract(_:)```方法根据不在该集合中的值创建一个新的集合。 -* 使用```intersect(_:)```方法根据两个集合中都包含的值创建的一个新的集合。 -* 使用```exclusiveOr(_:)```方法根据值在一个集合中但不在两个集合中的值创建一个新的集合。 - -```swift -let oddDigits: Set = [1, 3, 5, 7, 9] -let evenDigits: Set = [0, 2, 4, 6, 8] -let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] -sorted(oddDigits.union(evenDigits)) -// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -sorted(oddDigits.intersect(evenDigits)) -// [] -sorted(oddDigits.subtract(singleDigitPrimeNumbers)) -// [1, 9] -sorted(oddDigits.exclusiveOr(singleDigitPrimeNumbers)) -// [1, 2, 9] -``` - - -#### 集合比较 - -下面的插图描述了三个集合-```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) - -* 使用“是否等”运算符(```=```)来判断两个集合是否包含相同的值。 -* 使用```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 -``` - - -#### 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) - - - -## 字典 - -字典是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 - -Swift 的字典使用时需要具体规定可以存储键和值类型。不同于 Objective-C 的`NSDictionary`和`NSMutableDictionary` 类可以使用任何类型的对象来作键和值并且不提供任何关于这些对象的本质信息。在 Swift 中,在某个特定字典中可以存储的键和值必须提前定义清楚,方法是通过显性类型标注或者类型推断。 - -Swift 的字典使用`Dictionary`定义,其中`KeyType`是字典中键的数据类型,`ValueType`是字典中对应于这些键所存储值的数据类型。 - -`KeyType`的唯一限制就是可哈希的,这样可以保证它是独一无二的,所有的 Swift 基本类型(例如`String`,`Int`, `Double`和`Bool`)都是默认可哈希的,并且所有这些类型都可以在字典中当做键使用。未关联值的枚举成员(参见[枚举](08_Enumerations.html))也是默认可哈希的。 - - -## 字典字面量 - -我们可以使用字典字面量来构造字典,它们和我们刚才介绍过的数组字面量拥有相似语法。一个字典字面量是一个定义拥有一个或者多个键值对的字典集合的简单语句。 - -一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含并且由逗号分割: - -```swift -[key 1: value 1, key 2: value 2, key 3: value 3] -``` - -下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: - -```swift -var airports: [String:String] = ["TYO": "Tokyo", "DUB": "Dublin"] -``` - -`airports`字典被定义为一种 `[String: String]`,它意味着这个字典的键和值都是`String`类型。 - -> 注意: -> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 - -`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`TYO`,值是`Tokyo`。第二对的键是`DUB`,值是`Dublin`。 - -这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量是构造两个初始数据项的`airport`字典。 - -和数组一样,如果我们使用字面量构造字典就不用把类型定义清楚。`airports`的也可以用这种方法简短定义: - -```swift -var airports = ["TYO": "Tokyo", "DUB": "Dublin"] -``` - -因为这个语句中所有的键和值都分别是相同的数据类型,Swift 可以推断出`Dictionary`是`airports`字典的正确类型。 - - -### 读取和修改字典 - -我们可以通过字典的方法和属性来读取和修改字典,或者使用下标语法。和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: - -```swift -println("The dictionary of airports contains \(airports.count) items.") -// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) -``` - -可以使用布尔属性`isEmpty`来快捷的检查字典的`count`属性是否等于0。 - -```swift -if airports.isEmpty { - println("The airports dictionary is empty.") -} else { - println("The airports dictionary is not empty.") -} -// 打印 "The airports dictionary is not empty.(这个字典不为空)" -``` - -我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个合适类型的 key 作为下标索引,并且分配新的合适类型的值: - -```swift -airports["LHR"] = "London" -// airports 字典现在有三个数据项 -``` - -我们也可以使用下标语法来改变特定键对应的值: - -```swift -airports["LHR"] = "London Heathrow" -// "LHR"对应的值 被改为 "London Heathrow -``` - -作为另一种下标方法,字典的`updateValue(forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的示例,`updateValue(forKey:)`方法在这个键不存在对应值的时候设置值或者在存在时更新已存在的值。和上面的下标方法不一样,这个方法返回更新值之前的原值。这样方便我们检查更新是否成功。 - -`updateValue(forKey:)`函数会返回包含一个字典值类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。如果值存在,则这个可选值值等于被替换的值,否则将会是`nil`。 - -```swift -if let oldValue = airports.updateValue("Dublin Internation", forKey: "DUB") { - println("The old value for DUB was \(oldValue).") -} -// 输出 "The old value for DUB was Dublin."(DUB原值是dublin) -``` - -我们也可以使用下标语法来在字典中检索特定键对应的值。由于使用一个没有值的键这种情况是有可能发生的,可选类型返回这个键存在的相关值,否则就返回`nil`: - -```swift -if let airportName = airports["DUB"] { - println("The name of the airport is \(airportName).") -} else { - println("That airport is not in the airports dictionary.") -} -// 打印 "The name of the airport is Dublin Internation."(机场的名字是都柏林国际) -``` - -我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: - -```swift -airports["APL"] = "Apple Internation" -// "Apple Internation"不是真的 APL机场, 删除它 -airports["APL"] = nil -// APL现在被移除了 -``` - -另外,`removeValueForKey`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的value或者在没有值的情况下返回`nil`: - -```swift -if let removedValue = airports.removeValueForKey("DUB") { - println("The removed airport's name is \(removedValue).") -} else { - println("The airports dictionary does not contain a value for DUB.") -} -// prints "The removed airport's name is Dublin International." -``` - - -### 字典遍历 - -我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都由`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: - -```swift -for (airportCode, airportName) in airports { - println("\(airportCode): \(airportName)") -} -// TYO: Tokyo -// LHR: London Heathrow -``` - -`for-in`循环请参见[For 循环](05_Control_Flow.html#for_loops)。 - -我们也可以通过访问它的`keys`或者`values`属性(都是可遍历集合)检索一个字典的键或者值: - -```swift -for airportCode in airports.keys { - println("Airport code: \(airportCode)") -} -// Airport code: TYO -// Airport code: LHR - -for airportName in airports.values { - println("Airport name: \(airportName)") -} -// Airport name: Tokyo -// Airport name: London Heathrow -``` - -如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受`Array`实例 API 的参数,可以直接使用`keys`或者`values`属性直接构造一个新数组: - -```swift -let airportCodes = Array(airports.keys) -// airportCodes is ["TYO", "LHR"] - -let airportNames = Array(airports.values) -// airportNames is ["Tokyo", "London Heathrow"] -``` - -> 注意: -> Swift 的字典类型是无序集合类型。其中字典键,值,键值对在遍历的时候会重新排列,而且其中顺序是不固定的。 - - -### 创建一个空字典 - -我们可以像数组一样使用构造语法创建一个空字典: - -```swift -var namesOfIntegers = Dictionary() -// namesOfIntegers 是一个空的 Dictionary -``` - -这个例子创建了一个`Int, String`类型的空字典来储存英语对整数的命名。它的键是`Int`型,值是`String`型。 - -如果上下文已经提供了信息类型,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号): - -```swift -namesOfIntegers[16] = "sixteen" -// namesOfIntegers 现在包含一个键值对 -namesOfIntegers = [:] -// namesOfIntegers 又成为了一个 Int, String类型的空字典 -``` - -> 注意: -> 在后台,Swift 的数组和字典都是由泛型集合来实现的,想了解更多泛型和集合信息请参见[泛型](22_Generics.html)。 - - -## 集合的可变性 - -数组和字典都是在单个集合中存储可变值。如果我们创建一个数组或者字典并且把它分配成一个变量,这个集合将会是可变的。这意味着我们可以在创建之后添加更多或移除已存在的数据项来改变这个集合的大小。与此相反,如果我们把数组或字典分配成常量,那么它就是不可变的,它的大小不能被改变。 - -对字典来说,不可变性也意味着我们不能替换其中任何现有键所对应的值。不可变字典的内容在被首次设定之后不能更改。 -不可变性对数组来说有一点不同,当然我们不能试着改变任何不可变数组的大小,但是我们可以重新设定相对现存索引所对应的值。这使得 Swift 数组在大小被固定的时候依然可以做的很棒。 - -Swift 数组的可变性行为同时影响了数组实例如何被分配和修改,想获取更多信息,请参见[集合在赋值和复制中的行为](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)。 - -> 注意: -> 在我们不需要改变数组大小的时候创建不可变数组是很好的习惯。如此 Swift 编译器可以优化我们创建的集合。 +> 翻译:[zqp](https://github.com/zqp) +> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) + +# 集合类型 (Collection Types) +----------------- + +本页包含内容: + +- [数组(Arrays)](#arrays) +- [集合(Sets)](#sets) +- [字典(Dictionaries)](#dictionaries) +- [集合的可变性(Mutability of Collections)](#mutability_of_collections) + +Swift 语言提供经典的数组和字典两种集合类型来存储集合数据。数组用来按顺序存储相同类型的数据。字典虽然无序存储相同类型数据值但是需要由独有的标识符引用和寻址(就是键值对)。 + +Swift 语言里的数组和字典中存储的数据值类型必须明确。 这意味着我们不能把不正确的数据类型插入其中。 同时这也说明我们完全可以对获取出的值类型非常自信。 Swift 对显式类型集合的使用确保了我们的代码对工作所需要的类型非常清楚,也让我们在开发中可以早早地找到任何的类型不匹配错误。 + +> 注意: +Swift 的数组结构在被声明成常量和变量或者被传入函数与方法中时会相对于其他类型展现出不同的特性。 获取更多信息请参见[集合的可变性](#mutability_of_collections)与[集合在赋值和复制中的行为](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)章节。 + + +## 数组 + +数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 + +Swift 数组特定于它所存储元素的类型。这与 Objective-C 的 NSArray 和 NSMutableArray 不同,这两个类可以存储任意类型的对象,并且不提供所返回对象的任何特别信息。在 Swift 中,数据值在被存储进入某个数组之前类型必须明确,方法是通过显式的类型标注或类型推断,而且不是必须是`class`类型。例如: 如果我们创建了一个`Int`值类型的数组,我们不能往其中插入任何不是`Int`类型的数据。 Swift 中的数组是类型安全的,并且它们中包含的类型必须明确。 + + +### 数组的简单语法 + +写 Swift 数组应该遵循像`Array`这样的形式,其中`SomeType`是这个数组中唯一允许存在的数据类型。 我们也可以使用像`[SomeType]`这样的简单语法。 尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 + + +### 数组构造语句 + +我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值。 +`[value 1, value 2, value 3]`。 + +下面这个例子创建了一个叫做`shoppingList`并且存储字符串的数组: + +```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 +println("The shopping list contains \(shoppingList.count) items.") +// 输出"The shopping list contains 2 items."(这个数组有2个项) +``` + +使用布尔项`isEmpty`来作为检查`count`属性的值是否为 0 的捷径。 + +```swift +if shoppingList.isEmpty { + println("The shopping list is empty.") +} else { + println("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 现在有六项 +``` + +> 注意: +>我们不能使用下标语法在数组尾部添加新项。如果我们试着用这种方法对索引越界的数据进行检索或者设置新值的操作,我们会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。 + +调用数组的`insert(atIndex:)`方法来在某个具体索引值之前添加数据项: + +```swift +shoppingList.insert("Maple Syrup", atIndex: 0) +// shoppingList 现在有7项 +// "Maple Syrup" 现在是这个列表中的第一项 +``` + +这次`insert`函数调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。 + +类似的我们可以使用`removeAtIndex`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): + +```swift +let mapleSyrup = shoppingList.removeAtIndex(0) +// 索引值为0的数据项被移除 +// shoppingList 现在只有6项,而且不包括Maple Syrup +// mapleSyrup常量的值等于被移除数据项的值 "Maple Syrup" +``` + +数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`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 { + println(item) +} +// Six eggs +// Milk +// Flour +// Baking Powder +// Bananas +``` + +如果我们同时需要每个数据项的值和索引值,可以使用全局`enumerate`函数来进行数组遍历。`enumerate`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: + +```swift +for (index, value) in enumerate(shoppingList) { + println("Item \(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)。 + + +### 创建并且构造一个数组 + +我们可以使用构造语法来创建一个由特定数据类型构成的空数组: + +```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] +``` + +## 集合 + +集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以把集合当做是数组另一形式。 + +> 注意: +> Swift的Set类型被桥接到Fundation中的NSSet类 +> 关于使用FundationCocoa中集合的知识,请看Swift与Cocoa和Objective-C使用 + + +### Set类型语法 + +Swift中的Set类型被写为```Set```,这里的```SomeType```表示```Set```中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 + + +### 创建和构造一个Set + +你可以通过构造器语法创建一个特定类型的空集合: + +```swift +var letters = Set() +println("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```. + +为了找出一个```Set```中元素的数量,可以使用其只读属性```count```: + +```swift +println("I have \(favoriteGenres.count) favorite music genres.") +// 打印 ""I have 3 favorite music genres."" +``` + +使用布尔属性```isEmpty```作为一个缩写形式去检查```count```属性是否为```0```: + +```swift +if favoriteGenres.isEmpty { + println("As far as music goes, I'm not picky.") +} else { + println("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") { + println("\(removedValue)? I'm over it.") +} else { + println("I never much cared for that.") +} +// 打印 "Rock? I'm over it." +``` + +使用```contains(_:)```方法去检查```Set```中是否包含一个特定的值。 + +```swift +if favoriteGenres.contains("Funk") { + println("I get up on the good foot.") +} else { + println("It's too funky in here.") +} +// 打印 "It's too funky in here." +``` + + +### 遍历一个Set + +你可以在一个```for-in```循环中遍历一个```Set```中的所有值。 + +```swift +for genre in favoriteGenres { + println("\(value)") +} +// Classical +// Jazz +// Hip hop +``` + +更多关于```for-in```循环信息,请看For循环 + +Swift的```Set```类型没有确定的顺序,为了按照特定顺序来遍历一个```Set```中值可以使用全局```sorted```函数,它将根据提供的序列返回一个排序的集合. + +```swift +-> for genre in sorted(favoriteGenres) { + println("\(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) + +* 使用```union(_:)```方法根据两个集合的值创建一个新的集合。 +* 使用```subtract(_:)```方法根据不在该集合中的值创建一个新的集合。 +* 使用```intersect(_:)```方法根据两个集合中都包含的值创建的一个新的集合。 +* 使用```exclusiveOr(_:)```方法根据值在一个集合中但不在两个集合中的值创建一个新的集合。 + +```swift +let oddDigits: Set = [1, 3, 5, 7, 9] +let evenDigits: Set = [0, 2, 4, 6, 8] +let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] +sorted(oddDigits.union(evenDigits)) +// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +sorted(oddDigits.intersect(evenDigits)) +// [] +sorted(oddDigits.subtract(singleDigitPrimeNumbers)) +// [1, 9] +sorted(oddDigits.exclusiveOr(singleDigitPrimeNumbers)) +// [1, 2, 9] +``` + + +#### 集合比较 + +下面的插图描述了三个集合-```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) + +* 使用“是否等”运算符(```=```)来判断两个集合是否包含相同的值。 +* 使用```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 +``` + + +#### 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) + + + +## 字典 + +字典是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 + +Swift 的字典使用时需要具体规定可以存储键和值类型。不同于 Objective-C 的`NSDictionary`和`NSMutableDictionary` 类可以使用任何类型的对象来作键和值并且不提供任何关于这些对象的本质信息。在 Swift 中,在某个特定字典中可以存储的键和值必须提前定义清楚,方法是通过显性类型标注或者类型推断。 + +Swift 的字典使用`Dictionary`定义,其中`KeyType`是字典中键的数据类型,`ValueType`是字典中对应于这些键所存储值的数据类型。 + +`KeyType`的唯一限制就是可哈希的,这样可以保证它是独一无二的,所有的 Swift 基本类型(例如`String`,`Int`, `Double`和`Bool`)都是默认可哈希的,并且所有这些类型都可以在字典中当做键使用。未关联值的枚举成员(参见[枚举](08_Enumerations.html))也是默认可哈希的。 + + +## 字典字面量 + +我们可以使用字典字面量来构造字典,它们和我们刚才介绍过的数组字面量拥有相似语法。一个字典字面量是一个定义拥有一个或者多个键值对的字典集合的简单语句。 + +一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含并且由逗号分割: + +```swift +[key 1: value 1, key 2: value 2, key 3: value 3] +``` + +下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: + +```swift +var airports: [String:String] = ["TYO": "Tokyo", "DUB": "Dublin"] +``` + +`airports`字典被定义为一种 `[String: String]`,它意味着这个字典的键和值都是`String`类型。 + +> 注意: +> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 + +`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`TYO`,值是`Tokyo`。第二对的键是`DUB`,值是`Dublin`。 + +这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量是构造两个初始数据项的`airport`字典。 + +和数组一样,如果我们使用字面量构造字典就不用把类型定义清楚。`airports`的也可以用这种方法简短定义: + +```swift +var airports = ["TYO": "Tokyo", "DUB": "Dublin"] +``` + +因为这个语句中所有的键和值都分别是相同的数据类型,Swift 可以推断出`Dictionary`是`airports`字典的正确类型。 + + +### 读取和修改字典 + +我们可以通过字典的方法和属性来读取和修改字典,或者使用下标语法。和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: + +```swift +println("The dictionary of airports contains \(airports.count) items.") +// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) +``` + +可以使用布尔属性`isEmpty`来快捷的检查字典的`count`属性是否等于0。 + +```swift +if airports.isEmpty { + println("The airports dictionary is empty.") +} else { + println("The airports dictionary is not empty.") +} +// 打印 "The airports dictionary is not empty.(这个字典不为空)" +``` + +我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个合适类型的 key 作为下标索引,并且分配新的合适类型的值: + +```swift +airports["LHR"] = "London" +// airports 字典现在有三个数据项 +``` + +我们也可以使用下标语法来改变特定键对应的值: + +```swift +airports["LHR"] = "London Heathrow" +// "LHR"对应的值 被改为 "London Heathrow +``` + +作为另一种下标方法,字典的`updateValue(forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的示例,`updateValue(forKey:)`方法在这个键不存在对应值的时候设置值或者在存在时更新已存在的值。和上面的下标方法不一样,这个方法返回更新值之前的原值。这样方便我们检查更新是否成功。 + +`updateValue(forKey:)`函数会返回包含一个字典值类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。如果值存在,则这个可选值值等于被替换的值,否则将会是`nil`。 + +```swift +if let oldValue = airports.updateValue("Dublin Internation", forKey: "DUB") { + println("The old value for DUB was \(oldValue).") +} +// 输出 "The old value for DUB was Dublin."(DUB原值是dublin) +``` + +我们也可以使用下标语法来在字典中检索特定键对应的值。由于使用一个没有值的键这种情况是有可能发生的,可选类型返回这个键存在的相关值,否则就返回`nil`: + +```swift +if let airportName = airports["DUB"] { + println("The name of the airport is \(airportName).") +} else { + println("That airport is not in the airports dictionary.") +} +// 打印 "The name of the airport is Dublin Internation."(机场的名字是都柏林国际) +``` + +我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: + +```swift +airports["APL"] = "Apple Internation" +// "Apple Internation"不是真的 APL机场, 删除它 +airports["APL"] = nil +// APL现在被移除了 +``` + +另外,`removeValueForKey`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的value或者在没有值的情况下返回`nil`: + +```swift +if let removedValue = airports.removeValueForKey("DUB") { + println("The removed airport's name is \(removedValue).") +} else { + println("The airports dictionary does not contain a value for DUB.") +} +// prints "The removed airport's name is Dublin International." +``` + + +### 字典遍历 + +我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都由`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: + +```swift +for (airportCode, airportName) in airports { + println("\(airportCode): \(airportName)") +} +// TYO: Tokyo +// LHR: London Heathrow +``` + +`for-in`循环请参见[For 循环](05_Control_Flow.html#for_loops)。 + +我们也可以通过访问它的`keys`或者`values`属性(都是可遍历集合)检索一个字典的键或者值: + +```swift +for airportCode in airports.keys { + println("Airport code: \(airportCode)") +} +// Airport code: TYO +// Airport code: LHR + +for airportName in airports.values { + println("Airport name: \(airportName)") +} +// Airport name: Tokyo +// Airport name: London Heathrow +``` + +如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受`Array`实例 API 的参数,可以直接使用`keys`或者`values`属性直接构造一个新数组: + +```swift +let airportCodes = Array(airports.keys) +// airportCodes is ["TYO", "LHR"] + +let airportNames = Array(airports.values) +// airportNames is ["Tokyo", "London Heathrow"] +``` + +> 注意: +> Swift 的字典类型是无序集合类型。其中字典键,值,键值对在遍历的时候会重新排列,而且其中顺序是不固定的。 + + +### 创建一个空字典 + +我们可以像数组一样使用构造语法创建一个空字典: + +```swift +var namesOfIntegers = Dictionary() +// namesOfIntegers 是一个空的 Dictionary +``` + +这个例子创建了一个`Int, String`类型的空字典来储存英语对整数的命名。它的键是`Int`型,值是`String`型。 + +如果上下文已经提供了信息类型,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号): + +```swift +namesOfIntegers[16] = "sixteen" +// namesOfIntegers 现在包含一个键值对 +namesOfIntegers = [:] +// namesOfIntegers 又成为了一个 Int, String类型的空字典 +``` + +> 注意: +> 在后台,Swift 的数组和字典都是由泛型集合来实现的,想了解更多泛型和集合信息请参见[泛型](22_Generics.html)。 + + +## 集合的可变性 + +数组和字典都是在单个集合中存储可变值。如果我们创建一个数组或者字典并且把它分配成一个变量,这个集合将会是可变的。这意味着我们可以在创建之后添加更多或移除已存在的数据项来改变这个集合的大小。与此相反,如果我们把数组或字典分配成常量,那么它就是不可变的,它的大小不能被改变。 + +相反,如果你给常量赋值一个数组、集合或者字典,那它就是不可变的,大小和内容都不能修改。 + +Swift 数组的可变性行为同时影响了数组实例如何被分配和修改,想获取更多信息,请参见[集合在赋值和复制中的行为](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)。 + +> 注意: +> 在我们不需要改变数组大小的时候创建不可变数组是很好的习惯。如此 Swift 编译器可以优化我们创建的集合。 diff --git a/source/chapter2/05_Control_Flow.md b/source/chapter2/05_Control_Flow.md index 245eb491..c87698ec 100755 --- a/source/chapter2/05_Control_Flow.md +++ b/source/chapter2/05_Control_Flow.md @@ -1,726 +1,726 @@ -> 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao) -> 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai) - -# 控制流 ------------------ - -本页包含内容: - -- [For 循环](#for_loops) -- [While 循环](#while_loops) -- [条件语句](#conditional_statement) -- [控制转移语句(Control Transfer Statements)](#control_transfer_statements) - -Swift提供了类似 C 语言的流程控制结构,包括可以多次执行任务的`for`和`while`循环,基于特定条件选择执行不同代码分支的`if`和`switch`语句,还有控制流程跳转到其他代码的`break`和`continue`语句。 - -除了 C 语言里面传统的 for 条件递增(`for-condition-increment`)循环,Swift 还增加了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。 - -Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿(fallthrough)至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿(fallthrough)的情况。case 还可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。 - - -## For 循环 - -`for`循环用来按照指定的次数多次执行一系列语句。Swift 提供两种`for`循环形式: - -* `for-in`用来遍历一个区间(range),序列(sequence),集合(collection),系列(progression)里面所有的元素执行一系列语句。 -* for条件递增(`for-condition-increment`)语句,用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。 - - -### For-In - -你可以使用`for-in`循环来遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。 - -下面的例子用来输出乘 5 乘法表前面一部分内容: - -```swift -for index in 1...5 { - println("\(index) times 5 is \(index * 5)") -} -// 1 times 5 is 5 -// 2 times 5 is 10 -// 3 times 5 is 15 -// 4 times 5 is 20 -// 5 times 5 is 25 -``` - -例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1`到`5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`println`方法会再执行一次。整个过程会进行到闭区间结尾为止。 - -上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。 - ->注意: -`index`常量只存在于循环的生命周期里。如果你想在循环完成后访问`index`的值,又或者想让`index`成为一个变量而不是常量,你必须在循环之前自己进行声明。 - -如果你不需要知道区间内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问: - -```swift -let base = 3 -let power = 10 -var answer = 1 -for _ in 1...power { - answer *= base -} -println("\(base) to the power of \(power) is \(answer)") -// 输出 "3 to the power of 10 is 59049" -``` - -这个例子计算 base 这个数的 power 次幂(本例中,是`3`的`10`次幂),从`1`(`3`的`0`次幂)开始做`3`的乘法, 进行`10`次,使用`1`到`10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略具体的值,并且不提供循环遍历时对值的访问。 - -使用`for-in`遍历一个数组所有元素: - -```swift -let names = ["Anna", "Alex", "Brian", "Jack"] -for name in names { - println("Hello, \(name)!") -} -// Hello, Anna! -// Hello, Alex! -// Hello, Brian! -// Hello, Jack! -``` - -你也可以通过遍历一个字典来访问它的键值对(key-value pairs)。遍历字典时,字典的每项元素会以`(key, value)`元组的形式返回,你可以在`for-in`循环中使用显式的常量名称来解读`(key, value)`元组。下面的例子中,字典的键(key)解读为常量`animalName`,字典的值会被解读为常量`legCount`: - -```swift -let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] -for (animalName, legCount) in numberOfLegs { - println("\(animalName)s have \(legCount) legs") -} -// spiders have 8 legs -// ants have 6 legs -// cats have 4 legs -``` - -字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](../chapter2/04_Collection_Types.html)。 - -除了数组和字典,你也可以使用`for-in`循环来遍历字符串中的字符(`Character`): - -```swift -for character in "Hello" { - println(character) -} -// H -// e -// l -// l -// o -``` - - -### For条件递增(for-condition-increment) - -除了`for-in`循环,Swift 提供使用条件判断和递增方法的标准 C 样式`for`循环: - -```swift -for var index = 0; index < 3; ++index { - println("index is \(index)") -} -// index is 0 -// index is 1 -// index is 2 -``` - -下面是一般情况下这种循环方式的格式: - -> for `initialization`; `condition`; `increment` { -> `statements` -> } - -和 C 语言中一样,分号将循环的定义分为 3 个部分,不同的是,Swift 不需要使用圆括号将“initialization; condition; increment”包括起来。 - -这个循环执行流程如下: - -1. 循环首次启动时,初始化表达式(_initialization expression_)被调用一次,用来初始化循环所需的所有常量和变量。 -2. 条件表达式(_condition expression_)被调用,如果表达式调用结果为`false`,循环结束,继续执行`for`循环关闭大括号 -(`}`)之后的代码。如果表达式调用结果为`true`,则会执行大括号内部的代码(_statements_)。 -3. 执行所有语句(_statements_)之后,执行递增表达式(_increment expression_)。通常会增加或减少计数器的值,或者根据语句(_statements_)输出来修改某一个初始化的变量。当递增表达式运行完成后,重复执行第 2 步,条件表达式会再次执行。 - -上述描述和循环格式等同于: - -> `initialization` -> while `condition` { -> `statements` -> `increment` -> } - -在初始化表达式中声明的常量和变量(比如`var index = 0`)只在`for`循环的生命周期里有效。如果想在循环结束后访问`index`的值,你必须要在循环生命周期开始前声明`index`。 - -```swift -var index: Int -for index = 0; index < 3; ++index { - println("index is \(index)") -} -// index is 0 -// index is 1 -// index is 2 -println("The loop statements were executed \(index) times") -// 输出 "The loop statements were executed 3 times -``` - -注意`index`在循环结束后最终的值是`3`而不是`2`。最后一次调用递增表达式`++index`会将`index`设置为`3`,从而导致`index < 3`条件为`false`,并终止循环。 - - -## While 循环 - -`while`循环运行一系列语句直到条件变成`false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种`while`循环形式: - -* `while`循环,每次在循环开始时计算条件是否符合; -* `do-while`循环,每次在循环结束时计算条件是否符合。 - - -###While - -`while`循环从计算单一条件开始。如果条件为`true`,会重复运行一系列语句,直到条件变为`false`。 - -下面是一般情况下 `while` 循环格式: - -> while `condition` { -> `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) - -游戏的规则如下: - -* 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格; -* 每一轮,你通过掷一个 6 边的骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示; -* 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去; -* 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。 - -游戏盘面可以使用一个`Int`数组来表达。数组的长度由一个`finalSquare`常量储存,用来初始化数组和检测最终胜利条件。游戏盘面由 26 个 `Int` 0 值初始化,而不是 25 个(由`0`到`25`,一共 26 个): - -```swift -let finalSquare = 25 -var board = [Int](count: finalSquare + 1, repeatedValue: 0) -``` - -一些方块被设置成有蛇或者梯子的指定值。梯子底部的方块是一个正值,使你可以向上移动,蛇头处的方块是一个负值,会让你向下移动: - -```swift -board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 -board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 -``` - -3 号方块是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11`和`3`之间的差值)。使用一元加运算符(`+i`)是为了和一元减运算符(`-i`)对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。 - -玩家由左下角编号为 0 的方格开始游戏。一般来说玩家第一次掷骰子后才会进入游戏盘面: - -```swift -var square = 0 -var diceRoll = 0 -while square < finalSquare { - // 掷骰子 - if ++diceRoll == 7 { diceRoll = 1 } - // 根据点数移动 - square += diceRoll - if square < board.count { - // 如果玩家还在棋盘上,顺着梯子爬上去或者顺着蛇滑下去 - square += board[square] - } -} -println("Game over!") -``` - -本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。`++diceRoll`调用完成_后_,返回值等于`diceRoll`自增后的值。任何时候如果`diceRoll`的值等于7时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1`,`2`,`3`,`4`,`5`,`6`,`1`,`2`。 - -掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏结束,相应地,代码会在`square`增加`board[square]`的值向前或向后移动(遇到了梯子或者蛇)之前,检测`square`的值是否小于`board`的`count`属性。 - -如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。例如如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。 - -当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。 - -`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。 - - - -###Do-While - -`while`循环的另外一种形式是`do-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false`。 - -下面是一般情况下 `do-while`循环的格式: - -> do { -> `statements` -> } while `condition` - -还是蛇和梯子的游戏,使用`do-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环一样: - -``` swift -let finalSquare = 25 -var board = [Int](count: finalSquare + 1, repeatedValue: 0) -board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 -board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 -var square = 0 -var diceRoll = 0 -``` - -`do-while`的循环版本,循环中_第一步_就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。 - -游戏开始时,玩家在第 0 个方格上,`board[0]`一直等于 0, 不会有什么影响: - -```swift -do { - // 顺着梯子爬上去或者顺着蛇滑下去 - square += board[square] - // 掷骰子 - if ++diceRoll == 7 { diceRoll = 1 } - // 根据点数移动 - square += diceRoll -} while square < finalSquare -println("Game over!") -``` - -检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。 - -循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`do-while`表现得比`while`循环更好。`do-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断。 - - -## 条件语句 - -根据特定的条件执行特定的代码通常是十分有用的,例如:当错误发生时,你可能想运行额外的代码;或者,当输入的值太大或太小时,向用户显示一条消息等。要实现这些功能,你就需要使用*条件语句*。 - -Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、可能情况较多且需要用到模式匹配(pattern-matching)的情境。 - - -### If - -`if`语句最简单的形式就是只包含一个条件,当且仅当该条件为`true`时,才执行相关代码: - -```swift -var temperatureInFahrenheit = 30 -if temperatureInFahrenheit <= 32 { - println("It's very cold. Consider wearing a scarf.") -} -// 输出 "It's very cold. Consider wearing a scarf." -``` - -上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行`if`块后面的代码。 - -当然,`if`语句允许二选一,也就是当条件为`false`时,执行 *else 语句*: - -```swift -temperatureInFahrenheit = 40 -if temperatureInFahrenheit <= 32 { - println("It's very cold. Consider wearing a scarf.") -} else { - println("It's not that cold. Wear a t-shirt.") -} -// 输出 "It's not that cold. Wear a t-shirt." -``` - -显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾——因此,`else`分支就被触发了。 - -你可以把多个`if`语句链接在一起,像下面这样: - -```swift -temperatureInFahrenheit = 90 -if temperatureInFahrenheit <= 32 { - println("It's very cold. Consider wearing a scarf.") -} else if temperatureInFahrenheit >= 86 { - println("It's really warm. Don't forget to wear sunscreen.") -} else { - println("It's not that cold. Wear a t-shirt.") -} -// 输出 "It's really warm. Don't forget to wear sunscreen." -``` - -在上面的例子中,额外的`if`语句用于判断是不是特别热。而最后的`else`语句被保留了下来,用于打印既不冷也不热时的消息。 - -实际上,最后的`else`语句是可选的: - -```swift -temperatureInFahrenheit = 72 -if temperatureInFahrenheit <= 32 { - println("It's very cold. Consider wearing a scarf.") -} else if temperatureInFahrenheit >= 86 { - println("It's really warm. Don't forget to wear sunscreen.") -} -``` - -在这个例子中,由于既不冷也不热,所以不会触发`if`或`else if`分支,也就不会打印任何消息。 - - -### Switch - -`switch`语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,`switch`语句会执行对应的代码。当有可能的情况较多时,通常用`switch`语句替换`if`语句。 - -`switch`语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较: - -> switch `some value to consider` { -> case `value 1`: -> `respond to value 1` -> case `value 2`, -> `value 3`: -> `respond to value 2 or 3` -> default: -> `otherwise, do something else` -> } - -`switch`语句都由*多个 case* 构成。为了匹配某些更特定的值,Swift 提供了几种更复杂的匹配模式,这些模式将在本节的稍后部分提到。 - -每一个 case 都是代码执行的一条分支,这与`if`语句类似。与之不同的是,`switch`语句会决定哪一条分支应该被执行。 - -`switch`语句必须是_完备的_。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。 - -下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符: - -```swift -let someCharacter: Character = "e" -switch someCharacter { -case "a", "e", "i", "o", "u": - println("\(someCharacter) is a vowel") -case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", -"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": - println("\(someCharacter) is a consonant") -default: - println("\(someCharacter) is not a vowel or a consonant") -} -// 输出 "e is a vowel" -``` - -在这个例子中,第一个 case 分支用于匹配五个元音,第二个 case 分支用于匹配所有的辅音。 - -由于为其它可能的字符写 case 分支没有实际的意义,因此在这个例子中使用了默认分支来处理剩下的既不是元音也不是辅音的字符——这就保证了`switch`语句的完备性。 - - -#### 不存在隐式的贯穿(No Implicit Fallthrough) - -与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。 - -> 注意: -你依然可以在 case 分支中的代码执行完毕前跳出,详情请参考[Switch 语句中的 break](#break_in_a_switch_statement)。 - -每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的: - -```swift -let anotherCharacter: Character = "a" -switch anotherCharacter { -case "a": -case "A": - println("The letter A") -default: - println("Not the letter A") -} -// this will report a compile-time error -``` - -不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会同时匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": does not contain any executable statements`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。 - -一个 case 也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写): - -> switch `some value to consider` { -> case `value 1`, -> `value 2`: -> `statements` -> } - -> 注意: -如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。 - - -#### 区间匹配(Range Matching) - -case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式: - -```swift -let count = 3_000_000_000_000 -let countedThings = "stars in the Milky Way" -var naturalCount: String -switch count { -case 0: - naturalCount = "no" -case 1...3: - naturalCount = "a few" -case 4...9: - naturalCount = "several" -case 10...99: - naturalCount = "tens of" -case 100...999: - naturalCount = "hundreds of" -case 1000...999_999: - naturalCount = "thousands of" -default: - naturalCount = "millions and millions of" -} -println("There are \(naturalCount) \(countedThings).") -// 输出 "There are millions and millions of stars in the Milky Way." -``` - - -#### 元组(Tuple) - -你可以使用元组在同一个`switch`语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。 - -下面的例子展示了如何使用一个`(Int, Int)`类型的元组来分类下图中的点(x, y): - -```swift -let somePoint = (1, 1) -switch somePoint { -case (0, 0): - println("(0, 0) is at the origin") -case (_, 0): - println("(\(somePoint.0), 0) is on the x-axis") -case (0, _): - println("(0, \(somePoint.1)) is on the y-axis") -case (-2...2, -2...2): - println("(\(somePoint.0), \(somePoint.1)) is inside the box") -default: - println("(\(somePoint.0), \(somePoint.1)) is outside of the box") -} -// 输出 "(1, 1) is inside the box" -``` - -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png) - -在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。 - -不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉。 - - - -#### 值绑定(Value Bindings) - -case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了——这种行为被称为*值绑定*(value binding)。 - -下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y): - -```swift -let anotherPoint = (2, 0) -switch anotherPoint { -case (let x, 0): - println("on the x-axis with an x value of \(x)") -case (0, let y): - println("on the y-axis with a y value of \(y)") -case let (x, y): - println("somewhere else at (\(x), \(y))") -} -// 输出 "on the x-axis with an x value of 2" -``` - -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphMedium_2x.png) - -在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在黄色y轴上,或者不在坐标轴上。 - -这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。 - -一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`println`的书写。 - -请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。 - -在上面的例子中,`x`和`y`是常量,这是因为没有必要在其对应的 case 分支中修改它们的值。然而,它们也可以是变量——程序将会创建临时变量,并用相应的值初始化它。修改这些变量只会影响其对应的 case 分支。 - - -#### Where - -case 分支的模式可以使用`where`语句来判断额外的条件。 - -下面的例子把下图中的点(x, y)进行了分类: - -```swift -let yetAnotherPoint = (1, -1) -switch yetAnotherPoint { -case let (x, y) where x == y: - println("(\(x), \(y)) is on the line x == y") -case let (x, y) where x == -y: - println("(\(x), \(y)) is on the line x == -y") -case let (x, y): - println("(\(x), \(y)) is just some arbitrary point") -} -// 输出 "(1, -1) is on the line x == -y" -``` - -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphComplex_2x.png) - -在上面的例子中,`switch`语句会判断某个点是否在绿色的对角线`x == y`上,是否在紫色的对角线`x == -y`上,或者不在对角线上。 - -这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这些常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。 - -就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch`语句就已经完备了,因此不需要再书写默认分支。 - - -## 控制转移语句(Control Transfer Statements) - -控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift有四种控制转移语句。 - -- continue -- break -- fallthrough -- return - -我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](../chapter2/06_Functions.html)章节讨论。 - - -### Continue - -`continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。 - ->注意: -在一个for条件递增(`for-condition-increment`)循环体中,在调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。 - -下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句: - -```swift -let puzzleInput = "great minds think alike" -var puzzleOutput = "" -for character in puzzleInput { - switch character { - case "a", "e", "i", "o", "u", " ": - continue - default: - puzzleOutput.append(character) - } -} -println(puzzleOutput) - // 输出 "grtmndsthnklk" -``` - -在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环迭代结束,从新开始下次循环迭代。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。 - - -### Break - -`break`语句会立刻结束整个控制流的执行。当你想要更早的结束一个`switch`代码块或者一个循环体时,你都可以使用`break`语句。 - - -#### 循环语句中的 break - -当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环迭代的代码被执行,也不会再有下次的循环迭代产生。 - - -#### Switch 语句中的 break - -当在一个`switch`代码块中使用`break`时,会立即中断该`switch`代码块的执行,并且跳转到表示`switch`代码块结束的大括号(`}`)后的第一行代码。 - -这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的`switch`需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上`break`语句。当那个分支被匹配到时,分支内的`break`语句立即结束`switch`代码块。 - ->注意: -当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你总是可以使用`break`来忽略某个分支。 - -下面的例子通过`switch`来判断一个`Character`值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。 - -```swift -let numberSymbol: Character = "三" // 简体中文里的数字 3 -var possibleIntegerValue: Int? -switch numberSymbol { -case "1", "١", "一", "๑": - possibleIntegerValue = 1 -case "2", "٢", "二", "๒": - possibleIntegerValue = 2 -case "3", "٣", "三", "๓": - possibleIntegerValue = 3 -case "4", "٤", "四", "๔": - possibleIntegerValue = 4 -default: - break -} -if let integerValue = possibleIntegerValue { - println("The integer value of \(numberSymbol) is \(integerValue).") -} else { - println("An integer value could not be found for \(numberSymbol).") -} -// 输出 "The integer value of 三 is 3." -``` - -这个例子检查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰语中的`1`到`4`之一。如果被匹配到,该`switch`分支语句给`Int?`类型变量`possibleIntegerValue`设置一个整数值。 - -当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。 - -在上面的例子中,想要把`Character`所有的的可能性都枚举出来是不现实的,所以使用`default`分支来包含所有上面没有匹配到字符的情况。由于这个`default`分支不需要执行任何动作,所以它只写了一条`break`语句。一旦落入到`default`分支中后,`break`语句就完成了该分支的所有代码操作,代码继续向下,开始执行`if let`语句。 - - -### 贯穿(Fallthrough) - -Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显示的插入`break`语句到每个`switch`分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。 - -如果你确实需要 C 风格的贯穿(fallthrough)的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。 - -```swift -let integerToDescribe = 5 -var description = "The number \(integerToDescribe) is" -switch integerToDescribe { -case 2, 3, 5, 7, 11, 13, 17, 19: - description += " a prime number, and also" - fallthrough -default: - description += " an integer." -} -println(description) -// 输出 "The number 5 is a prime number, and also an integer." -``` - -这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数添加一段文字在`description`后,来表明这个是数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支添加一段额外的文字在`description`的最后,至此`switch`代码块执行完了。 - -如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。 - -当`switch`代码块执行完后,使用`println`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。 - ->注意: -`fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。 - - -### 带标签的语句(Labeled Statements) - -在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显示地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明`continue`语句想要影响哪一个循环体也会非常有用。 - -为了实现这个目的,你可以使用标签来标记一个循环体或者`switch`代码块,当使用`break`或者`continue`时,带上这个标签,可以控制该标签代表对象的中断或者执行。 - -产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个`while`循环体的语法,同样的规则适用于所有的循环体和`switch`代码块。 - -> `label name`: while `condition` { -> `statements` -> } - -下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中_蛇和梯子_的改编版本。这次,游戏增加了一条额外的规则: - -- 为了获胜,你必须_刚好_落在第 25 个方块中。 - -如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。 - -游戏的棋盘和之前一样: - -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) - -值`finalSquare`、`board`、`square`和`diceRoll`的初始化也和之前一样: - -```swift -let finalSquare = 25 -var board = Int[](count: finalSquare + 1, repeatedValue: 0) -board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 -board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 -var square = 0 -var diceRoll = 0 -``` - -这个版本的游戏使用`while`循环体和`switch`方法块来实现游戏的逻辑。`while`循环体有一个标签名`gameLoop`,来表明它是蛇与梯子的主循环。 - -该`while`循环体的条件判断语句是`while square !=finalSquare`,这表明你必须刚好落在方格25中。 - -```swift -gameLoop: while square != finalSquare { - if ++diceRoll == 7 { diceRoll = 1 } - switch square + diceRoll { - case finalSquare: - // 到达最后一个方块,游戏结束 - break gameLoop - case let newSquare where newSquare > finalSquare: - // 超出最后一个方块,再掷一次骰子 - continue gameLoop - default: - // 本次移动有效 - square += diceRoll - square += board[square] - } -} -println("Game over!") -``` - -每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。 - -- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,游戏结束。 -- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环的迭代,开始下一次循环迭代。 -- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到`while`循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代。 - ->注意: -如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。 -同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。 +> 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao) +> 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai) + +# 控制流 +----------------- + +本页包含内容: + +- [For 循环](#for_loops) +- [While 循环](#while_loops) +- [条件语句](#conditional_statement) +- [控制转移语句(Control Transfer Statements)](#control_transfer_statements) + +Swift提供了类似 C 语言的流程控制结构,包括可以多次执行任务的`for`和`while`循环,基于特定条件选择执行不同代码分支的`if`和`switch`语句,还有控制流程跳转到其他代码的`break`和`continue`语句。 + +除了 C 语言里面传统的 for 条件递增(`for-condition-increment`)循环,Swift 还增加了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。 + +Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿(fallthrough)至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿(fallthrough)的情况。case 还可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。 + + +## For 循环 + +`for`循环用来按照指定的次数多次执行一系列语句。Swift 提供两种`for`循环形式: + +* `for-in`用来遍历一个区间(range),序列(sequence),集合(collection),系列(progression)里面所有的元素执行一系列语句。 +* for条件递增(`for-condition-increment`)语句,用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。 + + +### For-In + +你可以使用`for-in`循环来遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。 + +下面的例子用来输出乘 5 乘法表前面一部分内容: + +```swift +for index in 1...5 { + println("\(index) times 5 is \(index * 5)") +} +// 1 times 5 is 5 +// 2 times 5 is 10 +// 3 times 5 is 15 +// 4 times 5 is 20 +// 5 times 5 is 25 +``` + +例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1`到`5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`println`方法会再执行一次。整个过程会进行到闭区间结尾为止。 + +上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。 + +>注意: +`index`常量只存在于循环的生命周期里。如果你想在循环完成后访问`index`的值,又或者想让`index`成为一个变量而不是常量,你必须在循环之前自己进行声明。 + +如果你不需要知道区间内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问: + +```swift +let base = 3 +let power = 10 +var answer = 1 +for _ in 1...power { + answer *= base +} +println("\(base) to the power of \(power) is \(answer)") +// 输出 "3 to the power of 10 is 59049" +``` + +这个例子计算 base 这个数的 power 次幂(本例中,是`3`的`10`次幂),从`1`(`3`的`0`次幂)开始做`3`的乘法, 进行`10`次,使用`1`到`10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略具体的值,并且不提供循环遍历时对值的访问。 + +使用`for-in`遍历一个数组所有元素: + +```swift +let names = ["Anna", "Alex", "Brian", "Jack"] +for name in names { + println("Hello, \(name)!") +} +// Hello, Anna! +// Hello, Alex! +// Hello, Brian! +// Hello, Jack! +``` + +你也可以通过遍历一个字典来访问它的键值对(key-value pairs)。遍历字典时,字典的每项元素会以`(key, value)`元组的形式返回,你可以在`for-in`循环中使用显式的常量名称来解读`(key, value)`元组。下面的例子中,字典的键(key)解读为常量`animalName`,字典的值会被解读为常量`legCount`: + +```swift +let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] +for (animalName, legCount) in numberOfLegs { + println("\(animalName)s have \(legCount) legs") +} +// spiders have 8 legs +// ants have 6 legs +// cats have 4 legs +``` + +字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](../chapter2/04_Collection_Types.html)。 + +除了数组和字典,你也可以使用`for-in`循环来遍历字符串中的字符(`Character`): + +```swift +for character in "Hello" { + println(character) +} +// H +// e +// l +// l +// o +``` + + +### For条件递增(for-condition-increment) + +除了`for-in`循环,Swift 提供使用条件判断和递增方法的标准 C 样式`for`循环: + +```swift +for var index = 0; index < 3; ++index { + println("index is \(index)") +} +// index is 0 +// index is 1 +// index is 2 +``` + +下面是一般情况下这种循环方式的格式: + +> for `initialization`; `condition`; `increment` { +> `statements` +> } + +和 C 语言中一样,分号将循环的定义分为 3 个部分,不同的是,Swift 不需要使用圆括号将“initialization; condition; increment”包括起来。 + +这个循环执行流程如下: + +1. 循环首次启动时,初始化表达式(_initialization expression_)被调用一次,用来初始化循环所需的所有常量和变量。 +2. 条件表达式(_condition expression_)被调用,如果表达式调用结果为`false`,循环结束,继续执行`for`循环关闭大括号 +(`}`)之后的代码。如果表达式调用结果为`true`,则会执行大括号内部的代码(_statements_)。 +3. 执行所有语句(_statements_)之后,执行递增表达式(_increment expression_)。通常会增加或减少计数器的值,或者根据语句(_statements_)输出来修改某一个初始化的变量。当递增表达式运行完成后,重复执行第 2 步,条件表达式会再次执行。 + +上述描述和循环格式等同于: + +> `initialization` +> while `condition` { +> `statements` +> `increment` +> } + +在初始化表达式中声明的常量和变量(比如`var index = 0`)只在`for`循环的生命周期里有效。如果想在循环结束后访问`index`的值,你必须要在循环生命周期开始前声明`index`。 + +```swift +var index: Int +for index = 0; index < 3; ++index { + println("index is \(index)") +} +// index is 0 +// index is 1 +// index is 2 +println("The loop statements were executed \(index) times") +// 输出 "The loop statements were executed 3 times +``` + +注意`index`在循环结束后最终的值是`3`而不是`2`。最后一次调用递增表达式`++index`会将`index`设置为`3`,从而导致`index < 3`条件为`false`,并终止循环。 + + +## While 循环 + +`while`循环运行一系列语句直到条件变成`false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种`while`循环形式: + +* `while`循环,每次在循环开始时计算条件是否符合; +* `do-while`循环,每次在循环结束时计算条件是否符合。 + + +###While + +`while`循环从计算单一条件开始。如果条件为`true`,会重复运行一系列语句,直到条件变为`false`。 + +下面是一般情况下 `while` 循环格式: + +> while `condition` { +> `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) + +游戏的规则如下: + +* 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格; +* 每一轮,你通过掷一个 6 边的骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示; +* 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去; +* 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。 + +游戏盘面可以使用一个`Int`数组来表达。数组的长度由一个`finalSquare`常量储存,用来初始化数组和检测最终胜利条件。游戏盘面由 26 个 `Int` 0 值初始化,而不是 25 个(由`0`到`25`,一共 26 个): + +```swift +let finalSquare = 25 +var board = [Int](count: finalSquare + 1, repeatedValue: 0) +``` + +一些方块被设置成有蛇或者梯子的指定值。梯子底部的方块是一个正值,使你可以向上移动,蛇头处的方块是一个负值,会让你向下移动: + +```swift +board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 +board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 +``` + +3 号方块是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11`和`3`之间的差值)。使用一元加运算符(`+i`)是为了和一元减运算符(`-i`)对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。 + +玩家由左下角编号为 0 的方格开始游戏。一般来说玩家第一次掷骰子后才会进入游戏盘面: + +```swift +var square = 0 +var diceRoll = 0 +while square < finalSquare { + // 掷骰子 + if ++diceRoll == 7 { diceRoll = 1 } + // 根据点数移动 + square += diceRoll + if square < board.count { + // 如果玩家还在棋盘上,顺着梯子爬上去或者顺着蛇滑下去 + square += board[square] + } +} +println("Game over!") +``` + +本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。`++diceRoll`调用完成_后_,返回值等于`diceRoll`自增后的值。任何时候如果`diceRoll`的值等于7时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1`,`2`,`3`,`4`,`5`,`6`,`1`,`2`。 + +掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏结束,相应地,代码会在`square`增加`board[square]`的值向前或向后移动(遇到了梯子或者蛇)之前,检测`square`的值是否小于`board`的`count`属性。 + +如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。例如如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。 + +当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。 + +`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。 + + + +###Do-While + +`while`循环的另外一种形式是`do-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false`。 + +下面是一般情况下 `do-while`循环的格式: + +> do { +> `statements` +> } while `condition` + +还是蛇和梯子的游戏,使用`do-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环一样: + +``` swift +let finalSquare = 25 +var board = [Int](count: finalSquare + 1, repeatedValue: 0) +board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 +board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 +var square = 0 +var diceRoll = 0 +``` + +`do-while`的循环版本,循环中_第一步_就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。 + +游戏开始时,玩家在第 0 个方格上,`board[0]`一直等于 0, 不会有什么影响: + +```swift +do { + // 顺着梯子爬上去或者顺着蛇滑下去 + square += board[square] + // 掷骰子 + if ++diceRoll == 7 { diceRoll = 1 } + // 根据点数移动 + square += diceRoll +} while square < finalSquare +println("Game over!") +``` + +检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。 + +循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`do-while`表现得比`while`循环更好。`do-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断。 + + +## 条件语句 + +根据特定的条件执行特定的代码通常是十分有用的,例如:当错误发生时,你可能想运行额外的代码;或者,当输入的值太大或太小时,向用户显示一条消息等。要实现这些功能,你就需要使用*条件语句*。 + +Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、可能情况较多且需要用到模式匹配(pattern-matching)的情境。 + + +### If + +`if`语句最简单的形式就是只包含一个条件,当且仅当该条件为`true`时,才执行相关代码: + +```swift +var temperatureInFahrenheit = 30 +if temperatureInFahrenheit <= 32 { + println("It's very cold. Consider wearing a scarf.") +} +// 输出 "It's very cold. Consider wearing a scarf." +``` + +上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行`if`块后面的代码。 + +当然,`if`语句允许二选一,也就是当条件为`false`时,执行 *else 语句*: + +```swift +temperatureInFahrenheit = 40 +if temperatureInFahrenheit <= 32 { + println("It's very cold. Consider wearing a scarf.") +} else { + println("It's not that cold. Wear a t-shirt.") +} +// 输出 "It's not that cold. Wear a t-shirt." +``` + +显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾——因此,`else`分支就被触发了。 + +你可以把多个`if`语句链接在一起,像下面这样: + +```swift +temperatureInFahrenheit = 90 +if temperatureInFahrenheit <= 32 { + println("It's very cold. Consider wearing a scarf.") +} else if temperatureInFahrenheit >= 86 { + println("It's really warm. Don't forget to wear sunscreen.") +} else { + println("It's not that cold. Wear a t-shirt.") +} +// 输出 "It's really warm. Don't forget to wear sunscreen." +``` + +在上面的例子中,额外的`if`语句用于判断是不是特别热。而最后的`else`语句被保留了下来,用于打印既不冷也不热时的消息。 + +实际上,最后的`else`语句是可选的: + +```swift +temperatureInFahrenheit = 72 +if temperatureInFahrenheit <= 32 { + println("It's very cold. Consider wearing a scarf.") +} else if temperatureInFahrenheit >= 86 { + println("It's really warm. Don't forget to wear sunscreen.") +} +``` + +在这个例子中,由于既不冷也不热,所以不会触发`if`或`else if`分支,也就不会打印任何消息。 + + +### Switch + +`switch`语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,`switch`语句会执行对应的代码。当有可能的情况较多时,通常用`switch`语句替换`if`语句。 + +`switch`语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较: + +> switch `some value to consider` { +> case `value 1`: +> `respond to value 1` +> case `value 2`, +> `value 3`: +> `respond to value 2 or 3` +> default: +> `otherwise, do something else` +> } + +`switch`语句都由*多个 case* 构成。为了匹配某些更特定的值,Swift 提供了几种更复杂的匹配模式,这些模式将在本节的稍后部分提到。 + +每一个 case 都是代码执行的一条分支,这与`if`语句类似。与之不同的是,`switch`语句会决定哪一条分支应该被执行。 + +`switch`语句必须是_完备的_。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。 + +下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符: + +```swift +let someCharacter: Character = "e" +switch someCharacter { +case "a", "e", "i", "o", "u": + println("\(someCharacter) is a vowel") +case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", +"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": + println("\(someCharacter) is a consonant") +default: + println("\(someCharacter) is not a vowel or a consonant") +} +// 输出 "e is a vowel" +``` + +在这个例子中,第一个 case 分支用于匹配五个元音,第二个 case 分支用于匹配所有的辅音。 + +由于为其它可能的字符写 case 分支没有实际的意义,因此在这个例子中使用了默认分支来处理剩下的既不是元音也不是辅音的字符——这就保证了`switch`语句的完备性。 + + +#### 不存在隐式的贯穿(No Implicit Fallthrough) + +与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。 + +> 注意: +你依然可以在 case 分支中的代码执行完毕前跳出,详情请参考[Switch 语句中的 break](#break_in_a_switch_statement)。 + +每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的: + +```swift +let anotherCharacter: Character = "a" +switch anotherCharacter { +case "a": +case "A": + println("The letter A") +default: + println("Not the letter A") +} +// this will report a compile-time error +``` + +不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会同时匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": does not contain any executable statements`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。 + +一个 case 也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写): + +> switch `some value to consider` { +> case `value 1`, +> `value 2`: +> `statements` +> } + +> 注意: +如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。 + + +#### 区间匹配(Range Matching) + +case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式: + +```swift +let count = 3_000_000_000_000 +let countedThings = "stars in the Milky Way" +var naturalCount: String +switch count { +case 0: + naturalCount = "no" +case 1...3: + naturalCount = "a few" +case 4...9: + naturalCount = "several" +case 10...99: + naturalCount = "tens of" +case 100...999: + naturalCount = "hundreds of" +case 1000...999_999: + naturalCount = "thousands of" +default: + naturalCount = "millions and millions of" +} +println("There are \(naturalCount) \(countedThings).") +// 输出 "There are millions and millions of stars in the Milky Way." +``` + + +#### 元组(Tuple) + +你可以使用元组在同一个`switch`语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。 + +下面的例子展示了如何使用一个`(Int, Int)`类型的元组来分类下图中的点(x, y): + +```swift +let somePoint = (1, 1) +switch somePoint { +case (0, 0): + println("(0, 0) is at the origin") +case (_, 0): + println("(\(somePoint.0), 0) is on the x-axis") +case (0, _): + println("(0, \(somePoint.1)) is on the y-axis") +case (-2...2, -2...2): + println("(\(somePoint.0), \(somePoint.1)) is inside the box") +default: + println("(\(somePoint.0), \(somePoint.1)) is outside of the box") +} +// 输出 "(1, 1) is inside the box" +``` + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png) + +在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。 + +不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉。 + + + +#### 值绑定(Value Bindings) + +case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了——这种行为被称为*值绑定*(value binding)。 + +下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y): + +```swift +let anotherPoint = (2, 0) +switch anotherPoint { +case (let x, 0): + println("on the x-axis with an x value of \(x)") +case (0, let y): + println("on the y-axis with a y value of \(y)") +case let (x, y): + println("somewhere else at (\(x), \(y))") +} +// 输出 "on the x-axis with an x value of 2" +``` + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphMedium_2x.png) + +在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在黄色y轴上,或者不在坐标轴上。 + +这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。 + +一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`println`的书写。 + +请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。 + +在上面的例子中,`x`和`y`是常量,这是因为没有必要在其对应的 case 分支中修改它们的值。然而,它们也可以是变量——程序将会创建临时变量,并用相应的值初始化它。修改这些变量只会影响其对应的 case 分支。 + + +#### Where + +case 分支的模式可以使用`where`语句来判断额外的条件。 + +下面的例子把下图中的点(x, y)进行了分类: + +```swift +let yetAnotherPoint = (1, -1) +switch yetAnotherPoint { +case let (x, y) where x == y: + println("(\(x), \(y)) is on the line x == y") +case let (x, y) where x == -y: + println("(\(x), \(y)) is on the line x == -y") +case let (x, y): + println("(\(x), \(y)) is just some arbitrary point") +} +// 输出 "(1, -1) is on the line x == -y" +``` + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphComplex_2x.png) + +在上面的例子中,`switch`语句会判断某个点是否在绿色的对角线`x == y`上,是否在紫色的对角线`x == -y`上,或者不在对角线上。 + +这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这些常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。 + +就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch`语句就已经完备了,因此不需要再书写默认分支。 + + +## 控制转移语句(Control Transfer Statements) + +控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift有四种控制转移语句。 + +- continue +- break +- fallthrough +- return + +我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](../chapter2/06_Functions.html)章节讨论。 + + +### Continue + +`continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。 + +>注意: +在一个for条件递增(`for-condition-increment`)循环体中,在调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。 + +下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句: + +```swift +let puzzleInput = "great minds think alike" +var puzzleOutput = "" +for character in puzzleInput { + switch character { + case "a", "e", "i", "o", "u", " ": + continue + default: + puzzleOutput.append(character) + } +} +println(puzzleOutput) + // 输出 "grtmndsthnklk" +``` + +在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环迭代结束,从新开始下次循环迭代。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。 + + +### Break + +`break`语句会立刻结束整个控制流的执行。当你想要更早的结束一个`switch`代码块或者一个循环体时,你都可以使用`break`语句。 + + +#### 循环语句中的 break + +当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环迭代的代码被执行,也不会再有下次的循环迭代产生。 + + +#### Switch 语句中的 break + +当在一个`switch`代码块中使用`break`时,会立即中断该`switch`代码块的执行,并且跳转到表示`switch`代码块结束的大括号(`}`)后的第一行代码。 + +这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的`switch`需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上`break`语句。当那个分支被匹配到时,分支内的`break`语句立即结束`switch`代码块。 + +>注意: +当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你总是可以使用`break`来忽略某个分支。 + +下面的例子通过`switch`来判断一个`Character`值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。 + +```swift +let numberSymbol: Character = "三" // 简体中文里的数字 3 +var possibleIntegerValue: Int? +switch numberSymbol { +case "1", "١", "一", "๑": + possibleIntegerValue = 1 +case "2", "٢", "二", "๒": + possibleIntegerValue = 2 +case "3", "٣", "三", "๓": + possibleIntegerValue = 3 +case "4", "٤", "四", "๔": + possibleIntegerValue = 4 +default: + break +} +if let integerValue = possibleIntegerValue { + println("The integer value of \(numberSymbol) is \(integerValue).") +} else { + println("An integer value could not be found for \(numberSymbol).") +} +// 输出 "The integer value of 三 is 3." +``` + +这个例子检查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰语中的`1`到`4`之一。如果被匹配到,该`switch`分支语句给`Int?`类型变量`possibleIntegerValue`设置一个整数值。 + +当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。 + +在上面的例子中,想要把`Character`所有的的可能性都枚举出来是不现实的,所以使用`default`分支来包含所有上面没有匹配到字符的情况。由于这个`default`分支不需要执行任何动作,所以它只写了一条`break`语句。一旦落入到`default`分支中后,`break`语句就完成了该分支的所有代码操作,代码继续向下,开始执行`if let`语句。 + + +### 贯穿(Fallthrough) + +Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显示的插入`break`语句到每个`switch`分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。 + +如果你确实需要 C 风格的贯穿(fallthrough)的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。 + +```swift +let integerToDescribe = 5 +var description = "The number \(integerToDescribe) is" +switch integerToDescribe { +case 2, 3, 5, 7, 11, 13, 17, 19: + description += " a prime number, and also" + fallthrough +default: + description += " an integer." +} +println(description) +// 输出 "The number 5 is a prime number, and also an integer." +``` + +这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数添加一段文字在`description`后,来表明这个是数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支添加一段额外的文字在`description`的最后,至此`switch`代码块执行完了。 + +如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。 + +当`switch`代码块执行完后,使用`println`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。 + +>注意: +`fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。 + + +### 带标签的语句(Labeled Statements) + +在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显示地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明`continue`语句想要影响哪一个循环体也会非常有用。 + +为了实现这个目的,你可以使用标签来标记一个循环体或者`switch`代码块,当使用`break`或者`continue`时,带上这个标签,可以控制该标签代表对象的中断或者执行。 + +产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个`while`循环体的语法,同样的规则适用于所有的循环体和`switch`代码块。 + +> `label name`: while `condition` { +> `statements` +> } + +下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中_蛇和梯子_的改编版本。这次,游戏增加了一条额外的规则: + +- 为了获胜,你必须_刚好_落在第 25 个方块中。 + +如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。 + +游戏的棋盘和之前一样: + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) + +值`finalSquare`、`board`、`square`和`diceRoll`的初始化也和之前一样: + +```swift +let finalSquare = 25 +var board = [Int](count: finalSquare + 1, repeatedValue: 0) +board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 +board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 +var square = 0 +var diceRoll = 0 +``` + +这个版本的游戏使用`while`循环体和`switch`方法块来实现游戏的逻辑。`while`循环体有一个标签名`gameLoop`,来表明它是蛇与梯子的主循环。 + +该`while`循环体的条件判断语句是`while square !=finalSquare`,这表明你必须刚好落在方格25中。 + +```swift +gameLoop: while square != finalSquare { + if ++diceRoll == 7 { diceRoll = 1 } + switch square + diceRoll { + case finalSquare: + // 到达最后一个方块,游戏结束 + break gameLoop + case let newSquare where newSquare > finalSquare: + // 超出最后一个方块,再掷一次骰子 + continue gameLoop + default: + // 本次移动有效 + square += diceRoll + square += board[square] + } +} +println("Game over!") +``` + +每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。 + +- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,游戏结束。 +- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环的迭代,开始下一次循环迭代。 +- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到`while`循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代。 + +>注意: +如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。 +同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。 diff --git a/source/chapter2/18_Type_Casting.md b/source/chapter2/18_Type_Casting.md index 7ba5f7cf..4fe2b437 100755 --- a/source/chapter2/18_Type_Casting.md +++ b/source/chapter2/18_Type_Casting.md @@ -150,7 +150,7 @@ for item in library { Swift为不确定类型提供了两种特殊类型别名: * `AnyObject`可以代表任何class类型的实例。 -* `Any`可以表示任何类型,除了方法类型(function types)。 +* `Any`可以表示任何类型,包括方法类型(function types)。 > 注意: 只有当你明确的需要它的行为和功能时才使用`Any`和`AnyObject`。在你的代码里使用你期望的明确的类型总是更好的。 diff --git a/source/chapter2/22_Generics.md b/source/chapter2/22_Generics.md index 12b40044..462196ee 100755 --- a/source/chapter2/22_Generics.md +++ b/source/chapter2/22_Generics.md @@ -1,518 +1,518 @@ - -> 翻译:[takalard](https://github.com/takalard) -> 校对:[lifedim](https://github.com/lifedim) - -# 泛型 - ------- - -本页包含内容: - -- [泛型所解决的问题](#the_problem_that_generics_solve) -- [泛型函数](#generic_functions) -- [类型参数](#type_parameters) -- [命名类型参数](#naming_type_parameters) -- [泛型类型](#generic_types) -- [类型约束](#type_constraints) -- [关联类型](#associated_types) -- [`Where`语句](#where_clauses) - -*泛型代码*可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。 - -泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的。事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已。例如,Swift 的数组和字典类型都是泛型集。你可以创建一个`Int`数组,也可创建一个`String`数组,或者甚至于可以是任何其他 Swift 的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。 - - -## 泛型所解决的问题 - -这里是一个标准的,非泛型函数`swapTwoInts`,用来交换两个Int值: - -```swift -func swapTwoInts(inout a: Int, inout b: Int) { - let temporaryA = a - a = b - b = temporaryA -} -``` - -这个函数使用写入读出(in-out)参数来交换`a`和`b`的值,请参考[写入读出参数](../chapter2/06_Functions.html)。 - -`swapTwoInts`函数可以交换`b`的原始值到`a`,也可以交换a的原始值到`b`,你可以调用这个函数交换两个`Int`变量值: - -```swift -var someInt = 3 -var anotherInt = 107 -swapTwoInts(&someInt, &anotherInt) -println("someInt is now \(someInt), and anotherInt is now \(anotherInt)") -// 输出 "someInt is now 107, and anotherInt is now 3" -``` - - -`swapTwoInts`函数是非常有用的,但是它只能交换`Int`值,如果你想要交换两个`String`或者`Double`,就不得不写更多的函数,如 `swapTwoStrings`和`swapTwoDoublesfunctions `,如同如下所示: - -```swift -func swapTwoStrings(inout a: String, inout b: String) { - let temporaryA = a - a = b - b = temporaryA -} - -func swapTwoDoubles(inout a: Double, inout b: Double) { - let temporaryA = a - a = b - b = temporaryA -} -``` - -你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles`函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是`Int`、`String`和`Double`。 - -但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。) - ->注意: -在所有三个函数中,`a`和`b`的类型是一样的。如果`a`和`b`不是相同的类型,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个`String`类型的变量和一个`Double`类型的变量互相交换值。如果一定要做,Swift 将报编译错误。 - - -## 泛型函数 - -`泛型函数`可以工作于任何类型,这里是一个上面`swapTwoInts`函数的泛型版本,用于交换两个值: - -```swift -func swapTwoValues(inout a: T, inout b: T) { - let temporaryA = a - a = b - b = temporaryA -} -``` - -`swapTwoValues`函数主体和`swapTwoInts`函数是一样的,它只在第一行稍微有那么一点点不同于`swapTwoInts`,如下所示: - -```swift -func swapTwoInts(inout a: Int, inout b: Int) -func swapTwoValues(inout a: T, inout b: T) -``` - - -这个函数的泛型版本使用了占位类型名字(通常此情况下用字母`T`来表示)来代替实际类型名(如`Int`、`String`或`Double`)。占位类型名没有提示`T`必须是什么类型,但是它提示了`a`和`b`必须是同一类型`T`,而不管`T`表示什么类型。只有`swapTwoValues`函数在每次调用时所传入的实际类型才能决定`T`所代表的类型。 - -另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的(``)。这个尖括号告诉 Swift 那个`T`是`swapTwoValues`函数所定义的一个类型。因为`T`是一个占位命名类型,Swift 不会去查找命名为T的实际类型。 - -`swapTwoValues`函数除了要求传入的两个任何类型值是同一类型外,也可以作为`swapTwoInts`函数被调用。每次`swapTwoValues`被调用,T所代表的类型值都会传给函数。 - -在下面的两个例子中,`T`分别代表`Int`和`String`: - -```swift -var someInt = 3 -var anotherInt = 107 -swapTwoValues(&someInt, &anotherInt) -// someInt is now 107, and anotherInt is now 3 -``` - -```swift -var someString = "hello" -var anotherString = "world" -swapTwoValues(&someString, &anotherString) -// someString is now "world", and anotherString is now "hello" -``` - - ->注意 -上面定义的函数`swapTwoValues`是受`swap`函数启发而实现的。`swap`函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似`swapTwoValues`函数的功能,你可以使用已存在的交换函数`swap`函数。 - - -## 类型参数 - -在上面的`swapTwoValues`例子中,占位类型`T`是一种类型参数的示例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(如``)。 - -一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如`swapTwoValues`函数中的参数`a`和`b`),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换(在上面`swapTwoValues`例子中,当函数第一次被调用时,`T`被`Int`替换,第二次调用时,被`String`替换。)。 - -你可支持多个类型参数,命名在尖括号中,用逗号分开。 - - -## 命名类型参数 - -在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的`swapTwoValues`泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母`T`来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。 - -如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift 字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为`KeyType`和`ValueType`,用来记住它们在你的泛型代码中的作用。 - ->注意 -请始终使用大写字母开头的驼峰式命名法(例如`T`和`KeyType`)来给类型参数命名,以表明它们是类型的占位符,而非类型值。 - - -## 泛型类型 - - -通常在泛型函数中,Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同`Array`和`Dictionary`的用法。 - -这部分向你展示如何写一个泛型集类型--`Stack`(栈)。一个栈是一系列值域的集合,和`Array`(数组)类似,但其是一个比 Swift 的`Array`类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同*push*一个新值进栈)。同样的一个栈也只能从末端移除项(如同*pop*一个值出栈)。 - ->注意 -栈的概念已被`UINavigationController`类使用来模拟试图控制器的导航结构。你通过调用`UINavigationController`的`pushViewController:animated:`方法来为导航栈添加(add)新的试图控制器;而通过`popViewControllerAnimated:`的方法来从导航栈中移除(pop)某个试图控制器。每当你需要一个严格的`后进先出`方式来管理集合,堆栈都是最实用的模型。 - -下图展示了一个栈的压栈(push)/出栈(pop)的行为: - -![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png) - -1. 现在有三个值在栈中; -2. 第四个值“pushed”到栈的顶部; -3. 现在有四个值在栈中,最近的那个在顶部; -4. 栈中最顶部的那个项被移除,或称之为“popped”; -5. 移除掉一个值后,现在栈又重新只有三个值。 - -这里展示了如何写一个非泛型版本的栈,`Int`值型的栈: - -```swift -struct IntStack { - var items = Int[]() - mutating func push(item: Int) { - items.append(item) - } - mutating func pop() -> Int { - return items.removeLast() - } -} -``` - -这个结构体在栈中使用一个`Array`性质的`items`存储值。`Stack`提供两个方法:`push`和`pop`,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或*转换*)结构体的`items`数组。 - -上面所展现的`IntStack`类型只能用于`Int`值,不过,其对于定义一个泛型`Stack`类(可以处理*任何*类型值的栈)是非常有用的。 - -这里是一个相同代码的泛型版本: - - -```swift -struct Stack { - var items = [T]() - mutating func push(item: T) { - items.append(item) - } - mutating func pop() -> T { - return items.removeLast() - } -} -``` - - -注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的占位类型参数为T代替了实际`Int`类型。这种类型参数包含在一对尖括号里(``),紧随在结构体名字后面。 - -`T`定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,`T`在如下三个地方被用作节点: - -- 创建一个名为`items`的属性,使用空的T类型值数组对其进行初始化; -- 指定一个包含一个参数名为`item`的`push`方法,该参数必须是T类型; -- 指定一个`pop`方法的返回值,该返回值将是一个T类型值。 - -当创建一个新单例并初始化时, 通过用一对紧随在类型名后的尖括号里写出实际指定栈用到类型,创建一个`Stack`实例,同创建`Array`和`Dictionary`一样: - -```swift -var stackOfStrings = Stack() -stackOfStrings.push("uno") -stackOfStrings.push("dos") -stackOfStrings.push("tres") -stackOfStrings.push("cuatro") -// 现在栈已经有4个string了 -``` - -下图将展示`stackOfStrings`如何`push`这四个值进栈的过程: - -![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png) - -从栈中`pop`并移除值"cuatro": - -```swift -let fromTheTop = stackOfStrings.pop() -// fromTheTop is equal to "cuatro", and the stack now contains 3 strings -``` - -下图展示了如何从栈中pop一个值的过程: -![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png) - -由于`Stack`是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同`Array`和`Dictionary`。 - - -##类型约束 - -`swapTwoValues`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。 - -例如,Swift 的`Dictionary`类型对作用于其键的类型做了些限制。在[字典](../chapter2/04_Collection_Types.html)的描述中,字典的键类型必须是*可哈希*,也就是说,必须有一种方法可以使其被唯一的表示。`Dictionary`之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,`Dictionary`既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。 - -这个需求强制加上一个类型约束作用于`Dictionary`的键上,当然其键类型必须遵循`Hashable`协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如`String`,`Int`, `Double`和 `Bool`)默认都是可哈希。 - -当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如`可哈希`具有的类型特征是根据它们概念特征来界定的,而不是它们的直接类型特征。 - -### 类型约束语法 - -你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同): - -```swift -func someFunction(someT: T, someU: U) { - // function body goes here -} -``` - -上面这个假定函数有两个类型参数。第一个类型参数`T`,有一个需要`T`必须是`SomeClass`子类的类型约束;第二个类型参数`U`,有一个需要`U`必须遵循`SomeProtocol`协议的类型约束。 - -### 类型约束行为 - -这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`: - -```swift -func findStringIndex(array: [String], valueToFind: String) -> Int? { - for (index, value) in enumerate(array) { - if value == valueToFind { - return index - } - } - return nil -} -``` - - -`findStringIndex`函数可以作用于查找一字符串数组中的某个字符串: - -```swift -let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] -if let foundIndex = findStringIndex(strings, "llama") { - println("The index of llama is \(foundIndex)") -} -// 输出 "The index of llama is 2" -``` - -如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数`findIndex`,用某个类型`T`值替换掉提到的字符串。 - -这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明: - -```swift -func findIndex(array: T[], valueToFind: T) -> Int? { - for (index, value) in enumerate(array) { - if value == valueToFind { - return index - } - } - return nil -} -``` - -上面所写的函数不会编译。这个问题的位置在等式的检查上,`“if value == valueToFind”`。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型`T`,当你试图编译这部分代码时估计会出现相应的错误。 - -不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持`Equatable`协议。 - -任何`Equatable`类型都可以安全的使用在`findIndex`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分: - -```swift -func findIndex(array: T[], valueToFind: T) -> Int? { - for (index, value) in enumerate(array) { - if value == valueToFind { - return index - } - } - return nil -} -``` - - -`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。 - -`findIndex`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`: - -```swift -let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3) -// doubleIndex is an optional Int with no value, because 9.3 is not in the array -let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") -// stringIndex is an optional Int containing a value of 2 -``` - - -##关联类型(Associated Types) - -当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为`typealias`关键字。 - -### 关联类型行为 - -这里是一个`Container`协议的例子,定义了一个ItemType关联类型: - -```swift -protocol Container { - typealias ItemType - mutating func append(item: ItemType) - var count: Int { get } - subscript(i: Int) -> ItemType { get } -} -``` - -`Container`协议定义了三个任何容器必须支持的兼容要求: - -- 必须可能通过`append`方法添加一个新item到容器里; -- 必须可能通过使用`count`属性获取容器里items的数量,并返回一个`Int`值; -- 必须可能通过容器的`Int`索引值下标可以检索到每一个item。 - -这个协议没有指定容器里item是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。 - -任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的items可以加进容器里,必须明确可以通过其下标返回item类型。 - -为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。 - -为了达到此目的,`Container`协议声明了一个ItemType的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别Container中Items类型的方法,并且用于`append`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。 - -这里是一个早前IntStack类型的非泛型版本,遵循Container协议: - -```swift -struct IntStack: Container { - // IntStack的原始实现 - var items = [Int]() - mutating func push(item: Int) { - items.append(item) - } - mutating func pop() -> Int { - return items.removeLast() - } - // 遵循Container协议的实现 - typealias ItemType = Int - mutating func append(item: Int) { - self.push(item) - } - var count: Int { - return items.count - } - subscript(i: Int) -> Int { - return items[i] - } -} -``` - - -`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。 - -此外,`IntStack`指定了`Container`的实现,适用的ItemType被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。 - -感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append`方法的item参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道ItemType使用的是何种类型。 - -你也可以生成遵循`Container`协议的泛型`Stack`类型: - -```swift -struct Stack: Container { - // original Stack implementation - var items = [T]() - mutating func push(item: T) { - items.append(item) - } - mutating func pop() -> T { - return items.removeLast() - } - // conformance to the Container protocol - mutating func append(item: T) { - self.push(item) - } - var count: Int { - return items.count - } - subscript(i: Int) -> T { - return items[i] - } -} -``` - -这个时候,占位类型参数`T`被用作`append`方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。 - - -### 扩展一个存在的类型为一指定关联类型 - -在[使用扩展来添加协议兼容性](../chapter2/21_Protocols.html)中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。 - -Swift的`Array`已经提供`append`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[使用扩展来声明协议的采纳](../chapter2/21_Protocols.html)中有描述这样一个实现一个空扩展的行为: - -```swift -extension Array: Container {} -``` - -如同上面的泛型`Stack`类型一样,`Array的append`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。 - - -## Where 语句 - -[类型约束](#type_constraints)能够确保类型符合泛型函数或类的定义约束。 - -对关联类型定义约束是非常有用的。你可以在参数列表中通过*where*语句定义参数的约束。一个`where`语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个`where`语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。 - -下面的例子定义了一个名为`allItemsMatch`的泛型函数,用来检查两个`Container`实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回一个为`true`的`Boolean`值,反之则为`false`。 - -被检查的两个`Container`可以不是相同类型的容器(虽然它们可以是),但它们确实拥有相同类型的元素。这个需求通过一个类型约束和`where`语句结合来表示: - -```swift -func allItemsMatch< - C1: Container, C2: Container - where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> - (someContainer: C1, anotherContainer: C2) -> Bool { - - // 检查两个Container的元素个数是否相同 - if someContainer.count != anotherContainer.count { - return false - } - - // 检查两个Container相应位置的元素彼此是否相等 - for i in 0..() -stackOfStrings.push("uno") -stackOfStrings.push("dos") -stackOfStrings.push("tres") - -var arrayOfStrings = ["uno", "dos", "tres"] - -if allItemsMatch(stackOfStrings, arrayOfStrings) { - println("All items match.") -} else { - println("Not all items match.") -} -// 输出 "All items match." -``` - - 上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组是不同的类型,但它们都遵循`Container`协议,而且它们都包含同样的类型值。因此你可以调用`allItemsMatch`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch`函数正确的显示了所有的这两个容器的`items`匹配。 - - [1]: ../chapter2/06_Functions.html - [2]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png - [3]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png - [4]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png - [5]: ../chapter2/04_Collection_Types.html - [6]: ../chapter2/21_Protocols.html - [7]: ../chapter2/21_Protocols.html - [8]: #type_constraints + +> 翻译:[takalard](https://github.com/takalard) +> 校对:[lifedim](https://github.com/lifedim) + +# 泛型 + +------ + +本页包含内容: + +- [泛型所解决的问题](#the_problem_that_generics_solve) +- [泛型函数](#generic_functions) +- [类型参数](#type_parameters) +- [命名类型参数](#naming_type_parameters) +- [泛型类型](#generic_types) +- [类型约束](#type_constraints) +- [关联类型](#associated_types) +- [`Where`语句](#where_clauses) + +*泛型代码*可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。 + +泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的。事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已。例如,Swift 的数组和字典类型都是泛型集。你可以创建一个`Int`数组,也可创建一个`String`数组,或者甚至于可以是任何其他 Swift 的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。 + + +## 泛型所解决的问题 + +这里是一个标准的,非泛型函数`swapTwoInts`,用来交换两个Int值: + +```swift +func swapTwoInts(inout a: Int, inout b: Int) { + let temporaryA = a + a = b + b = temporaryA +} +``` + +这个函数使用写入读出(in-out)参数来交换`a`和`b`的值,请参考[写入读出参数](../chapter2/06_Functions.html)。 + +`swapTwoInts`函数可以交换`b`的原始值到`a`,也可以交换a的原始值到`b`,你可以调用这个函数交换两个`Int`变量值: + +```swift +var someInt = 3 +var anotherInt = 107 +swapTwoInts(&someInt, &anotherInt) +println("someInt is now \(someInt), and anotherInt is now \(anotherInt)") +// 输出 "someInt is now 107, and anotherInt is now 3" +``` + + +`swapTwoInts`函数是非常有用的,但是它只能交换`Int`值,如果你想要交换两个`String`或者`Double`,就不得不写更多的函数,如 `swapTwoStrings`和`swapTwoDoublesfunctions `,如同如下所示: + +```swift +func swapTwoStrings(inout a: String, inout b: String) { + let temporaryA = a + a = b + b = temporaryA +} + +func swapTwoDoubles(inout a: Double, inout b: Double) { + let temporaryA = a + a = b + b = temporaryA +} +``` + +你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles`函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是`Int`、`String`和`Double`。 + +但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。) + +>注意: +在所有三个函数中,`a`和`b`的类型是一样的。如果`a`和`b`不是相同的类型,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个`String`类型的变量和一个`Double`类型的变量互相交换值。如果一定要做,Swift 将报编译错误。 + + +## 泛型函数 + +`泛型函数`可以工作于任何类型,这里是一个上面`swapTwoInts`函数的泛型版本,用于交换两个值: + +```swift +func swapTwoValues(inout a: T, inout b: T) { + let temporaryA = a + a = b + b = temporaryA +} +``` + +`swapTwoValues`函数主体和`swapTwoInts`函数是一样的,它只在第一行稍微有那么一点点不同于`swapTwoInts`,如下所示: + +```swift +func swapTwoInts(inout a: Int, inout b: Int) +func swapTwoValues(inout a: T, inout b: T) +``` + + +这个函数的泛型版本使用了占位类型名字(通常此情况下用字母`T`来表示)来代替实际类型名(如`Int`、`String`或`Double`)。占位类型名没有提示`T`必须是什么类型,但是它提示了`a`和`b`必须是同一类型`T`,而不管`T`表示什么类型。只有`swapTwoValues`函数在每次调用时所传入的实际类型才能决定`T`所代表的类型。 + +另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的(``)。这个尖括号告诉 Swift 那个`T`是`swapTwoValues`函数所定义的一个类型。因为`T`是一个占位命名类型,Swift 不会去查找命名为T的实际类型。 + +`swapTwoValues`函数除了要求传入的两个任何类型值是同一类型外,也可以作为`swapTwoInts`函数被调用。每次`swapTwoValues`被调用,T所代表的类型值都会传给函数。 + +在下面的两个例子中,`T`分别代表`Int`和`String`: + +```swift +var someInt = 3 +var anotherInt = 107 +swapTwoValues(&someInt, &anotherInt) +// someInt is now 107, and anotherInt is now 3 +``` + +```swift +var someString = "hello" +var anotherString = "world" +swapTwoValues(&someString, &anotherString) +// someString is now "world", and anotherString is now "hello" +``` + + +>注意 +上面定义的函数`swapTwoValues`是受`swap`函数启发而实现的。`swap`函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似`swapTwoValues`函数的功能,你可以使用已存在的交换函数`swap`函数。 + + +## 类型参数 + +在上面的`swapTwoValues`例子中,占位类型`T`是一种类型参数的示例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(如``)。 + +一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如`swapTwoValues`函数中的参数`a`和`b`),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换(在上面`swapTwoValues`例子中,当函数第一次被调用时,`T`被`Int`替换,第二次调用时,被`String`替换。)。 + +你可支持多个类型参数,命名在尖括号中,用逗号分开。 + + +## 命名类型参数 + +在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的`swapTwoValues`泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母`T`来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。 + +如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift 字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为`KeyType`和`ValueType`,用来记住它们在你的泛型代码中的作用。 + +>注意 +请始终使用大写字母开头的驼峰式命名法(例如`T`和`KeyType`)来给类型参数命名,以表明它们是类型的占位符,而非类型值。 + + +## 泛型类型 + + +通常在泛型函数中,Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同`Array`和`Dictionary`的用法。 + +这部分向你展示如何写一个泛型集类型--`Stack`(栈)。一个栈是一系列值域的集合,和`Array`(数组)类似,但其是一个比 Swift 的`Array`类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同*push*一个新值进栈)。同样的一个栈也只能从末端移除项(如同*pop*一个值出栈)。 + +>注意 +栈的概念已被`UINavigationController`类使用来模拟试图控制器的导航结构。你通过调用`UINavigationController`的`pushViewController:animated:`方法来为导航栈添加(add)新的试图控制器;而通过`popViewControllerAnimated:`的方法来从导航栈中移除(pop)某个试图控制器。每当你需要一个严格的`后进先出`方式来管理集合,堆栈都是最实用的模型。 + +下图展示了一个栈的压栈(push)/出栈(pop)的行为: + +![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png) + +1. 现在有三个值在栈中; +2. 第四个值“pushed”到栈的顶部; +3. 现在有四个值在栈中,最近的那个在顶部; +4. 栈中最顶部的那个项被移除,或称之为“popped”; +5. 移除掉一个值后,现在栈又重新只有三个值。 + +这里展示了如何写一个非泛型版本的栈,`Int`值型的栈: + +```swift +struct IntStack { + var items = Int[]() + mutating func push(item: Int) { + items.append(item) + } + mutating func pop() -> Int { + return items.removeLast() + } +} +``` + +这个结构体在栈中使用一个`Array`性质的`items`存储值。`Stack`提供两个方法:`push`和`pop`,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或*转换*)结构体的`items`数组。 + +上面所展现的`IntStack`类型只能用于`Int`值,不过,其对于定义一个泛型`Stack`类(可以处理*任何*类型值的栈)是非常有用的。 + +这里是一个相同代码的泛型版本: + + +```swift +struct Stack { + var items = [T]() + mutating func push(item: T) { + items.append(item) + } + mutating func pop() -> T { + return items.removeLast() + } +} +``` + + +注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的占位类型参数为T代替了实际`Int`类型。这种类型参数包含在一对尖括号里(``),紧随在结构体名字后面。 + +`T`定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,`T`在如下三个地方被用作节点: + +- 创建一个名为`items`的属性,使用空的T类型值数组对其进行初始化; +- 指定一个包含一个参数名为`item`的`push`方法,该参数必须是T类型; +- 指定一个`pop`方法的返回值,该返回值将是一个T类型值。 + +当创建一个新单例并初始化时, 通过用一对紧随在类型名后的尖括号里写出实际指定栈用到类型,创建一个`Stack`实例,同创建`Array`和`Dictionary`一样: + +```swift +var stackOfStrings = Stack() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") +stackOfStrings.push("cuatro") +// 现在栈已经有4个string了 +``` + +下图将展示`stackOfStrings`如何`push`这四个值进栈的过程: + +![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png) + +从栈中`pop`并移除值"cuatro": + +```swift +let fromTheTop = stackOfStrings.pop() +// fromTheTop is equal to "cuatro", and the stack now contains 3 strings +``` + +下图展示了如何从栈中pop一个值的过程: +![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png) + +由于`Stack`是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同`Array`和`Dictionary`。 + + +##类型约束 + +`swapTwoValues`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。 + +例如,Swift 的`Dictionary`类型对作用于其键的类型做了些限制。在[字典](../chapter2/04_Collection_Types.html)的描述中,字典的键类型必须是*可哈希*,也就是说,必须有一种方法可以使其被唯一的表示。`Dictionary`之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,`Dictionary`既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。 + +这个需求强制加上一个类型约束作用于`Dictionary`的键上,当然其键类型必须遵循`Hashable`协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如`String`,`Int`, `Double`和 `Bool`)默认都是可哈希。 + +当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如`可哈希`具有的类型特征是根据它们概念特征来界定的,而不是它们的直接类型特征。 + +### 类型约束语法 + +你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同): + +```swift +func someFunction(someT: T, someU: U) { + // function body goes here +} +``` + +上面这个假定函数有两个类型参数。第一个类型参数`T`,有一个需要`T`必须是`SomeClass`子类的类型约束;第二个类型参数`U`,有一个需要`U`必须遵循`SomeProtocol`协议的类型约束。 + +### 类型约束行为 + +这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`: + +```swift +func findStringIndex(array: [String], valueToFind: String) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + + +`findStringIndex`函数可以作用于查找一字符串数组中的某个字符串: + +```swift +let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] +if let foundIndex = findStringIndex(strings, "llama") { + println("The index of llama is \(foundIndex)") +} +// 输出 "The index of llama is 2" +``` + +如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数`findIndex`,用某个类型`T`值替换掉提到的字符串。 + +这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明: + +```swift +func findIndex(array: [T], valueToFind: T) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + +上面所写的函数不会编译。这个问题的位置在等式的检查上,`“if value == valueToFind”`。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型`T`,当你试图编译这部分代码时估计会出现相应的错误。 + +不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持`Equatable`协议。 + +任何`Equatable`类型都可以安全的使用在`findIndex`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分: + +```swift +func findIndex(array: T[], valueToFind: T) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + + +`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。 + +`findIndex`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`: + +```swift +let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3) +// doubleIndex is an optional Int with no value, because 9.3 is not in the array +let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") +// stringIndex is an optional Int containing a value of 2 +``` + + +##关联类型(Associated Types) + +当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为`typealias`关键字。 + +### 关联类型行为 + +这里是一个`Container`协议的例子,定义了一个ItemType关联类型: + +```swift +protocol Container { + typealias ItemType + mutating func append(item: ItemType) + var count: Int { get } + subscript(i: Int) -> ItemType { get } +} +``` + +`Container`协议定义了三个任何容器必须支持的兼容要求: + +- 必须可能通过`append`方法添加一个新item到容器里; +- 必须可能通过使用`count`属性获取容器里items的数量,并返回一个`Int`值; +- 必须可能通过容器的`Int`索引值下标可以检索到每一个item。 + +这个协议没有指定容器里item是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。 + +任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的items可以加进容器里,必须明确可以通过其下标返回item类型。 + +为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。 + +为了达到此目的,`Container`协议声明了一个ItemType的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别Container中Items类型的方法,并且用于`append`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。 + +这里是一个早前IntStack类型的非泛型版本,遵循Container协议: + +```swift +struct IntStack: Container { + // IntStack的原始实现 + var items = [Int]() + mutating func push(item: Int) { + items.append(item) + } + mutating func pop() -> Int { + return items.removeLast() + } + // 遵循Container协议的实现 + typealias ItemType = Int + mutating func append(item: Int) { + self.push(item) + } + var count: Int { + return items.count + } + subscript(i: Int) -> Int { + return items[i] + } +} +``` + + +`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。 + +此外,`IntStack`指定了`Container`的实现,适用的ItemType被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。 + +感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append`方法的item参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道ItemType使用的是何种类型。 + +你也可以生成遵循`Container`协议的泛型`Stack`类型: + +```swift +struct Stack: Container { + // original Stack implementation + var items = [T]() + mutating func push(item: T) { + items.append(item) + } + mutating func pop() -> T { + return items.removeLast() + } + // conformance to the Container protocol + mutating func append(item: T) { + self.push(item) + } + var count: Int { + return items.count + } + subscript(i: Int) -> T { + return items[i] + } +} +``` + +这个时候,占位类型参数`T`被用作`append`方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。 + + +### 扩展一个存在的类型为一指定关联类型 + +在[使用扩展来添加协议兼容性](../chapter2/21_Protocols.html)中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。 + +Swift的`Array`已经提供`append`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[使用扩展来声明协议的采纳](../chapter2/21_Protocols.html)中有描述这样一个实现一个空扩展的行为: + +```swift +extension Array: Container {} +``` + +如同上面的泛型`Stack`类型一样,`Array的append`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。 + + +## Where 语句 + +[类型约束](#type_constraints)能够确保类型符合泛型函数或类的定义约束。 + +对关联类型定义约束是非常有用的。你可以在参数列表中通过*where*语句定义参数的约束。一个`where`语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个`where`语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。 + +下面的例子定义了一个名为`allItemsMatch`的泛型函数,用来检查两个`Container`实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回一个为`true`的`Boolean`值,反之则为`false`。 + +被检查的两个`Container`可以不是相同类型的容器(虽然它们可以是),但它们确实拥有相同类型的元素。这个需求通过一个类型约束和`where`语句结合来表示: + +```swift +func allItemsMatch< + C1: Container, C2: Container + where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> + (someContainer: C1, anotherContainer: C2) -> Bool { + + // 检查两个Container的元素个数是否相同 + if someContainer.count != anotherContainer.count { + return false + } + + // 检查两个Container相应位置的元素彼此是否相等 + for i in 0..() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") + +var arrayOfStrings = ["uno", "dos", "tres"] + +if allItemsMatch(stackOfStrings, arrayOfStrings) { + println("All items match.") +} else { + println("Not all items match.") +} +// 输出 "All items match." +``` + + 上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组是不同的类型,但它们都遵循`Container`协议,而且它们都包含同样的类型值。因此你可以调用`allItemsMatch`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch`函数正确的显示了所有的这两个容器的`items`匹配。 + + [1]: ../chapter2/06_Functions.html + [2]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png + [3]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png + [4]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png + [5]: ../chapter2/04_Collection_Types.html + [6]: ../chapter2/21_Protocols.html + [7]: ../chapter2/21_Protocols.html + [8]: #type_constraints diff --git a/source/theme/style/css/xcode.css b/source/theme/style/css/xcode.css new file mode 100644 index 00000000..1cf3b0ef --- /dev/null +++ b/source/theme/style/css/xcode.css @@ -0,0 +1,159 @@ +/* + +XCode style (c) Angel Garcia + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; + color: black; +} + +.hljs-comment, +.hljs-template_comment, +.hljs-javadoc { + color: #006a00; +} + +.hljs-keyword, +.hljs-literal, +.nginx .hljs-title { + color: #aa0d91; +} +.method, +.hljs-list .hljs-title, +.hljs-tag .hljs-title, +.setting .hljs-value, +.hljs-winutils, +.tex .hljs-command, +.http .hljs-title, +.hljs-request, +.hljs-status { + color: #008; +} + +.hljs-envvar, +.tex .hljs-special { + color: #660; +} + +.hljs-string { + color: #c41a16; +} +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.hljs-regexp { + color: #080; +} + +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.ini .hljs-title, +.hljs-shebang, +.hljs-prompt, +.hljs-hexcolor, +.hljs-rules .hljs-value, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-number, +.css .hljs-function, +.clojure .hljs-title, +.clojure .hljs-built_in, +.hljs-function .hljs-title, +.coffeescript .hljs-attribute { + color: #1c00cf; +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.smalltalk .hljs-class, +.hljs-javadoctag, +.hljs-yardoctag, +.hljs-phpdoc, +.hljs-typename, +.hljs-tag .hljs-attribute, +.hljs-doctype, +.hljs-class .hljs-id, +.hljs-built_in, +.setting, +.hljs-params, +.clojure .hljs-attribute { + color: #5c2699; +} + +.hljs-variable { + color: #3f6e74; +} +.css .hljs-tag, +.hljs-rules .hljs-property, +.hljs-pseudo, +.hljs-subst { + color: #000; +} + +.css .hljs-class, +.css .hljs-id { + color: #9b703f; +} + +.hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; +} + +.hljs-rules .hljs-keyword { + color: #c5af75; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9b859d; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #643820; +} + +.tex .hljs-formula { + background-color: #eee; + font-style: italic; +} + +.diff .hljs-header, +.hljs-chunk { + color: #808080; + font-weight: bold; +} + +.diff .hljs-change { + background-color: #bccff9; +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-comment .hljs-yardoctag { + font-weight: bold; +} + +.method .hljs-id { + color: #000; +} diff --git a/source/theme/style/js/highlight.pack.js b/source/theme/style/js/highlight.pack.js new file mode 100644 index 00000000..ed7ab519 --- /dev/null +++ b/source/theme/style/js/highlight.pack.js @@ -0,0 +1 @@ +var hljs=new function(){function j(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function h(w,x){var v=w&&w.exec(x);return v&&v.index==0}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^lang(uage)?-/,"")});return v.filter(function(x){return i(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=j(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+j(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};var E=function(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})};if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b="\\b("+D.bK.split(" ").join("|")+")\\b"}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?("+F.b+")\\.?":F.b}).concat([D.tE,D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T";U+=Z+'">';return U+X+Y}function N(){if(!I.k){return j(C)}var T="";var W=0;I.lR.lastIndex=0;var U=I.lR.exec(C);while(U){T+=j(C.substr(W,U.index-W));var V=E(I,U);if(V){H+=V[1];T+=w(V[0],j(U[0]))}else{T+=j(U[0])}W=I.lR.lastIndex;U=I.lR.exec(C)}return T+j(C.substr(W))}function F(){if(I.sL&&!f[I.sL]){return j(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):e(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=j(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=j(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"")+'"')}C+=X;return X.length||1}var M=i(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D+=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:j(L)}}else{throw O}}}function e(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:j(y)};var w=v;x.forEach(function(z){if(!i(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function g(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"
")}return v}function p(z){var y=b.useBR?z.innerHTML.replace(/\n/g,"").replace(/
|
]*>/g,"\n").replace(/<[^>]*>/g,""):z.textContent;var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):e(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=g(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function d(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function k(){return Object.keys(f)}function i(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=e;this.fixMarkup=g;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=d;this.listLanguages=k;this.getLanguage=i;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/};this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]};this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]};this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0};this.RM={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("cs",function(b){var a="abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async await ascending descending from get group into join let orderby partial select set value var where yield";return{aliases:["csharp"],k:a,i:/::/,c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:""}]}]},b.CLCM,b.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},b.ASM,b.QSM,b.CNM,{bK:"protected public private internal",e:/[{;=]/,k:a,c:[{bK:"class namespace interface",starts:{c:[b.TM]}},{b:b.IR+"\\s*\\(",rB:true,c:[b.TM]}]}]}});hljs.registerLanguage("ruby",function(f){var j="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var i="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor";var b={cN:"yardoctag",b:"@[A-Za-z]+"};var c={cN:"value",b:"#<",e:">"};var k={cN:"comment",v:[{b:"#",e:"$",c:[b]},{b:"^\\=begin",e:"^\\=end",c:[b],r:10},{b:"^__END__",e:"\\n$"}]};var d={cN:"subst",b:"#\\{",e:"}",k:i};var e={cN:"string",c:[f.BE,d],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:"%[qw]?\\(",e:"\\)"},{b:"%[qw]?\\[",e:"\\]"},{b:"%[qw]?{",e:"}"},{b:"%[qw]?<",e:">"},{b:"%[qw]?/",e:"/"},{b:"%[qw]?%",e:"%"},{b:"%[qw]?-",e:"-"},{b:"%[qw]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]};var a={cN:"params",b:"\\(",e:"\\)",k:i};var h=[e,c,k,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[f.inherit(f.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+f.IR+"::)?"+f.IR}]},k]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[f.inherit(f.TM,{b:j}),a,k]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[e,{b:j}],r:0},{cN:"symbol",b:f.UIR+"(\\!|\\?)?:",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+f.RSR+")\\s*",c:[c,k,{cN:"regexp",c:[f.BE,d],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];d.c=h;a.c=h;var g=[{r:1,cN:"output",b:"^\\s*=> ",e:"$",rB:true,c:[{cN:"status",b:"^\\s*=>"},{b:" ",e:"$",c:h}]},{r:1,cN:"input",b:"^[^ ][^=>]*>+ ",e:"$",rB:true,c:[{cN:"prompt",b:"^[^ ][^=>]*>+"},{b:" ",e:"$",c:h}]}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:i,c:g.concat(h)}});hljs.registerLanguage("diff",function(a){return{aliases:["patch"],c:[{cN:"chunk",r:10,v:[{b:/^\@\@ +\-\d+,\d+ +\+\d+,\d+ +\@\@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("javascript",function(a){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBCM,a.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:true,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("css",function(a){var b="[a-zA-Z-][a-zA-Z0-9_-]*";var c={cN:"function",b:b+"\\(",rB:true,eE:true,e:"\\("};return{cI:true,i:"[=/|']",c:[a.CBCM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:true,eE:true,r:0,c:[c,a.ASM,a.QSM,a.CSSNM]}]},{cN:"tag",b:b,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBCM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[c,a.CSSNM,a.QSM,a.ASM,a.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});hljs.registerLanguage("http",function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}});hljs.registerLanguage("java",function(b){var a="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws";return{aliases:["jsp"],k:a,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}],r:10},b.CLCM,b.CBCM,b.ASM,b.QSM,{bK:"protected public private",e:/[{;=]/,k:a,c:[{cN:"class",bK:"class interface",eW:true,eE:true,i:/[:"\[\]]/,c:[{bK:"extends implements",r:10},b.UTM]},{b:b.UIR+"\\s*\\(",rB:true,c:[b.UTM]}]},b.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("swift",function(a){var e={keyword:"class deinit enum extension func import init let protocol static struct subscript typealias var break case continue default do else fallthrough if in for return switch where while as dynamicType is new super self Self Type __COLUMN__ __FILE__ __FUNCTION__ __LINE__ associativity didSet get infix inout left mutating none nonmutating operator override postfix precedence prefix right set unowned unowned safe unsafe weak willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue assert bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal false filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced join lexicographicalCompare map max maxElement min minElement nil numericCast partition posix print println quickSort reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith strideof strideofValue swap swift toString transcode true underestimateCount unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafePointers withVaList"};var g={cN:"type",b:"\\b[A-Z][\\w']*",r:0};var b={cN:"comment",b:"/\\*",e:"\\*/",c:[a.PWM,"self"]};var c={cN:"subst",b:/\\\(/,e:"\\)",k:e,c:[]};var f={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0};var d=a.inherit(a.QSM,{c:[c,a.BE]});c.c=[f];return{k:e,c:[d,a.CLCM,b,g,f,{cN:"func",bK:"func",eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/,i:/\(/}),{cN:"generics",b:/\/,i:/\>/},{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBCM],i:/["']/}],i:/\[|%/},{cN:"class",k:"struct protocol class extension enum",b:"(struct|protocol|class(?! (func|var))|extension|enum)",e:"\\{",eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{cN:"preprocessor",b:"(@assignment|@class_protocol|@exported|@final|@lazy|@noreturn|@NSCopying|@NSManaged|@objc|@optional|@required|@auto_closure|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix)"},]}});hljs.registerLanguage("php",function(b){var e={cN:"variable",b:"(\\$|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var a={cN:"preprocessor",b:/<\?(php)?|\?>/};var c={cN:"string",c:[b.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},b.inherit(b.ASM,{i:null}),b.inherit(b.QSM,{i:null})]};var d={v:[b.BNM,b.CNM]};return{aliases:["php3","php4","php5","php6"],cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[b.CLCM,b.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"},a]},{cN:"comment",b:"__halt_compiler.+?;",eW:true,k:"__halt_compiler",l:b.UIR},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[b.BE]},a,e,{cN:"function",bK:"function",e:/[;{]/,eE:true,i:"\\$|\\[|%",c:[b.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",e,b.CBCM,c,d]}]},{cN:"class",bK:"class interface",e:"{",eE:true,i:/[:\(\$"]/,c:[{bK:"extends implements",r:10},b.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[b.UTM]},{bK:"use",e:";",c:[b.UTM]},{b:"=>"},c,d]}});hljs.registerLanguage("python",function(a){var f={cN:"prompt",b:/^(>>>|\.\.\.) /};var b={cN:"string",c:[a.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[f],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[f],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},a.ASM,a.QSM]};var d={cN:"number",r:0,v:[{b:a.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:a.CNR+"[lLjJ]?"}]};var e={cN:"params",b:/\(/,e:/\)/,c:["self",f,d,b]};var c={e:/:/,i:/[${=;\n]/,c:[a.UTM,e]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[f,d,b,a.HCM,a.inherit(c,{cN:"function",bK:"def",r:10}),a.inherit(c,{cN:"class",bK:"class"}),{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("sql",function(a){var b={cN:"comment",b:"--",e:"$"};return{cI:true,i:/[<>]/,c:[{cN:"operator",bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate savepoint release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup",e:/;/,eW:true,k:{keyword:"abs absolute acos action add adddate addtime aes_decrypt aes_encrypt after aggregate all allocate alter analyze and any are as asc ascii asin assertion at atan atan2 atn2 authorization authors avg backup before begin benchmark between bin binlog bit_and bit_count bit_length bit_or bit_xor both by cache call cascade cascaded case cast catalog ceil ceiling chain change changed char_length character_length charindex charset check checksum checksum_agg choose close coalesce coercibility collate collation collationproperty column columns columns_updated commit compress concat concat_ws concurrent connect connection connection_id consistent constraint constraints continue contributors conv convert convert_tz corresponding cos cot count count_big crc32 create cross cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime data database databases datalength date_add date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts datetimeoffsetfromparts day dayname dayofmonth dayofweek dayofyear deallocate declare decode default deferrable deferred degrees delayed delete des_decrypt des_encrypt des_key_file desc describe descriptor diagnostics difference disconnect distinct distinctrow div do domain double drop dumpfile each else elt enclosed encode encrypt end end-exec engine engines eomonth errors escape escaped event eventdata events except exception exec execute exists exp explain export_set extended external extract fast fetch field fields find_in_set first first_value floor flush for force foreign format found found_rows from from_base64 from_days from_unixtime full function get get_format get_lock getdate getutcdate global go goto grant grants greatest group group_concat grouping grouping_id gtid_subset gtid_subtract handler having help hex high_priority hosts hour ident_current ident_incr ident_seed identified identity if ifnull ignore iif ilike immediate in index indicator inet6_aton inet6_ntoa inet_aton inet_ntoa infile initially inner innodb input insert install instr intersect into is is_free_lock is_ipv4 is_ipv4_compat is_ipv4_mapped is_not is_not_null is_used_lock isdate isnull isolation join key kill language last last_day last_insert_id last_value lcase lead leading least leaves left len lenght level like limit lines ln load load_file local localtime localtimestamp locate lock log log10 log2 logfile logs low_priority lower lpad ltrim make_set makedate maketime master master_pos_wait match matched max md5 medium merge microsecond mid min minute mod mode module month monthname mutex name_const names national natural nchar next no no_write_to_binlog not now nullif nvarchar oct octet_length of old_password on only open optimize option optionally or ord order outer outfile output pad parse partial partition password patindex percent_rank percentile_cont percentile_disc period_add period_diff pi plugin position pow power pragma precision prepare preserve primary prior privileges procedure procedure_analyze processlist profile profiles public publishingservername purge quarter query quick quote quotename radians rand read references regexp relative relaylog release release_lock rename repair repeat replace replicate reset restore restrict return returns reverse revoke right rlike rollback rollup round row row_count rows rpad rtrim savepoint schema scroll sec_to_time second section select serializable server session session_user set sha sha1 sha2 share show sign sin size slave sleep smalldatetimefromparts snapshot some soname soundex sounds_like space sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sql_variant_property sqlstate sqrt square start starting status std stddev stddev_pop stddev_samp stdev stdevp stop str str_to_date straight_join strcmp string stuff subdate substr substring subtime subtring_index sum switchoffset sysdate sysdatetime sysdatetimeoffset system_user sysutcdatetime table tables tablespace tan temporary terminated tertiary_weights then time time_format time_to_sec timediff timefromparts timestamp timestampadd timestampdiff timezone_hour timezone_minute to to_base64 to_days to_seconds todatetimeoffset trailing transaction translation trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse ucase uncompress uncompressed_length unhex unicode uninstall union unique unix_timestamp unknown unlock update upgrade upped upper usage use user user_resources using utc_date utc_time utc_timestamp uuid uuid_short validate_password_strength value values var var_pop var_samp variables variance varp version view warnings week weekday weekofyear weight_string when whenever where with work write xml xor year yearweek zon",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int integer interval number numeric real serial smallint varchar varying int8 serial8 text"},c:[{cN:"string",b:"'",e:"'",c:[a.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[a.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[a.BE]},a.CNM,a.CBCM,b]},a.CBCM,b]}});hljs.registerLanguage("ini",function(a){return{cI:true,i:/\S/,c:[{cN:"comment",b:";",e:"$"},{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9\\[\\]_-]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:true,k:"on off true false yes no",c:[a.QSM,a.NM],r:0}]}]}});hljs.registerLanguage("perl",function(c){var d="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when";var f={cN:"subst",b:"[$@]\\{",e:"\\}",k:d};var g={b:"->{",e:"}"};var a={cN:"variable",v:[{b:/\$\d/},{b:/[\$\%\@](\^\w\b|#\w+(\:\:\w+)*|{\w+}|\w+(\:\:\w*)*)/},{b:/[\$\%\@][^\s\w{]/,r:0}]};var e={cN:"comment",b:"^(__END__|__DATA__)",e:"\\n$",r:5};var h=[c.BE,f,a];var b=[a,c.HCM,e,{cN:"comment",b:"^\\=\\w",e:"\\=cut",eW:true},g,{cN:"string",c:h,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[c.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[c.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+c.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[c.HCM,e,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[c.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0}];f.c=b;g.c=b;return{aliases:["pl"],k:d,c:b}});hljs.registerLanguage("objectivec",function(a){var d={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"NSString NSDictionary CGRect CGPoint UIButton UILabel UITextView UIWebView MKMapView NSView NSViewController NSWindow NSWindowController NSSet NSUUID NSIndexSet UISegmentedControl NSObject UITableViewDelegate UITableViewDataSource NSThread UIActivityIndicator UITabbar UIToolBar UIBarButtonItem UIImageView NSAutoreleasePool UITableView BOOL NSInteger CGFloat NSException NSLog NSMutableString NSMutableArray NSMutableDictionary NSURL NSIndexPath CGSize UITableViewCell UIView UIViewController UINavigationBar UINavigationController UITabBarController UIPopoverController UIPopoverControllerDelegate UIImage NSNumber UISearchBar NSFetchedResultsController NSFetchedResultsChangeType UIScrollView UIScrollViewDelegate UIEdgeInsets UIColor UIFont UIApplication NSNotFound NSNotificationCenter NSNotification UILocalNotification NSBundle NSFileManager NSTimeInterval NSDate NSCalendar NSUserDefaults UIWindow NSRange NSArray NSError NSURLRequest NSURLConnection UIInterfaceOrientation MPMoviePlayerController dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"};var c=/[a-zA-Z@][a-zA-Z0-9_]*/;var b="@interface @class @protocol @implementation";return{aliases:["m","mm","objc","obj-c"],k:d,l:c,i:""}]}]},{cN:"class",b:"("+b.split(" ").join("|")+")\\b",e:"({|$)",eE:true,k:b,l:c,c:[a.UTM]},{cN:"variable",b:"\\."+a.UIR,r:0}]}});hljs.registerLanguage("coffeescript",function(c){var b={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",reserved:"case default function var void with const let enum export import native __hasProp __extends __slice __bind __indexOf",built_in:"npm require console print module global window document"};var a="[A-Za-z$_][0-9A-Za-z$_]*";var f=c.inherit(c.TM,{b:a});var e={cN:"subst",b:/#\{/,e:/}/,k:b};var d=[c.BNM,c.inherit(c.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[c.BE]},{b:/'/,e:/'/,c:[c.BE]},{b:/"""/,e:/"""/,c:[c.BE,e]},{b:/"/,e:/"/,c:[c.BE,e]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[e,c.HCM]},{b:"//[gim]*",r:0},{b:"/\\S(\\\\.|[^\\n])*?/[gim]*(?=\\s|\\W|$)"}]},{cN:"property",b:"@"+a},{b:"`",e:"`",eB:true,eE:true,sL:"javascript"}];e.c=d;return{aliases:["coffee","cson","iced"],k:b,c:d.concat([{cN:"comment",b:"###",e:"###"},c.HCM,{cN:"function",b:"("+a+"\\s*=\\s*)?(\\(.*\\))?\\s*\\B[-=]>",e:"[-=]>",rB:true,c:[f,{cN:"params",b:"\\(",rB:true,c:[{b:/\(/,e:/\)/,k:b,c:["self"].concat(d)}]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:true,i:/[:="\[\]]/,c:[f]},f]},{cN:"attribute",b:a+":",e:":",rB:true,eE:true,r:0}])}});hljs.registerLanguage("nginx",function(c){var b={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+c.UIR}]};var a={eW:true,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[c.HCM,{cN:"string",c:[c.BE,b],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:true,eE:true},{cN:"regexp",c:[c.BE,b],v:[{b:"\\s\\^",e:"\\s|{|;",rE:true},{b:"~\\*?\\s+",e:"\\s|{|;",rE:true},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},b]};return{aliases:["nginxconf"],c:[c.HCM,{b:c.UIR+"\\s",e:";|{",rB:true,c:[{cN:"title",b:c.UIR,starts:a}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});hljs.registerLanguage("apache",function(a){var b={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:true,c:[a.HCM,{cN:"tag",b:""},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",b]},b,a.QSM]}}],i:/\S/}});hljs.registerLanguage("cpp",function(a){var b={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long throw volatile static protected bool template mutable if public friend do return goto auto void enum else break new extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c","h","c++","h++"],k:b,i:""]',k:"include",i:"\\n"},a.CLCM]},{cN:"stl_container",b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:b,c:["self"]},{b:a.IR+"::"}]}});hljs.registerLanguage("makefile",function(a){var b={cN:"variable",b:/\$\(/,e:/\)/,c:[a.BE]};return{aliases:["mk","mak"],c:[a.HCM,{b:/^\w+\s*\W*=/,rB:true,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:true,starts:{e:/$/,r:0,c:[b]}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,c:[a.QSM,b]}]}}); \ No newline at end of file