@ -76,7 +76,7 @@ struct BlackjackCard {
|
||||
|
||||
```swift
|
||||
let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
|
||||
println("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
print("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
// 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11"
|
||||
```
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
> 翻译:[takalard](https://github.com/takalard)
|
||||
> 翻译:[takalard](https://github.com/takalard) [SergioChan](https://github.com/SergioChan)
|
||||
> 校对:[lifedim](https://github.com/lifedim)
|
||||
|
||||
# 泛型
|
||||
@ -13,6 +13,7 @@
|
||||
- [类型参数](#type_parameters)
|
||||
- [命名类型参数](#naming_type_parameters)
|
||||
- [泛型类型](#generic_types)
|
||||
- [扩展一个泛型类型](#extending_a_generic_type)
|
||||
- [类型约束](#type_constraints)
|
||||
- [关联类型](#associated_types)
|
||||
- [`Where`语句](#where_clauses)
|
||||
@ -42,7 +43,7 @@ func swapTwoInts(inout a: Int, inout _ b: Int) {
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// 输出 "someInt is now 107, and anotherInt is now 3"
|
||||
```
|
||||
|
||||
@ -226,11 +227,38 @@ let fromTheTop = stackOfStrings.pop()
|
||||
下图展示了如何从栈中pop一个值的过程:
|
||||

|
||||
|
||||
<a name="extending_a_generic_type"></a>
|
||||
## 扩展一个泛型类型
|
||||
|
||||
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
||||
|
||||
下面的例子扩展了泛型`Stack`类型,为其添加了一个名为`topItem`的只读计算属性,它将会返回当前栈顶端的元素而不会将其从栈中移除。
|
||||
|
||||
```swift
|
||||
extension Stack {
|
||||
var topItem: T? {
|
||||
return items.isEmpty ? nil : items[items.count - 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`topItem`属性会返回一个`T`类型的可选值。当栈为空的时候,`topItem`将会返回`nil`;当栈不为空的时候,`topItem`会返回`items`数组中的最后一个元素。
|
||||
|
||||
注意这里的扩展并没有定义一个类型参数列表。相反的,`Stack`类型已有的类型参数名称,`T`,被用在扩展中当做`topItem`计算属性的可选类型。
|
||||
|
||||
`topItem`计算属性现在可以被用来返回任意`Stack`实例的顶端元素而无需移除它:
|
||||
|
||||
```swift
|
||||
if let topItem = stackOfStrings.topItem {
|
||||
print("The top item on the stack is \(topItem).")
|
||||
}
|
||||
// 输出 "The top item on the stack is tres."
|
||||
```
|
||||
|
||||
<a name="type_constraints"></a>
|
||||
##类型约束
|
||||
|
||||
`swapTwoValues`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
|
||||
`swapTwoValues(_:_:)`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
|
||||
|
||||
例如,Swift 的`Dictionary`类型对作用于其键的类型做了些限制。在[字典](../chapter2/04_Collection_Types.html)的描述中,字典的键类型必须是*可哈希*,也就是说,必须有一种方法可以使其被唯一的表示。`Dictionary`之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,`Dictionary`既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。
|
||||
|
||||
@ -244,7 +272,7 @@ let fromTheTop = stackOfStrings.pop()
|
||||
|
||||
```swift
|
||||
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
||||
// function body goes here
|
||||
// 这里是函数主体
|
||||
}
|
||||
```
|
||||
|
||||
@ -252,11 +280,11 @@ func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
||||
|
||||
### 类型约束行为
|
||||
|
||||
这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`:
|
||||
这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex(_:_:)`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`:
|
||||
|
||||
```swift
|
||||
func findStringIndex(array: [String], valueToFind: String) -> Int? {
|
||||
for (index, value) in enumerate(array) {
|
||||
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
||||
for (index, value) in array.enumerate() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
@ -266,12 +294,12 @@ func findStringIndex(array: [String], valueToFind: String) -> Int? {
|
||||
```
|
||||
|
||||
|
||||
`findStringIndex`函数可以作用于查找一字符串数组中的某个字符串:
|
||||
`findStringIndex(_:_:)`函数可以作用于查找一字符串数组中的某个字符串:
|
||||
|
||||
```swift
|
||||
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
||||
if let foundIndex = findStringIndex(strings, "llama") {
|
||||
println("The index of llama is \(foundIndex)")
|
||||
print("The index of llama is \(foundIndex)")
|
||||
}
|
||||
// 输出 "The index of llama is 2"
|
||||
```
|
||||
@ -281,8 +309,8 @@ if let foundIndex = findStringIndex(strings, "llama") {
|
||||
这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明:
|
||||
|
||||
```swift
|
||||
func findIndex<T>(array: [T], valueToFind: T) -> Int? {
|
||||
for (index, value) in enumerate(array) {
|
||||
func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
||||
for (index, value) in array.enumerate() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
@ -295,11 +323,11 @@ func findIndex<T>(array: [T], valueToFind: T) -> Int? {
|
||||
|
||||
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持`Equatable`协议。
|
||||
|
||||
任何`Equatable`类型都可以安全的使用在`findIndex`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分:
|
||||
任何`Equatable`类型都可以安全的使用在`findIndex(_:_:)`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分:
|
||||
|
||||
```swift
|
||||
func findIndex<T: Equatable>(array: T[], valueToFind: T) -> Int? {
|
||||
for (index, value) in enumerate(array) {
|
||||
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
||||
for (index, value) in array.enumerate() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
@ -311,7 +339,7 @@ func findIndex<T: Equatable>(array: T[], valueToFind: T) -> Int? {
|
||||
|
||||
`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。
|
||||
|
||||
`findIndex`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`:
|
||||
`findIndex(_:_:)`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`:
|
||||
|
||||
```swift
|
||||
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
|
||||
@ -327,7 +355,7 @@ let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
|
||||
|
||||
### 关联类型行为
|
||||
|
||||
这里是一个`Container`协议的例子,定义了一个ItemType关联类型:
|
||||
这里是一个`Container`协议的例子,定义了一个`ItemType`关联类型:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
@ -340,19 +368,19 @@ protocol Container {
|
||||
|
||||
`Container`协议定义了三个任何容器必须支持的兼容要求:
|
||||
|
||||
- 必须可以通过`append`方法添加一个新item到容器里;
|
||||
- 必须可以通过使用`count`属性获取容器里items的数量,并返回一个`Int`值;
|
||||
- 必须可以通过容器的`Int`索引值下标可以检索到每一个item。
|
||||
- 必须可以通过`append(_:)`方法添加一个新元素到容器里;
|
||||
- 必须可以通过使用`count`属性获取容器里元素的数量,并返回一个`Int`值;
|
||||
- 必须可以通过容器的`Int`索引值下标可以检索到每一个元素。
|
||||
|
||||
这个协议没有指定容器里item是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
这个协议没有指定容器里的元素是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
|
||||
任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的items可以加进容器里,必须明确可以通过其下标返回item类型。
|
||||
任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的元素可以加进容器里,必须明确可以通过其下标返回元素类型。
|
||||
|
||||
为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。
|
||||
为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append(_:)`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。
|
||||
|
||||
为了达到此目的,`Container`协议声明了一个ItemType的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别Container中Items类型的方法,并且用于`append`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。
|
||||
为了达到此目的,`Container`协议声明了一个`ItemType`的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别`Container`中元素类型的方法,并且用于`append(_:)`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。
|
||||
|
||||
这里是一个早前IntStack类型的非泛型版本,遵循Container协议:
|
||||
这里是一个早前`IntStack`类型的非泛型版本,遵循`Container`协议:
|
||||
|
||||
```swift
|
||||
struct IntStack: Container {
|
||||
@ -370,7 +398,7 @@ struct IntStack: Container {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> Int {
|
||||
return items[i]
|
||||
@ -381,9 +409,9 @@ struct IntStack: Container {
|
||||
|
||||
`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。
|
||||
|
||||
此外,`IntStack`指定了`Container`的实现,适用的ItemType被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。
|
||||
此外,`IntStack`指定了`Container`的实现,适用的`ItemType`被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。
|
||||
|
||||
感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append`方法的item参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道ItemType使用的是何种类型。
|
||||
感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append(_:)`方法的`item`参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道`ItemType`使用的是何种类型。
|
||||
|
||||
你也可以生成遵循`Container`协议的泛型`Stack`类型:
|
||||
|
||||
@ -402,7 +430,7 @@ struct Stack<T>: Container {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> T {
|
||||
return items[i]
|
||||
@ -410,20 +438,20 @@ struct Stack<T>: Container {
|
||||
}
|
||||
```
|
||||
|
||||
这个时候,占位类型参数`T`被用作`append`方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。
|
||||
这个时候,占位类型参数`T`被用作`append(_:)`方法的`item`参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。
|
||||
|
||||
|
||||
### 扩展一个存在的类型为一指定关联类型
|
||||
|
||||
在[使用扩展来添加协议兼容性](../chapter2/21_Protocols.html)中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。
|
||||
|
||||
Swift的`Array`已经提供`append`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[使用扩展来声明协议的采纳](../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`来使用。
|
||||
如同上面的泛型`Stack`类型一样,`Array`的`append(_:)`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。
|
||||
|
||||
<a name="where_clauses"></a>
|
||||
## Where 语句
|
||||
@ -481,15 +509,15 @@ func allItemsMatch<
|
||||
|
||||
第三个和第四个要求结合起来的意思是`anotherContainer`中的元素也可以通过 `!=` 操作来检查,因为它们在`someContainer`中元素确实是相同的类型。
|
||||
|
||||
这些要求能够使`allItemsMatch`函数比较两个容器,即便它们是不同的容器类型。
|
||||
这些要求能够使`allItemsMatch(_:_:)`函数比较两个容器,即便它们是不同的容器类型。
|
||||
|
||||
`allItemsMatch`首先检查两个容器是否拥有同样数目的items,如果它们的元素数目不同,没有办法进行匹配,函数就会`false`。
|
||||
`allItemsMatch(_:_:)`首先检查两个容器是否拥有同样数目的items,如果它们的元素数目不同,没有办法进行匹配,函数就会`false`。
|
||||
|
||||
检查完之后,函数通过`for-in`循环和半闭区间操作(..)来迭代`someContainer`中的所有元素。对于每个元素,函数检查是否`someContainer`中的元素不等于对应的`anotherContainer`中的元素,如果这两个元素不等,则这两个容器不匹配,返回`false`。
|
||||
检查完之后,函数通过`for-in`循环和半闭区间操作(`..<`)来迭代`someContainer`中的所有元素。对于每个元素,函数检查是否`someContainer`中的元素不等于对应的`anotherContainer`中的元素,如果这两个元素不等,则这两个容器不匹配,返回`false`。
|
||||
|
||||
如果循环体结束后未发现没有任何的不匹配,那表明两个容器匹配,函数返回`true`。
|
||||
|
||||
这里演示了allItemsMatch函数运算的过程:
|
||||
这里演示了`allItemsMatch(_:_:)`函数运算的过程:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
@ -500,14 +528,14 @@ stackOfStrings.push("tres")
|
||||
var arrayOfStrings = ["uno", "dos", "tres"]
|
||||
|
||||
if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
||||
println("All items match.")
|
||||
print("All items match.")
|
||||
} else {
|
||||
println("Not all items match.")
|
||||
print("Not all items match.")
|
||||
}
|
||||
// 输出 "All items match."
|
||||
```
|
||||
|
||||
上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组是不同的类型,但它们都遵循`Container`协议,而且它们都包含同样的类型值。因此你可以调用`allItemsMatch`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch`函数正确的显示了所有的这两个容器的`items`匹配。
|
||||
上面的例子创建一个`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
|
||||
|
||||
Reference in New Issue
Block a user