校对以后的修改

This commit is contained in:
dzy_PC
2019-01-07 20:58:15 +08:00
13 changed files with 590 additions and 570 deletions

View File

@ -14,7 +14,7 @@ print("Hello, world!")
>
> 最好的体验是把这一章作为 Playground 文件在 Xcode 中打开。 Playgrounds 允许你可以编辑代码并立刻看到输出结果。
>
> [Download Playground](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.playground.zip)
> [Download Playground](https://docs.swift.org/swift-book/GuidedTour/GuidedTour.playground.zip)
<a name="simple_values"></a>
## 简单值

View File

@ -84,7 +84,7 @@ Swift 中所有数值类型都支持了基本的四则*算术运算符*
我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中:
![Art/remainderInteger_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/remainderInteger_2x.png "Art/remainderInteger_2x.png")
![Art/remainderInteger_2x.png](https://docs.swift.org/swift-book/_images/remainderInteger_2x.png "Art/remainderInteger_2x.png")
你可以在 `9` 中放入两个 `4`,那余数是 1用橙色标出

View File

@ -10,7 +10,7 @@ Swift 的 `String` 和 `Character` 类型提供了一种快速且兼容 Unicode
>
> Swift 的 `String` 类型与 Foundation `NSString` 类进行了无缝桥接。Foundation 还对 `String` 进行扩展使其可以访问 `NSString` 类型中定义的方法。这意味着调用那些 `NSString` 的方法,你无需进行任何类型转换。
>
> 更多关于在 Foundation 和 Cocoa 中使用 `String` 的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 4)](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)*。
> 更多关于在 Foundation 和 Cocoa 中使用 `String` 的信息请查看 *[Bridging Between String and NSString](https://developer.apple.com/documentation/swift/string#2919514)*。
<a name="string_literals"></a>
## 字符串字面量
@ -74,7 +74,7 @@ It also ends with a line break.
一个多行字符串字面量能够缩进来匹配周围的代码。关闭引号(`"""`)之前的空白字符串告诉 Swift 编译器其他各行多少空白字符串需要忽略。然而,如果你在某行的前面写的空白字符串超出了关闭引号(`"""`)之前的空白字符串,则超出部分将被包含在多行字符串字面量中。
![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/multilineStringWhitespace_2x.png)
![](https://docs.swift.org/swift-book/_images/multilineStringWhitespace_2x.png)
在上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用空白字符串(源代码缩进)比关闭引号(`"""`之前的空白字符串多所以它的行首将有4个空格。
@ -449,7 +449,7 @@ let newString = String(beginning)
上面的例子,`greeting` 是一个 `String`,意味着它在内存里有一片空间保存字符集。而由于 `beginning``greeting``SubString`,它重用了 `greeting` 的内存空间。相反,`newString` 是一个 `String` —— 它是使用 `SubString` 创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:
![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stringSubstring_2x.png)
![](https://docs.swift.org/swift-book/_images/stringSubstring_2x.png)
> 注意
>

View File

@ -2,7 +2,7 @@
Swift 语言提供 `Arrays``Sets``Dictionaries` 三种基本的*集合类型*用来存储集合数据。数组Arrays是有序数据的集。集合Sets是无序无重复数据的集。字典Dictionaries是无序的键值对的集。
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/CollectionTypes_intro_2x.png)
![](https://docs.swift.org/swift-book/_images/CollectionTypes_intro_2x.png)
Swift 语言中的 `Arrays``Sets``Dictionaries` 中存储的数据值类型必须明确。这意味着我们不能把错误的数据类型插入其中。同时这也说明你完全可以对取回值的类型非常放心。
@ -234,7 +234,7 @@ for item in shoppingList {
如果我们同时需要每个数据项的值和索引值,可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
```swift
for (index, value) in shoppingList. enumerated() {
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}
// Item 1: Six eggs
@ -259,9 +259,9 @@ for (index, value) in shoppingList. enumerated() {
<a name="hash_values_for_set_types"></a>
### 集合类型的哈希值
一个类型为了存储在集合中,该类型必须是*可哈希化*的--也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`
一个类型为了存储在集合中,该类型必须是*可哈希化*的——也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`
Swift 的所有基本类型(比如 `String`,`Int`,`Double``Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。
Swift 的所有基本类型(比如 `String``Int``Double``Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。
> 注意
>
@ -301,7 +301,7 @@ print("letters is of type Set<Character> with \(letters.count) items.")
letters.insert("a")
// letters 现在含有1个 Character 类型的值
letters = []
// letters 现在是一个空的 Set, 但是它依然是 Set<Character> 类型
// letters 现在是一个空的 Set但是它依然是 Set<Character> 类型
```
<a name="creating_a_set_with_an_array_literal"></a>
@ -417,9 +417,9 @@ for genre in favoriteGenres.sorted() {
<a name="fundamental_set_operations"></a>
### 基本集合操作
下面的插图描述了两个集合-`a``b`-以及通过阴影部分的区域显示集合各种操作的结果。
下面的插图描述了两个集合 `a``b`以及通过阴影部分的区域显示集合各种操作的结果。
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png)
![](https://docs.swift.org/swift-book/_images/setVennDiagram_2x.png)
* 使用 `intersection(_:)` 方法根据两个集合中都包含的值创建的一个新的集合。
* 使用 `symmetricDifference(_:)` 方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
@ -433,20 +433,20 @@ let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits. intersection(evenDigits).sorted()
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits. symmetricDifference(singleDigitPrimeNumbers).sorted()
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
```
<a name="set_membership_and_equality"></a>
### 集合成员关系和相等
下面的插图描述了三个集合-`a`,`b``c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。
下面的插图描述了三个集合 `a``b``c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png)
![](https://docs.swift.org/swift-book/_images/setEulerDiagram_2x.png)
* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。
* 使用 `isSubset(of:)` 方法来判断一个集合中的值是否也被包含在另外一个集合中。
@ -619,7 +619,7 @@ airports["APL"] = nil
此外,`removeValue(forKey:)` 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回 `nil`
```swift
if let removedValue = airports. removeValue(forKey: "DUB") {
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary does not contain a value for DUB.")

View File

@ -79,7 +79,7 @@ for tickMark in 0..<minutes {
}
```
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每5分钟作为一个刻度。使用 `stride(from:to:by:)` 函数跳过不需要的标记。
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 `stride(from:to:by:)` 函数跳过不需要的标记。
```swift
let minuteInterval = 5
@ -121,7 +121,7 @@ while condition {
下面的例子来玩一个叫做*蛇和梯子*(也叫做*滑道和梯子*)的小游戏:
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png)
![image](https://docs.swift.org/swift-book/_images/snakesAndLadders_2x.png)
游戏的规则如下:
@ -430,7 +430,7 @@ default:
// 输出 "(1, 1) is inside the box"
```
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png)
![image](https://docs.swift.org/swift-book/_images/coordinateGraphSimple_2x.png)
在上面的例子中,`switch` 语句会判断某个点是否是原点 (0, 0),是否在红色的 x 轴上,是否在橘黄色的 y 轴上是否在一个以原点为中心的4x4的蓝色矩形里或者在这个矩形外面。
@ -456,7 +456,7 @@ case let (x, y):
// 输出 "on the x-axis with an x value of 2"
```
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphMedium_2x.png)
![image](https://docs.swift.org/swift-book/_images/coordinateGraphMedium_2x.png)
在上面的例子中,`switch` 语句会判断某个点是否在红色的 x 轴上,是否在橘黄色的 y 轴上,或者不在坐标轴上。
@ -486,7 +486,7 @@ case let (x, y):
// 输出 "(1, -1) is on the line x == -y"
```
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphComplex_2x.png)
![image](https://docs.swift.org/swift-book/_images/coordinateGraphComplex_2x.png)
在上面的例子中,`switch` 语句会判断某个点是否在绿色的对角线 `x == y` 上,是否在紫色的对角线 `x == -y` 上,或者不在对角线上。
@ -525,11 +525,10 @@ case (let distance, 0), (0, let distance):
default:
print("Not on an axis")
}
// 输出 "On an axis, 9 from the origin"
```
上面的 case 有两个模式:`(let distance, 0)` 匹配了在 x 轴上的值,`(0, let distance)` 匹配了在 y 轴上的值。两个模式都绑定了 `distance`,并且 `distance` 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 `distance`
上面的 case 有两个模式:`(let distance, 0)` 匹配了在 x 轴上的值,`(0, let distance)` 匹配了在 y 轴上的值。两个模式都绑定了 `distance`,并且 `distance` 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 `distance`
<a name="control_transfer_statements"></a>
## 控制转移语句
@ -674,7 +673,7 @@ print(description)
游戏的棋盘和之前一样:
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png)
![image](https://docs.swift.org/swift-book/_images/snakesAndLadders_2x.png)
`finalSquare``board``square``diceRoll` 值被和之前一样的方式初始化:
@ -750,7 +749,7 @@ greet(["name": "Jane", "location": "Cupertino"])
如果 `guard` 语句的条件被满足,则继续执行 `guard` 语句大括号后的代码。将变量或者常量的可选绑定作为 `guard` 语句的条件,都可以保护 `guard` 语句后面的代码。
如果条件不被满足,在 `else` 分支上的代码就会被执行。这个分支必须转移控制以退出 `guard` 语句出现的代码段。它可以用控制转移语句如 `return`,`break`,`continue` 或者 `throw` 做这件事,或者调用一个不返回的方法或函数,例如 `fatalError()`
如果条件不被满足,在 `else` 分支上的代码就会被执行。这个分支必须转移控制以退出 `guard` 语句出现的代码段。它可以用控制转移语句如 `return``break``continue` 或者 `throw` 做这件事,或者调用一个不返回的方法或函数,例如 `fatalError()`
相比于可以实现同样功能的 `if` 语句,按需使用 `guard` 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 `else` 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。

View File

@ -11,7 +11,7 @@ Swift 统一的函数语法非常的灵活,可以用来表示任何函数,
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为*参数*,也可以定义某种类型的值作为函数执行结束时的输出,称为*返回类型*。
每个函数有个*函数名*,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作 *实参* )。函数的实参必须与函数参数表里参数的顺序一致。
每个函数有个*函数名*,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作*实参*)。函数的实参必须与函数参数表里参数的顺序一致。
下面例子中的函数的名字是 `greet(person:)`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回向这个人问候的语句。为了完成这个任务,你需要定义一个输入参数——一个叫做 `person``String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
@ -37,7 +37,7 @@ print(greet(person: "Brian"))
> 注意
>
> `print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名 以及 默认参数值。
> `print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名以及默认参数值。
`greet(person:)` 的函数体中,先定义了一个新的名为 `greeting``String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
@ -111,9 +111,9 @@ greet(person: "Dave")
> 注意
>
> 严格上来说,虽然没有返回值被定义,`greet(person:)` 函数然返回值。没有定义返回类型的函数返回一个特殊的 `Void` 值。它其实是一个空元组,没有任何元素,可以写成 `()`
> 严格地说,即使没有明确定义返回值,该 `greet(Person)` 函数然返回一个值。没有明确定义返回类型的函数返回一个 `Void` 类型特殊值,该值为一个空元组,写成 ()。
调用时,一个函数的返回值可以被忽略
调用函数时,可以忽略该函数的返回值:
```swift
func printAndCount(string: String) -> Int {

View File

@ -155,7 +155,7 @@ print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性 `center` 的值会调用它的 setter 来修改属性 `origin``x``y` 的值,从而实现移动正方形到新的位置。
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
<img src="https://docs.swift.org/swift-book/_images/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
<a name="shorthand_setter_declaration"></a>
### 简化 Setter 声明
@ -354,7 +354,7 @@ print(SomeClass.computedTypeProperty)
下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 `0`,没有一个灯会亮;当声道的音量是 `10`,所有灯点亮。本图中,左声道的音量是 `9`,右声道的音量是 `7`
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
<img src="https://docs.swift.org/swift-book/_images/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
上面所描述的声道模型使用 `AudioChannel` 结构体的实例来表示:

View File

@ -218,7 +218,7 @@ struct LevelTracker {
除了类型属性和类型方法,`LevelTracker` 还监测每个玩家的进度。它用实例属性 `currentLevel` 来监测每个玩家当前的等级。
为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@ discardableResult` 属性,更多关于属性信息,请参考[属性](../chapter3/07_Attributes.html)章节。
为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考[属性](../chapter3/07_Attributes.html)章节。
下面,`Player` 类使用 `LevelTracker` 来监测和更新每个玩家的发展进度:

View File

@ -26,7 +26,7 @@ class Vehicle {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什么也不做-因为车辆不一定会有噪音
// 什么也不做——因为车辆不一定会有噪音
}
}
```
@ -219,8 +219,8 @@ print("AutomaticCar: \(automatic.description)")
<a name="preventing_overrides"></a>
## 防止重写
你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var``final func``final class func`以及 `final subscript`)。
你可以通过把方法,属性或下标标记为 *`final`* 来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var``final func``final class func` 以及 `final subscript`)。
任何试图对带有 `final` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final。
任何试图对带有 `final` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 `final`
可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。

View File

@ -166,7 +166,7 @@ let bodyTemperature = Celsius(37.0)
### 可选属性类型
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它y声明为 `可选类型`。可选类型的属性将自动初始化为 `nil`,表示这个属性是特意在构造过程设置为空。
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 `可选类型`。可选类型的属性将自动初始化为 `nil`,表示这个属性是特意在构造过程设置为空。
下面例子中定义了类 `SurveyQuestion`,它包含一个可选 ` String` 属性 `response`
@ -335,6 +335,7 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
## 类的继承和构造过程
类里面的所有存储型属性——包括所有继承自父类的属性——都*必须*在构造过程中设置初始值。
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们被称为指定构造器和便利构造器。
<a name="designated_initializers_and_convenience_initializers"></a>
@ -500,7 +501,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
在下面的例子中定义了一个叫 Vehicle 的基类。基类中声明了一个存储型属性 numberOfWheels它是默认值为 Int 类型的 0。numberOfWheels 属性用在一个描述车辆特征 String 类型为 descrpiption 的计算型属性中:
在下面的例子中定义了一个叫 `Vehicle` 的基类。基类中声明了一个存储型属性 `numberOfWheels`,它是默认值为 `Int` 类型的 `0``numberOfWheels` 属性用在一个描述车辆特征 `String` 类型为 `descrpiption` 的计算型属性中:
```swift
class Vehicle {
@ -511,8 +512,7 @@ class Vehicle {
}
```
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels``0``Vehicle`
实例:
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels``0``Vehicle` 实例:
```swift
let vehicle = Vehicle()
@ -543,7 +543,7 @@ print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"
```
如果父类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以省略 `super.init()` 的调用在所有父类的存储属性赋值之后
如果父类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以在所有父类的存储属性赋值之后省略 `super.init()` 的调用。
这个例子定义了另一个 `Vehicle` 的子类 `Hoverboard` ,只设置它的 `color` 属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 `super.init()`
@ -864,7 +864,7 @@ if unknownUnit == nil {
<a name="propagation_of_initialization_failure"></a>
### 构造失败的传递
结构体枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
结构体枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。

View File

@ -1,11 +1,51 @@
# 泛型
-------------------
*泛型代码*让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
> 1.0
> 翻译:[takalard](https://github.com/takalard)
> 校对:[lifedim](https://github.com/lifedim)
泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上泛型的使用贯穿了整本语言手册只是你可能没有发现而已。例如Swift 的 `Array``Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。
> 2.0
> 翻译+校对: [SergioChan](https://github.com/SergioChan)
> 2.1
> 校对:[shanks](http://codebuild.me)2015-11-01
> 2.2:翻译+校对:[Lanford](https://github.com/LanfordCai)2016-04-08 [SketchK](https://github.com/SketchK) 2016-05-16
> 3.0:翻译+校对:[chenmingjia](https://github.com/chenmingjia)2016-09-12
> 3.0.1shanks2016-11-13
> 3.1:翻译:[qhd](https://github.com/qhd)2017-04-10
> 4.0
> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
- [泛型解决的问题](#the_problem_that_generics_solve)
- [泛型函数](#generic_functions)
- [类型参数](#type_parameters)
- [命名类型参数](#naming_type_parameters)
- [泛型类型](#generic_types)
- [泛型扩展](#extending_a_generic_type)
- [类型约束](#type_constraints)
- [关联类型](#associated_types)
- [泛型 Where 语句](#where_clauses)
- [具有泛型 where 子句的扩展](#extensions_with_a_generic_where_clause)
- [具有泛型 Where 子句的关联类型](#associated_types_with_a_generic_where_clause)
- [泛型下标](#generic_subscripts)
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,用一种清晰抽象的方式来表达代码的意图。
泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在*语言指南*中使用泛型。例如Swift 的 `Array``Dictionary` 都是泛型集合。你可以创建一个 `Int` 类型数组,也可创建一个 `String` 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。
<a name="the_problem_that_generics_solve"></a>
## 泛型解决的问题
## 泛型解决的问题
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
@ -16,20 +56,19 @@ func swapTwoInts(_ a: inout Int, _ b: inout Int) {
b = temporaryA
}
```
这个函数使用输入输出参数(`inout`)来交换 `a``b` 的值,具体请参考[输入输出参数](./06_Functions.html#in_out_parameters)
这个函数使用输入输出参数(`inout`)来交换 `a` `b`值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。
`swapTwoInts(_:_:)` 函数交换 `b` 的原始值到 `a`,并交换 `a` 的原始值到 `b`。你可以调用这个函数交换两个 `Int` 变量的值:
`swapTwoInts(_:_:)` 函数将 `b` 的原始值换成了 `a`,将 `a`原始值换成了 `b`,你可以调用这个函数来交换两个 `Int` 类型变量:
```swift
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印 someInt is now 107, and anotherInt is now 3
```
// 打印 "someInt is now 107, and anotherInt is now 3"
诚然,`swapTwoInts(_:_:)` 函数挺有用,但是它只能交换 `Int` 值,如果你想要交换两个 `String` 值或者 `Double` 值,就不得不写更多的函数,例如 `swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)`,如下所示:
```
`swapTwoInts(_:_:)` 函数很实用,但它只能作用于 `Int` 类型。如果你想交换两个 `String` 类型值,或者 `Double` 类型值,你必须编写对应的函数,类似下面 `swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 函数:
```swift
func swapTwoStrings(_ a: inout String, _ b: inout String) {
@ -44,19 +83,17 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
b = temporaryA
}
```
你可能注意到 `swapTwoInts(_:_:)``swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int``String``Double`
你可能注意到了,`swapTwoInts(_:_:)``swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int``String``Double`)。
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
> 注意
>
> 在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
<a name="generic_functions"></a>
## 泛型函数
泛型函数可适用于任类型,下面 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本:
泛型函数可适用于任类型,下面是函数 `swapTwoInts(_:_:)` 的泛型版本,命名为 `swapTwoValues(_:_:)`
```swift
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
@ -65,21 +102,24 @@ func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
b = temporaryA
}
```
`swapTwoValues(_:_:)` 的函数主体和 `swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示:
`swapTwoValues(_:_:)``swapTwoInts(_:_:)` 函数体内容相同,它们只在第一行不同,如下所示:
```swift
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
```
这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int``String``Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a``b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才会根据所传入的实际类型决定 `T` 所代表的类型。
-----
泛型函数和非泛型函数的另外一个不同之处,在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T``swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来
泛型版本的函数使用`占位符`类型名(这里叫做 `T` ),而不是 *实际*类型名(例如 `Int``String``Double``占位符`类型名并不关心 `T` 具体的类型,但它要求 `a`` b` 必须是相同的类型,`T` 的实际类型由每次调用 `swapTwoValues(_:_:)` 来决定
在下面的两个例子中,`T` 分别代表 `Int``String`
泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T``swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T `的实际类型。
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T ` 所代表的类型都会由传入的值的类型推断出来。
在下面的两个例子中,`T` 分别代表 ` Int``String`
```swift
var someInt = 3
@ -91,52 +131,53 @@ var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在 "world", and anotherString 现在 "hello"
```
> 注意
>
> 上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
<a name="type_parameters"></a>
## 类型参数
上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T`一个类型参数的例子类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a``b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T``Int` 替换,第二次调用时,被 `String` 替换。)
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
<a name="naming_type_parameters"></a>
## 命名类型参数
大多情况下,类型参数具有一个描述性名字,例如 `Dictionary<Key, Value>` 中的 `Key``Value`,以及 `Array<Element>` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T``U``V`如上面演示 `swapTwoValues(_:_:)` 函数中的 `T` 一样
大多情况下,类型参数具有描述下的名称,例如字典 `Dictionary<Key, Value>` 中的 `Key``Value` 及数组 `Array<Element>` 中的 `Element`,这告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 `T``U``V`如上面演示函数 `swapTwoValues(_:_:)` 中的 `T`
> 注意
>
> 注意
> 请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
<a name="generic_types"></a>
## 泛型类型
除了泛型函数Swift 还允许定义*泛型*类型。这些自定义类、结构体和枚举可以适用于*任何*类型,类似于 `Array``Dictionary`
除了泛型函数Swift 还允许定义*泛型类型*。这些自定义类、结构体和枚举可以适用于*任类型*,类似于 `Array``Dictionary`
这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为*入*栈)。类似的,栈也只能从末端移除元素(称之为*出*栈)。
本节将向你展示如何编写一个名为 `Stack`(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为栈)。类似的,栈也只能从末端移除元素(称之为栈)。
> 注意
>
> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的”后进先出”方式来管理集合,栈都是最实用的模型。
下图展示了一个栈的入栈push和出栈pop的行为
下图展示了入栈push和出栈pop的行为
![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png)
![](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png)
1. 现在有三个值在栈中。
2. 第四个值被压入到栈的顶部。
3. 现在有四个值在栈中,最近入栈的那个值在顶部。
3. 现在栈中有四个值,最近入栈的那个值在顶部。
4. 栈中最顶部的那个值被移除出栈。
5. 一个值移除出栈后,现在栈又只有三个值了。
下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
```swift
struct IntStack {
@ -150,16 +191,16 @@ struct IntStack {
}
```
这个结构体在栈中使用一个名为 `items` `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)``pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
这个结构体在栈中使用一个名为 `items`数组属性来存储值。栈提供了两个方法:`push(_:)``pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理*任意*类型的值。
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
下面是相同代码的泛型版本:
```swift
struct Stack<Element> {
var items = [Element]()
   mutating func push(_ item: Element) {
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
@ -167,16 +208,15 @@ struct Stack<Element> {
}
}
```
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(`<Element>`)。
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(<`Element`>)。
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
+ 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
+ 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
+ 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array``Dictionary` 那样。
由于 `Stack` 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 `Array``Dictionary` 那样。
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`
@ -189,27 +229,27 @@ stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串
```
下图展示了 `stackOfStrings` 如何将这四个值栈:
下图展示了 `stackOfStrings` 如何将这四个值栈:
![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png)
![](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png)
移除并返回栈顶部的值 `"cuatro"`,即将其出栈:
移除并返回栈顶部的值 "cuatro",即出栈:
```swift
let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串
```
下图展示了如何将顶部的值出栈:
下图展示了 `stackOfStrings` 如何将顶部的值出栈:
![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png)
![](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png)
<a name="extending_a_generic_type"></a>
## 扩展一个泛型类型
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。*原始*类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
## 泛型扩展
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:
当对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶元素且不会将其从栈中移除:
```swift
extension Stack {
@ -218,10 +258,9 @@ extension Stack {
}
}
```
`topItem` 属性会返回 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素
注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
注意:这个扩展并没有定义类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
@ -233,33 +272,32 @@ if let topItem = stackOfStrings.topItem {
```
<a name="type_constraints"></a>
## 类型约束
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任类型。不过,有的时候如果能将使用在泛型函数泛型类型中的类型添加一个特定的类型约束,将会是非常有用。类型约束可以指定一个类型参数必须继承自指定类,或者遵循一个特定的协议或协议组合。
`swapTwoValues(_:_:)` 函数和 `Stack` 用于任类型。不过,如果能对泛型函数泛型类型中添加特定的*类型约束*,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary`键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在 [字典的描述](./04_Collection_Types.html#dictionaries) 字典键的类型必须是可哈希hashable的。也就是说必须有一种方法能够唯一地表示它。字典键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,字典将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 键类型上,要求其键类型必须遵循 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
这个要求通过 `Dictionary` 键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 `Hashable` 协议。所有 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。`可哈希hashable` 这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。
<a name="type_constraint_syntax"></a>
### 类型约束语法
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型的语法与之相同):
在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同):
```swift
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
```
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须遵循 `SomeProtocol` 协议的类型约束。
上面这个函数有两个类型参数。第一个类型参数 `T` 必须是 `SomeClass` 子类;第二个类型参数 `U` 必须符合 `SomeProtocol` 协议。
<a name="type_constraints_in_action"></a>
### 类型约束实践
这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,` findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`
这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`
```swift
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
@ -271,8 +309,7 @@ func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
return nil
}
```
`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串:
`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串值:
```swift
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
@ -281,10 +318,9 @@ if let foundIndex = findIndex(ofString: "llama", in: strings) {
}
// 打印 “The index of llama is 2”
```
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`
下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(ofString:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明:
下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(of:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因将在后面说明:
```swift
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
@ -297,11 +333,11 @@ func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
}
```
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。例如,如果你自定义类或结构体来描述复杂的数据模型,对于这个类或结构体而言Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 `T`,当你试图编译这部分代码时会出现相应的错误。
不过所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
任何 `Equatable` 类型都可以安全地使用在 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
遵循 `Equatable` 协议的类型都可以安全地用于 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
```swift
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
@ -313,10 +349,9 @@ func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
return nil
}
```
`findIndex(of:in:)` 类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。
`findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何遵循 `Equatable` 协议的类型 `T`”。
`findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何遵循 `Equatable` 协议的类型,如 `Double``String`
`findIndex(of:in:)` 函数现在可以成功编译了,并且适用于任何符合 `Equatable` 的类型,如 `Double``String`
```swift
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
@ -324,13 +359,12 @@ let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 类型为 Int?,其值为 2
```
<a name="associated_types"></a>
## 关联类型
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型
定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 `associatedtype` 关键字来指定。
<a name="associated_types_in_action"></a>
### 关联类型实践
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `Item`
@ -343,22 +377,21 @@ protocol Container {
subscript(i: Int) -> Item { get }
}
```
`Container` 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能:
`Container` 协议定义了三个任何遵循了该协议的类型(即容器)必须提供的功能:
+ 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
+ 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 Int 值。
+ 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
该协议没有指定容器中元素该如何存储以及元素类型。该协议只指定了任何遵从 `Container` 协议的类型必须提供的三个功能。遵从协议的类型在满足这三个条件的情况下,也可以提供其他额外的功能
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵 `Container` 协议的类型必须提供的功能。遵循协议的类型在满足这三个条件的情况下也可以提供其他额外的功能
任何遵 `Container` 协议的类型必须能够指定其存储的元素的类型。具体来说,它必须确保添加到容器内的元素以及下标返回的元素类型是正确的
任何遵循 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
为了定义这些条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器内的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型
了定义这三个条件`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型
`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。协议没有定义 `Item` 是什么,这个信息留给遵从协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能如预期
为了达到这个目的,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。这个协议无法定义 `Item` 是什么类型的别名,这个信息将留给遵循协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
下面是先前的非泛型的 `IntStack` 类型,这一版本遵循了 `Container` 协议:
这是前面非泛型版本 `IntStack` 类型,使其遵循 `Container` 协议:
```swift
struct IntStack: Container {
@ -388,7 +421,7 @@ struct IntStack: Container {
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item``Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
由于 Swift 的类型推断,实际上不用`IntStack` 的定义中声明 `Item``Int`。因为 `IntStack` 遵循 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
由于 Swift 的类型推断,实际上在 `IntStack` 的定义中不需要声明 `Item``Int`。因为 `IntStack` 符合 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切也可正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
你也可以让泛型 `Stack` 结构体遵循 `Container` 协议:
@ -414,26 +447,22 @@ struct Stack<Element>: Container {
}
}
```
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `Item` 的类型。
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
### 通过扩展一个存在的类型来指定关联类型
### 扩展现有类型来指定关联类型
[通过扩展添加协议遵循](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型协议。
[扩展添加协议一致性](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型协议。
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引的下标用以检索其元素。这三个功能都遵循 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 遵循协议就可以扩展 `Array`,使其遵`Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展遵循协议](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,`count` 属性,以及带有 `Int` 索引的下标检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需声明 `Array` 遵循`Container` 协议就可以扩展 Array使其遵Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:
```swift
extension Array: Container {}
```
`Array``append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 具体类型。定义了这个扩展后,你可以将任意 `Array` 当作 Container 来使用。
如同上面的泛型 `Stack` 结构体一样,`Array``append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。
<a name="using_type_annotations_to_constrain_an_associated_type"></a>
### 给关联类型添加约束
你可以协议里关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` `Container` 类型
你可以协议里关联类型添加约束来要求遵循的类型满足约束。例如,下面的代码定义了 `Container` 协议, 要求关联类型 `Item` 必须遵循 `Equatable` 协议
```swift
protocol Container {
@ -443,13 +472,12 @@ protocol Container {
subscript(i: Int) -> Item { get }
}
```
为了遵循 `Container` 协议Item 类型也必须遵循 `Equatable` 协议。
要遵守 `Container` 协议,`Item` 类型也必须遵守 `Equatable` 协议。
<a name="Using_a_Protocol_in_Its_Associated_Types_Constraints"></a>
### 在关联类型约束里使用协议
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个 `suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,把它们存储在一个 `Suffix` 类型的实例里。
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个` suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,把它们存储在一个 `Suffix` 类型的实例里。
```swift
protocol SuffixableContainer: Container {
@ -457,10 +485,9 @@ protocol SuffixableContainer: Container {
func suffix(_ size: Int) -> Suffix
}
```
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container``Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面带有泛型 `Where` 分句的扩展中有讨论。
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container``Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `wher`e 分句,它在下面[带有泛型 Where 分句的扩展](#extensions_with_a_generic_where_clause)中有讨论。
这里有一个来自[闭包的循环强引用](./23_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)的 Stack 类型的扩展,它添加了对 `SuffixableContainer` 协议的遵循:
这是上面 [强引用循环闭包](./23_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures) 中 `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议
```swift
extension Stack: SuffixableContainer {
@ -480,8 +507,7 @@ stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
```
在上面的例子中,`Suffix``Stack` 的关联类型,也就是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它添加了 `SuffixableContainer` 遵循,使用 `Stack<Int>` 作为它的后缀类型而不是 `IntStack`
在上面的例子中,`Suffix``Stack` 的关联类型,也是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它遵循了 `SuffixableContainer` 协议,使用 `Stack<Int>` 作为它的后缀类型而不是 `IntStack`
```swift
extension IntStack: SuffixableContainer {
@ -496,13 +522,13 @@ extension IntStack: SuffixableContainer {
}
```
<a name="where_clauses"></a>
## 泛型 Where 语句
[类型约束](#type_constraints)让你能够为泛型函数下标类型的类型参数定义一些强制要求。
[类型约束](#type_constraints)让你能够为泛型函数下标类型的类型参数定义一些强制要求。
关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句关联类型遵某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句。
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`
@ -529,36 +555,35 @@ func allItemsMatch<C1: Container, C2: Container>
return true
}
```
这个函数接受 `someContainer``anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2``C1``C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
这个函数的类型参数列表还定义了对两个类型参数的要求:
- `C1` 必须遵循 `Container` 协议(写作 `C1: Container`)。
- `C2` 必须遵循 `Container` 协议(写作 `C2: Container`)。
- `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。
- `C1``Item` 必须遵循 `Equatable` 协议(写作 `C1.Item: Equatable`)。
+ `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
+ `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
+ `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。
+ `C1``Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。
第三个和第四个要求定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
前两个要求定义在函数的类型形式参数列表里,后两个要求定义在了函数的泛型 `where` 分句中
这些要求意味着:
- `someContainer` 是一个 `C1` 类型的容器。
- `anotherContainer` 是一个 `C2` 类型的容器。
- `someContainer``anotherContainer` 包含相同类型的元素。
- `someContainer` 中的元素可以通过不等于操作符(`!=`)来检查它们是否彼此不同。
+ `someContainer` 是一个 `C1` 类型的容器。
+ `anotherContainer` 是一个 `C2` 类型的容器。
+ `someContainer``anotherContainer` 包含相同类型的元素。
+ `someContainer` 中的元素可以通过不等于操作符(!=)来检查它们是否同。
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
`allItemsMatch(_:_:)` 函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回 `false`
`allItemsMatch(_:_:)` 函数首先检查两个容器元素个数是否相同,如果元素个数不同,那么一定不匹配,函数就会返回 `false`
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 `false`
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 false。
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`
下面演示了 `allItemsMatch(_:_:)` 函数的使用
下面 `allItemsMatch(_:_:)` 函数的示例
```swift
var stackOfStrings = Stack<String>()
@ -575,10 +600,11 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
}
// 打印 “All items match.”
```
上面的例子创建 `Stack` 实例来存储 `String` 值,然后将三个字符串压栈。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵循 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
<a name="extensions_with_a_generic_where_clause"></a>
## 具有泛型 Where 子句的扩展
你也可以使用泛型 `where` 子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 `Stack` 结构体,添加一个 `isTop(_:)` 方法。
@ -594,7 +620,7 @@ extension Stack where Element: Equatable {
}
```
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是遵循 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素遵循 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
以下是 `isTop(_:)` 方法的调用方式:
@ -607,7 +633,7 @@ if stackOfStrings.isTop("tres") {
// 打印 "Top element is tres."
```
如果尝试在其元素未遵循 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
如果尝试在其元素不符合 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
```swift
struct NotEquatable { }
@ -616,7 +642,6 @@ let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // 报错
```
你可以使用泛型 `where` 子句去扩展一个协议。基于以前的示例,下面的示例扩展了 `Container` 协议,添加一个 `startsWith(_:)` 方法。
```swift
@ -627,7 +652,7 @@ extension Container where Item: Equatable {
}
```
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何遵循 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是遵循 `Equatable` 协议的。
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 `Equatable` 协议的。
```swift
if [9, 9, 9].startsWith(42) {
@ -653,15 +678,15 @@ extension Container where Item == Double {
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印 "648.9"
```
此示例将一个 `average()` 方法添加到 `Item` 类型为 `Double` 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 `Int` 转换为 `Double` 确保能够进行浮点除法。
就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
<a name="associated_types_with_a_generic_where_clause"></a>
## 具有泛型 Where 子句的关联类型
你可以在关联类型后面加上具有泛型 `where` 的字句。例如建立一个包含迭代器Iterator的容器就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(`Iterator`)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
```swift
protocol Container {
@ -674,19 +699,18 @@ protocol Container {
func makeIterator() -> Iterator
}
```
迭代器Iterator的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。
迭代器(`Iterator`)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。
一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 `where` 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 `ComparableContainer` 协议,它要求所有的 `Item` 必须是 `Comparable` 的。
```swift
protocol ComparableContainer: Container where Item: Comparable { }
```
<a name="generic_subscripts"></a>
##泛型下标
下标能够是泛型的,他们能够包含泛型 `where` 子句。你可以把占位符类型的名称写在 `subscript` 后面的尖括号里,在下标代码体开始的标志的花括号之前写下泛型 `where` 子句。例如:
## 泛型下标
下标可以是泛型,它们能够包含泛型 `where` 子句。你可以在 `subscript` 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 `where` 子句。例如:
```swift
extension Container {
@ -703,11 +727,8 @@ extension Container {
这个 `Container` 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:
这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
- 在尖括号中的泛型参数 `Indices`,必须是遵循标准库中的 `Sequence` 协议的类型
- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
- 泛型 `where` 子句要求 SequenceIndices的迭代器其所有的元素都是 `Int` 类型。这样就能确保在序列Sequence中的索引和容器Container里面的索引类型是一致的。
+ 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。
+ 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
+ 泛型 `where` 子句要求 `SequenceIndices`的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(`Sequence`)中的索引和容器(`Container`)里面的索引类型是一致的
综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列。

View File

@ -36,7 +36,7 @@ let invertedBits = ~initialBits // 等于 0b11110000
<a name="bitwise_and_operator"></a>
### Bitwise AND Operator按位与运算符
*按位与运算符(`&`*对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位*都*为 `1` 的时候,新数的对应位才为 `1`
*按位与运算符(`&`* 对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位*都*为 `1` 的时候,新数的对应位才为 `1`
![Art/bitwiseAND_2x.png](https://docs.swift.org/swift-book/_images/bitwiseAND_2x.png)
@ -51,7 +51,7 @@ let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
<a name="bitwise_or_operator"></a>
### Bitwise OR Operator按位或运算符
*按位或运算符(`|`*可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有*任意一个*为 `1` 时,新数的对应位就为 `1`
*按位或运算符(`|`* 可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有*任意一个*为 `1` 时,新数的对应位就为 `1`
![Art/bitwiseOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseOR_2x.png)
@ -81,7 +81,7 @@ let outputBits = firstBits ^ otherBits // 等于 00010001
<a name="bitwise_left_and_right_shift_operators"></a>
### Bitwise Left and Right Shift Operators按位左移、右移运算符
*按位左移运算符(`<<`**按位右移运算符(`>>`*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
*按位左移运算符(`<<`**按位右移运算符(`>>`*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2同样地将一个整数右移一位等价于将这个数除以 2。
@ -476,7 +476,7 @@ let plusMinusVector = firstVector +- secondVector
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
```
这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+``-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [Operator Declarations](https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[Operator Declaration](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID380)
这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+``-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[运算符声明](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID380)
> 注意
>