校对
This commit is contained in:
@ -227,59 +227,59 @@ let fromTheTop = stackOfStrings.pop()
|
|||||||
<a name="extending_a_generic_type"></a>
|
<a name="extending_a_generic_type"></a>
|
||||||
## 扩展一个泛型类型
|
## 扩展一个泛型类型
|
||||||
|
|
||||||
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
||||||
|
|
||||||
下面的例子扩展了泛型`Stack`类型,为其添加了一个名为`topItem`的只读计算属性,它将会返回当前栈顶端的元素而不会将其从栈中移除。
|
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension Stack {
|
extension Stack {
|
||||||
var topItem: T? {
|
var topItem: Element? {
|
||||||
return items.isEmpty ? nil : items[items.count - 1]
|
return items.isEmpty ? nil : items[items.count - 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`topItem`属性会返回一个`T`类型的可选值。当栈为空的时候,`topItem`将会返回`nil`;当栈不为空的时候,`topItem`会返回`items`数组中的最后一个元素。
|
`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
|
||||||
|
|
||||||
注意这里的扩展并没有定义一个类型参数列表。相反的,`Stack`类型已有的类型参数名称,`T`,被用在扩展中当做`topItem`计算属性的可选类型。
|
注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
|
||||||
|
|
||||||
`topItem`计算属性现在可以被用来返回任意`Stack`实例的顶端元素而无需移除它:
|
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素而不是移除它:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
if let topItem = stackOfStrings.topItem {
|
if let topItem = stackOfStrings.topItem {
|
||||||
print("The top item on the stack is \(topItem).")
|
print("The top item on the stack is \(topItem).")
|
||||||
}
|
}
|
||||||
// 输出 "The top item on the stack is tres."
|
// 打印 “The top item on the stack is tres.”
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="type_constraints"></a>
|
<a name="type_constraints"></a>
|
||||||
## 类型约束
|
## 类型约束
|
||||||
|
|
||||||
`swapTwoValues(_:_:)`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
|
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型,强制约束为某种特定类型,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
|
||||||
|
|
||||||
例如,Swift 的`Dictionary`类型对作用于其键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键类型必须是*可哈希*,也就是说,必须有一种方法可以使其被唯一的表示。`Dictionary`之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,`Dictionary`既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。
|
例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希的。也就是说,必须有一种方法能作为其唯一的表示。`Dictionary` 之所以需要其键是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。如无此要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
|
||||||
|
|
||||||
这个需求强制加上一个类型约束作用于`Dictionary`的键上,当然其键类型必须遵循`Hashable`协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如`String`,`Int`, `Double`和 `Bool`)默认都是可哈希。
|
这个要求强制加上了一个类型约束作用于 `Dictionary` 的键类型上,其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。
|
||||||
|
|
||||||
当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如`可哈希`具有的类型特征是根据它们概念特征来界定的,而不是它们的直接类型特征。
|
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
|
||||||
|
|
||||||
<a name="type_constraint_syntax"></a>
|
<a name="type_constraint_syntax"></a>
|
||||||
### 类型约束语法
|
### 类型约束语法
|
||||||
|
|
||||||
你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):
|
你可以在一个类型参数名后面放置一个类名或者协议名,通过冒号分隔,从而定义类型约束,它们将作为类型参数列表的一部分。这种基本的类型约束作用于泛型函数时的语法如下所示(作用于泛型类型时的语法与之相同):
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
||||||
// 这里是函数主体
|
// 这里是泛型函数的函数体部分
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面这个假定函数有两个类型参数。第一个类型参数`T`,有一个需要`T`必须是`SomeClass`子类的类型约束;第二个类型参数`U`,有一个需要`U`必须遵循`SomeProtocol`协议的类型约束。
|
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。
|
||||||
|
|
||||||
<a name="type_constraints_in_action"></a>
|
<a name="type_constraints_in_action"></a>
|
||||||
### 类型约束实例
|
### 类型约束实践
|
||||||
|
|
||||||
这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex(_:_:)`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`:
|
这里有个名为 `findStringIndex` 的非泛型函数,该函数的功能是在 `String` 值的数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findStringIndex(_:_:)` 函数返回该字符串在数组中的索引值,反之则返回 `nil`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
||||||
@ -292,20 +292,19 @@ func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`findStringIndex(_:_:)` 函数可以用于查找字符串数组中的某个字符串:
|
||||||
`findStringIndex(_:_:)`函数可以作用于查找一字符串数组中的某个字符串:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
||||||
if let foundIndex = findStringIndex(strings, "llama") {
|
if let foundIndex = findStringIndex(strings, "llama") {
|
||||||
print("The index of llama is \(foundIndex)")
|
print("The index of llama is \(foundIndex)")
|
||||||
}
|
}
|
||||||
// 输出 "The index of llama is 2"
|
// 打印 “The index of llama is 2”
|
||||||
```
|
```
|
||||||
|
|
||||||
如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数`findIndex`,用某个类型`T`值替换掉提到的字符串。
|
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以写出相同功能的泛型函数 `findIndex(_:_:)`,用占位类型 `T` 替换 `String` 类型。
|
||||||
|
|
||||||
这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明:
|
下面展示了 `findStringIndex(_:_:)` 函数的泛型版本 `findIndex(_:_:)`。请注意这个函数仍然返回 `Int?`,那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因在例子后面会说明:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
||||||
@ -318,11 +317,11 @@ func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面所写的函数不会编译。这个问题的位置在等式的检查上,`“if value == valueToFind”`。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型`T`,当你试图编译这部分代码时估计会出现相应的错误。
|
上面所写的函数无法通过编译。这个问题出在相等性检查上,即 `“if value == valueToFind”`。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。
|
||||||
|
|
||||||
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持`Equatable`协议。
|
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何符合该协议的类型必须实现等式符(`==`),从而能对符合该协议的类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
|
||||||
|
|
||||||
任何`Equatable`类型都可以安全的使用在`findIndex(_:_:)`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分:
|
任何 `Equatable` 类型都可以安全地使用在 `findIndex(_:_:)` 函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
||||||
@ -335,27 +334,26 @@ func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`findIndex(_:_:)` 中的这个单一类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的 `T` 类型”。
|
||||||
|
|
||||||
`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。
|
`findIndex(_:_:)` 函数现在可以成功编译了,并且可以作用于任何符合 `Equatable` 的类型,如 `Double` 或 `String`:
|
||||||
|
|
||||||
`findIndex(_:_:)`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
|
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
|
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
|
||||||
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
|
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
|
||||||
// stringIndex is an optional Int containing a value of 2
|
// stringIndex 类型为 Int?,其值为 2
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="associated_types"></a>
|
<a name="associated_types"></a>
|
||||||
##关联类型(Associated Types)
|
## 关联类型
|
||||||
|
|
||||||
当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为`typealias`关键字。
|
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型作为协议的一部分,为某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `typealias` 关键字来指定关联类型。
|
||||||
|
|
||||||
<a name="associated_types_in_action"></a>
|
<a name="associated_types_in_action"></a>
|
||||||
### 关联类型实例
|
### 关联类型实践
|
||||||
|
|
||||||
这里是一个`Container`协议的例子,定义了一个`ItemType`关联类型:
|
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `ItemType`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
protocol Container {
|
protocol Container {
|
||||||
@ -366,25 +364,25 @@ protocol Container {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Container`协议定义了三个任何容器必须支持的兼容要求:
|
`Container` 协议定义了三个任何采纳协议的类型必须提供的功能:
|
||||||
|
|
||||||
- 必须可以通过`append(_:)`方法添加一个新元素到容器里;
|
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
||||||
- 必须可以通过使用`count`属性获取容器里元素的数量,并返回一个`Int`值;
|
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
|
||||||
- 必须可以通过容器的`Int`索引值下标可以检索到每一个元素。
|
- 必须可以通过接受 `Int` 索引值的下标检索到每一个元素。
|
||||||
|
|
||||||
这个协议没有指定容器里的元素是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何采纳 `Container` 协议的类型必须提供的功能。采纳协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||||
|
|
||||||
任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的元素可以加进容器里,必须明确可以通过其下标返回元素类型。
|
任何采纳 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
||||||
|
|
||||||
为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append(_:)`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。
|
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
|
||||||
|
|
||||||
为了达到此目的,`Container`协议声明了一个`ItemType`的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别`Container`中元素类型的方法,并且用于`append(_:)`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。
|
为了达到此目的,`Container` 协议声明了一个关联类型 `ItemType`,写作 `typealias ItemType`。这个协议无法定义 `ItemType` 是什么类型的别名,这个信息将留给采纳协议的类型来提供。尽管如此,`ItemType` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的预期行为都能够被执行。
|
||||||
|
|
||||||
这里是一个早前`IntStack`类型的非泛型版本,遵循`Container`协议:
|
下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合了 `Container` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct IntStack: Container {
|
struct IntStack: Container {
|
||||||
// IntStack的原始实现
|
// IntStack 的原始实现部分
|
||||||
var items = [Int]()
|
var items = [Int]()
|
||||||
mutating func push(item: Int) {
|
mutating func push(item: Int) {
|
||||||
items.append(item)
|
items.append(item)
|
||||||
@ -392,7 +390,7 @@ struct IntStack: Container {
|
|||||||
mutating func pop() -> Int {
|
mutating func pop() -> Int {
|
||||||
return items.removeLast()
|
return items.removeLast()
|
||||||
}
|
}
|
||||||
// 遵循Container协议的实现
|
// Container 协议的实现部分
|
||||||
typealias ItemType = Int
|
typealias ItemType = Int
|
||||||
mutating func append(item: Int) {
|
mutating func append(item: Int) {
|
||||||
self.push(item)
|
self.push(item)
|
||||||
@ -406,118 +404,115 @@ struct IntStack: Container {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。
|
||||||
|
|
||||||
`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。
|
此外,`IntStack` 指定 `ItemType` 为 `Int` 类型,即 `typealias ItemType = Int`,从而将 `Container` 协议中抽象的 `ItemType` 类型转换为具体的 `Int` 类型。
|
||||||
|
|
||||||
此外,`IntStack`指定了`Container`的实现,适用的`ItemType`被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。
|
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `ItemType` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `ItemType` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias ItemType = Int` 这一行,这一切仍旧可以正常工作,因为 Swift 清楚地知道 `ItemType` 应该是何种类型。
|
||||||
|
|
||||||
感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append(_:)`方法的`item`参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道`ItemType`使用的是何种类型。
|
你也可以令泛型 `Stack` 结构体符合 `Container` 协议:
|
||||||
|
|
||||||
你也可以生成遵循`Container`协议的泛型`Stack`类型:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct Stack<T>: Container {
|
struct Stack<Element>: Container {
|
||||||
// original Stack<T> implementation
|
// Stack<Element> 的原始实现部分
|
||||||
var items = [T]()
|
var items = [Element]()
|
||||||
mutating func push(item: T) {
|
mutating func push(item: Element) {
|
||||||
items.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
mutating func pop() -> T {
|
mutating func pop() -> Element {
|
||||||
return items.removeLast()
|
return items.removeLast()
|
||||||
}
|
}
|
||||||
// conformance to the Container protocol
|
// Container 协议的实现部分
|
||||||
mutating func append(item: T) {
|
mutating func append(item: Element) {
|
||||||
self.push(item)
|
self.push(item)
|
||||||
}
|
}
|
||||||
var count: Int {
|
var count: Int {
|
||||||
return items.count
|
return items.count
|
||||||
}
|
}
|
||||||
subscript(i: Int) -> T {
|
subscript(i: Int) -> Element {
|
||||||
return items[i]
|
return items[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这个时候,占位类型参数`T`被用作`append(_:)`方法的`item`参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。
|
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `ItemType` 的类型。
|
||||||
|
|
||||||
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
|
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
|
||||||
### 扩展一个存在的类型为一指定关联类型
|
### 通过扩展一个存在的类型来指定关联类型
|
||||||
|
|
||||||
在[在扩展中添加协议成员](./22_Protocols.html#adding_protocol_conformance_with_an_extension)中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。
|
[通过扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。
|
||||||
|
|
||||||
Swift的`Array`已经提供`append(_:)`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[通过扩展补充协议声明](./22_Protocols.html#declaring_protocol_adoption_with_an_extension)中有描述这样一个实现一个空扩展的行为:
|
Swift 的 `Array` 已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 型索引值的可用来检索数组元素的下标。这三个功能都符合 `Container` 协议的要求,也就意味着你可以扩展 `Array` 去符合 `Container` 协议,只需简单地声明 `Array` 采纳该协议即可。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./22_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension Array: Container {}
|
extension Array: Container {}
|
||||||
```
|
```
|
||||||
|
|
||||||
如同上面的泛型`Stack`类型一样,`Array`的`append(_:)`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。
|
如同上面的泛型 `Stack` 结构体一样,`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `ItemType` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。
|
||||||
|
|
||||||
<a name="where_clauses"></a>
|
<a name="where_clauses"></a>
|
||||||
## Where 语句
|
## Where 子句
|
||||||
|
|
||||||
[类型约束](#type_constraints)能够确保类型符合泛型函数或类的定义约束。
|
[类型约束](#type_constraints)让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。
|
||||||
|
|
||||||
对关联类型定义约束是非常有用的。你可以在参数列表中通过*where*语句定义参数的约束。一个`where`语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个`where`语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。
|
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。一个 `where` 子句能够使一个关联类型符合某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。
|
||||||
|
|
||||||
下面的例子定义了一个名为`allItemsMatch`的泛型函数,用来检查两个`Container`实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回一个为`true`的`Boolean`值,反之则为`false`。
|
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
||||||
|
|
||||||
被检查的两个`Container`可以不是相同类型的容器(虽然它们可以是),但它们确实拥有相同类型的元素。这个需求通过一个类型约束和`where`语句结合来表示:
|
被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func allItemsMatch<
|
func allItemsMatch<
|
||||||
C1: Container, C2: Container
|
C1: Container, C2: Container
|
||||||
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
|
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
|
||||||
(someContainer: C1, anotherContainer: C2) -> Bool {
|
(someContainer: C1, _ anotherContainer: C2) -> Bool {
|
||||||
|
|
||||||
// 检查两个Container的元素个数是否相同
|
// 检查两个容器含有相同数量的元素
|
||||||
if someContainer.count != anotherContainer.count {
|
if someContainer.count != anotherContainer.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查两个Container相应位置的元素彼此是否相等
|
// 检查每一对元素是否相等
|
||||||
for i in 0..<someContainer.count {
|
for i in 0..<someContainer.count {
|
||||||
if someContainer[i] != anotherContainer[i] {
|
if someContainer[i] != anotherContainer[i] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果所有元素检查都相同则返回true
|
// 所有元素都匹配,返回 true
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
这个函数接受 `someContainer` 和 `anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2`。`C1` 和 `C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
|
||||||
|
|
||||||
这个函数用了两个参数:`someContainer`和`anotherContainer`。`someContainer`参数是类型`C1`,`anotherContainer`参数是类型`C2`。`C1`和`C2`是容器的两个占位类型参数,决定了这个函数何时被调用。
|
这个函数的类型参数列表还定义了两个类型参数的要求:
|
||||||
|
|
||||||
这个函数的类型参数列紧随在两个类型参数需求的后面:
|
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
|
||||||
|
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
|
||||||
|
- `C1` 的 `ItemType` 必须和 `C2` 的 `ItemType`类型相同(写作 `C1.ItemType == C2.ItemType`)。
|
||||||
|
- `C1` 的 `ItemType` 必须符合 `Equatable` 协议(写作 `C1.ItemType: Equatable`)。
|
||||||
|
|
||||||
- `C1`必须遵循`Container`协议 (写作 `C1: Container`)。
|
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
|
||||||
- `C2`必须遵循`Container`协议 (写作 `C2: Container`)。
|
|
||||||
- `C1`的`ItemType`同样是C2的`ItemType`(写作 `C1.ItemType == C2.ItemType`)。
|
|
||||||
- `C1`的`ItemType`必须遵循`Equatable`协议 (写作 `C1.ItemType: Equatable`)。
|
|
||||||
|
|
||||||
第三个和第四个要求被定义为一个`where`语句的一部分,写在关键字`where`后面,作为函数类型参数链的一部分。
|
这些要求意味着:
|
||||||
|
|
||||||
这些要求意思是:
|
- `someContainer` 是一个 `C1` 类型的容器。
|
||||||
|
- `anotherContainer` 是一个 `C2` 类型的容器。
|
||||||
|
- `someContainer` 和 `anotherContainer` 包含相同类型的元素。
|
||||||
|
- `someContainer` 中的元素可以通过不等于操作符(`!=`)来检查它们是否彼此不同。
|
||||||
|
|
||||||
`someContainer`是一个`C1`类型的容器。
|
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
|
||||||
`anotherContainer`是一个`C2`类型的容器。
|
|
||||||
`someContainer`和`anotherContainer`包含相同的元素类型。
|
|
||||||
`someContainer`中的元素可以通过不等于操作(`!=`)来检查它们是否彼此不同。
|
|
||||||
|
|
||||||
第三个和第四个要求结合起来的意思是`anotherContainer`中的元素也可以通过 `!=` 操作来检查,因为它们在`someContainer`中元素确实是相同的类型。
|
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们是不同的容器类型。
|
||||||
|
|
||||||
这些要求能够使`allItemsMatch(_:_:)`函数比较两个容器,即便它们是不同的容器类型。
|
`allItemsMatch(_:_:)` 函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回 `false`。
|
||||||
|
|
||||||
`allItemsMatch(_:_:)`首先检查两个容器是否拥有同样数目的items,如果它们的元素数目不同,没有办法进行匹配,函数就会`false`。
|
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 `false`。
|
||||||
|
|
||||||
检查完之后,函数通过`for-in`循环和半闭区间操作(`..<`)来迭代`someContainer`中的所有元素。对于每个元素,函数检查是否`someContainer`中的元素不等于对应的`anotherContainer`中的元素,如果这两个元素不等,则这两个容器不匹配,返回`false`。
|
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`。
|
||||||
|
|
||||||
如果循环体结束后未发现没有任何的不匹配,那表明两个容器匹配,函数返回`true`。
|
下面演示了 `allItemsMatch(_:_:)` 函数的使用:
|
||||||
|
|
||||||
这里演示了`allItemsMatch(_:_:)`函数运算的过程:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
var stackOfStrings = Stack<String>()
|
var stackOfStrings = Stack<String>()
|
||||||
@ -532,16 +527,7 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
|||||||
} else {
|
} else {
|
||||||
print("Not all items match.")
|
print("Not all items match.")
|
||||||
}
|
}
|
||||||
// 输出 "All items match."
|
// 打印 “All items match.”
|
||||||
```
|
```
|
||||||
|
|
||||||
上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组是不同的类型,但它们都遵循`Container`协议,而且它们都包含同样的类型值。因此你可以调用`allItemsMatch(_:_:)`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch(_:_:)`函数正确的显示了这两个容器的所有元素都是相互匹配的。
|
上面的例子创建一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含三个同栈中一样的字符串。即使栈和数组是不同的类型,但它们都符合 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||||
|
|
||||||
[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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user