From d6a25806420589d1f2142f73ab78f9ef21463091 Mon Sep 17 00:00:00 2001 From: 949478479 <949478479@qq.com> Date: Fri, 27 Nov 2015 17:14:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=A1=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/chapter2/23_Generics.md | 130 ++++++++++++++++----------------- 1 file changed, 61 insertions(+), 69 deletions(-) diff --git a/source/chapter2/23_Generics.md b/source/chapter2/23_Generics.md index ec300ab2..b0f3beb0 100644 --- a/source/chapter2/23_Generics.md +++ b/source/chapter2/23_Generics.md @@ -22,39 +22,38 @@ - [扩展一个泛型类型](#extending_a_generic_type) - [类型约束](#type_constraints) - [关联类型](#associated_types) -- [`Where`语句](#where_clauses) +- [Where 子句](#where_clauses) -*泛型代码*可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。 +泛型代码可以让你编写适用自定义需求以及任意类型的灵活可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。 -泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的。事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已。例如,Swift 的数组和字典类型都是泛型集。你可以创建一个`Int`数组,也可创建一个`String`数组,或者甚至于可以是任何其他 Swift 的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。 +泛型是 Swift 的强大特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。 ## 泛型所解决的问题 -这里是一个标准的,非泛型函数`swapTwoInts`,用来交换两个Int值: +下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值: ```swift func swapTwoInts(inout a: Int, inout _ b: Int) { - let temporaryA = a - a = b - b = temporaryA + let temporaryA = a + a = b + b = temporaryA } ``` -这个函数使用写入读出(in-out)参数来交换`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(inout a: String, inout _ b: String) { @@ -70,17 +69,17 @@ func swapTwoDoubles(inout a: Double, inout _ b: Double) { } ``` -你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles(_:_:)`函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是`Int`、`String`和`Double`。 +你可能注意到 `swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int`、`String` 和 `Double`。 -但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。) +在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。) ->注意: -在所有三个函数中,`a`和`b`的类型是一样的。如果`a`和`b`不是相同的类型,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个`String`类型的变量和一个`Double`类型的变量互相交换值。如果一定要做,Swift 将报编译错误。 +> 注意 +在上面三个函数中,`a` 和 `b` 类型相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。 ## 泛型函数 -`泛型函数`可以工作于任何类型,这里是一个上面`swapTwoInts(_:_:)`函数的泛型版本,用于交换两个值: +泛型函数可以适用于任何类型,下面的 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本: ```swift func swapTwoValues(inout a: T, inout _ b: T) { @@ -90,79 +89,74 @@ func swapTwoValues(inout a: T, inout _ b: T) { } ``` -`swapTwoValues(_:_:)`函数主体和`swapTwoInts(_:_:)`函数是一样的,它只在第一行稍微有那么一点点不同于`swapTwoInts`,如下所示: +`swapTwoValues(_:_:)` 的函数主体和 `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`来表示)来代替实际类型名(如`Int`、`String`或`Double`)。占位类型名没有提示`T`必须是什么类型,但是它提示了`a`和`b`必须是同一类型`T`,而不管`T`表示什么类型。只有`swapTwoValues(_:_:)`函数在每次调用时所传入的实际类型才能决定`T`所代表的类型。 +另外一个不同之处在于这个泛型函数名后面跟着占位类型名(`T`),而且是用尖括号括起来的(``)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。 -另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的(``)。这个尖括号告诉 Swift 那个`T`是`swapTwoValues(_:_:)`函数所定义的一个类型。因为`T`是一个占位命名类型,Swift 不会去查找命名为T的实际类型。 +`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,可以传入任意类型的值,只要两个值的类型相同。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。 -`swapTwoValues(_:_:)`函数除了要求传入的两个任何类型值是同一类型外,也可以作为`swapTwoInts`函数被调用。每次`swapTwoValues`被调用,T所代表的类型值都会传给函数。 - -在下面的两个例子中,`T`分别代表`Int`和`String`: +在下面的两个例子中,`T` 分别代表 `Int` 和 `String`: ```swift var someInt = 3 var anotherInt = 107 swapTwoValues(&someInt, &anotherInt) -// someInt 现在等于 107, anotherInt 现在等于 3 +// someInt is now 107, and anotherInt is now 3 var someString = "hello" var anotherString = "world" swapTwoValues(&someString, &anotherString) -// someString 现在等于 "world", anotherString 现在等于 "hello" +// someString is now "world", and anotherString is now "hello" ``` - ->注意 -上面定义的函数`swapTwoValues(_:_:)`是受`swap`函数启发而实现的。`swap`函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似`swapTwoValues(_:_:)`函数的功能,你可以使用已存在的交换函数`swap(_:_:)`函数。 +> 注意 +上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。 ## 类型参数 -在上面的`swapTwoValues`例子中,占位类型`T`是一种类型参数的示例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(如``)。 +在上面的 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 ``)。 -一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如`swapTwoValues(_:_:)`函数中的参数`a`和`b`),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换(在上面`swapTwoValues`例子中,当函数第一次被调用时,`T`被`Int`替换,第二次调用时,被`String`替换。)。 +一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a` 和 `b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T` 被 `Int` 替换,第二次调用时,被 `String` 替换。) -你可支持多个类型参数,命名在尖括号中,用逗号分开。 +你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。 ## 命名类型参数 -在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的`swapTwoValues`泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母`T`来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。 +在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionary` 中的 `Key` 和 `Value`,以及 `Array` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间的关系没有意义时,通常使用单一的字母来命名,例如 `T`、`U`、`V`,正如上面演示的 `swapTwoValues(_:_:)` 函数中的 `T` 一样。 -如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift 字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为`Key`和`Value`,用来记住它们在你的泛型代码中的作用。 - ->注意 -请始终使用大写字母开头的驼峰式命名法(例如`T`和`Key`)来给类型参数命名,以表明它们是类型的占位符,而非类型值。 +> 注意 +请始终使用大写字母开头的驼峰式命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。 ## 泛型类型 +除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,如同 `Array` 和 `Dictionary` 的用法。 -通常在泛型函数中,Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同`Array`和`Dictionary`的用法。 +这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许对其中任意位置的元素执行插入或删除操作。而栈,只允许在集合的末端添加新的元素(称之为入栈)。同样的,栈也只能从末端移除元素(称之为出栈)。 -这部分向你展示如何写一个泛型集类型--`Stack`(栈)。一个栈是一系列值域的集合,和`Array`(数组)类似,但其是一个比 Swift 的`Array`类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同*push*一个新值进栈)。同样的一个栈也只能从末端移除项(如同*pop*一个值出栈)。 +> 注意 +栈的概念已被 `UINavigationController` 类用来模拟视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除某个视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。 ->注意 -栈的概念已被`UINavigationController`类使用来模拟试图控制器的导航结构。你通过调用`UINavigationController`的`pushViewController(_:animated:)`方法来为导航栈添加(add)新的试图控制器;而通过`popViewControllerAnimated(_:)`的方法来从导航栈中移除(pop)某个试图控制器。每当你需要一个严格的`后进先出`方式来管理集合,堆栈都是最实用的模型。 - -下图展示了一个栈的压栈(push)/出栈(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. 移除掉一个值后,现在栈又重新只有三个值。 +1. 现在有三个值在栈中。 +2. 第四个值被压入到栈的顶部。 +3. 现在有四个值在栈中,最近入栈的那个值在顶部。 +4. 栈中最顶部的那个值被移除,或称之为出栈。 +5. 移除掉一个值后,现在栈再一次只有三个值。 -这里展示了如何写一个非泛型版本的栈,`Int`值型的栈: +下面展示了如何编写一个非泛型版本的栈,在这种情况下是 `Int` 型的栈: ```swift struct IntStack { @@ -176,37 +170,35 @@ struct IntStack { } ``` -这个结构体在栈中使用一个`Array`性质的`items`存储值。`Stack`提供两个方法:`push`和`pop`,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或*转换*)结构体的`items`数组。 +这个结构体在栈中使用一个名为 `items` 的 `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。 -上面所展现的`IntStack`类型只能用于`Int`值,不过,其对于定义一个泛型`Stack`类(可以处理*任何*类型值的栈)是非常有用的。 - -这里是一个相同代码的泛型版本: +上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。 +下面是相同代码的泛型版本: ```swift -struct Stack { - var items = [T]() - mutating func push(item: T) { +struct Stack { + var items = [Element]() + mutating func push(item: Element) { items.append(item) } - mutating func pop() -> T { + mutating func pop() -> Element { return items.removeLast() } } ``` +注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这种类型参数包裹在一对尖括号里(``),紧跟在结构体名后面。 -注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的占位类型参数为T代替了实际`Int`类型。这种类型参数包含在一对尖括号里(``),紧随在结构体名字后面。 +`Element` 为尚未提供的类型定义了一个占位名。这种尚未提供的类型可以在结构体的定义中通过 `Element` 来引用。在这种情况下,`Element` 在如下三个地方被用作占位符: -`T`定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,`T`在如下三个地方被用作节点: +- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。 +- 指定 `push(_:)` 方法的单一参数 `item` 的类型必须是 `Element` 类型。 +- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。 -- 创建一个名为`items`的属性,使用空的T类型值数组对其进行初始化; -- 指定一个包含一个参数名为`item`的`push(_:)`方法,该参数必须是T类型; -- 指定一个`pop`方法的返回值,该返回值将是一个T类型值。 +由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,如同 `Array` 和 `Dictionary`。 -由于`Stack`是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同`Array`和`Dictionary`。 - -你可以通过在尖括号里写出栈中需要存储的数据类型来创建并初始化一个`Stack`实例。比如,要创建一个`strings`的栈,你可以写成`Stack()`: +你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack()`: ```swift var stackOfStrings = Stack() @@ -214,21 +206,21 @@ stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") -// 现在栈已经有4个string了 +// 栈中现在有 4 个字符串 ``` -下图将展示`stackOfStrings`如何`push`这四个值进栈的过程: +下图展示了 `stackOfStrings` 如何将这四个值入栈: ![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png) -从栈中`pop`并移除值"cuatro": +移除并返回栈顶部的值 `"cuatro"`,即将其出栈: ```swift let fromTheTop = stackOfStrings.pop() -// fromTheTop 等于 "cuatro", 现在栈中还有3个string +// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串 ``` -下图展示了如何从栈中pop一个值的过程: +下图展示了 `stackOfStrings` 如何将顶部的值出栈: ![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png)