泛型
通用代码使您能够编写灵活的,可重用的函数和类型,它们可以与任何类型一起使用,并符合您定义的要求。 您可以编写避免重复的代码,并以清晰抽象的方式表达其意图。
泛型是Swift最强大的特性之一,Swift标准库的大部分都是用泛型代码构建的。 实际上,即使您没有意识到,您在整个“语言指南”中都一直在使用泛型。 例如,Swift的数组和字典类型都是通用集合。 您可以创建一个保存Int值的数组,或者一个包含String值的数组,或者确实可以在Swift中创建任何其他类型的数组。 同样,您可以创建一个字典来存储任何指定类型的值,并且对该类型可以是什么没有限制。
泛型求解的问题
这是一个称为swapTwoInts(_:_ :)的标准的非泛型函数,它交换了两个Int值:
func swapTwoInts(_a: inoutInt, _b: inoutInt) {
let temporaryA = a
a = b
b = temporaryA
}
此功能利用输入输出参数交换a和b的值,如输入输出参数中所述。
swapTwoInts(_:_ :)函数将b的原始值交换为a,并将a的原始值交换为b。 你可以调用这个函数来交换两个Int变量的值:
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
swapTwoInts(_:_ :)函数很有用,但它只能用于Int值。 如果要交换两个字符串值或两个Double值,则必须编写更多函数,如下面所示的swapTwoStrings(_:_ :)和swapTwoDoubles(_:_ :)函数:
func swapTwoStrings(_a: inoutString, _b: inoutString) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_a: inoutDouble, _b: inoutDouble) {
let temporaryA = a
a = b
b = temporaryA
}
您可能已经注意到swapTwoInts(_:_ :),swapTwoStrings(_:_ :)和swapTwoDoubles(_:_ :)函数的主体是相同的。 唯一的区别是它们接受的值的类型(Int,String和Double)。
编写一个交换任何类型的两个值的单个函数更有用,而且更灵活。 通用代码使您能够编写这样的功能。 (这些函数的通用版本定义如下。)
注意
在所有三种功能中,a和b的类型必须相同。 如果a和b不是同一类型,则不可能交换它们的值。 Swift是一种类型安全的语言,不允许(例如)一个String类型的变量和一个Double类型的变量来相互交换值。 试图这样做会导致编译时错误。
通用函数
通用函数可以处理任何类型。 这是上面的swapTwoInts(_:_ :)函数的一个通用版本,称为swapTwoValues(_:_ :):
func swapTwoValues<T>(_a: inoutT, _b: inoutT) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues(_:_ :)函数的主体与swapTwoInts(_:_ :)函数的主体相同。 但是,swapTwoValues(_:_ :)的第一行与swapTwoInts(_:_ :)略有不同。 以下是第一行比较的方式:
func swapTwoInts(_a: inoutInt, _b: inoutInt)
func swapTwoValues<T>(_a: inoutT, _b: inoutT)
该函数的通用版本使用占位符类型名称(在本例中称为T),而不是实际类型名称(如Int,String或Double)。占位符类型名称并没有说T应该是什么,但它确实表示a和b必须是T的相同类型,无论T是什么。每次调用swapTwoValues(_:_ :)函数时,都会确定用于代替T的实际类型。
泛型函数和非泛型函数之间的另一个区别是泛型函数的名称(swapTwoValues(_:_ :))后面跟着尖括号(<T>)中的占位符类型名称(T)。括号告诉Swift T是swapTwoValues(_:_ :)函数定义中的占位符类型名称。因为T是一个占位符,所以Swift不会查找名为T的实际类型。
swapTwoValues(_:_ :)函数现在可以与swapTwoInts相同的方式调用,只要它可以传递任何类型的两个值,只要这两个值的类型相同即可。每次调用swapTwoValues(_:_ :)时,用于T的类型都是从传递给函数的值的类型中推断出来的。
在下面的两个例子中,T分别被推断为Int和String:
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
注意
上面定义的swapTwoValues(_:_ :)函数受Swift标准库中一个名为swap的通用函数的启发,并且该函数会自动供您在应用程序中使用。如果您需要在自己的代码中使用swapTwoValues(_:_ :)函数的行为,则可以使用Swift的现有swap(_:_ :)函数,而不是提供自己的实现。
类型参数
在上面的swapTwoValues(_:_ :)示例中,占位符类型T是类型参数的示例。类型参数指定并命名占位符类型,并在一对匹配的尖括号(如<T>)之间紧跟在函数名称后。
一旦你指定了一个类型参数,你可以用它来定义函数参数的类型(比如swapTwoValues(_:_ :)函数的a和b参数),或者函数的返回类型,或者类型函数体内的注释。在每种情况下,只要函数被调用,类型参数就会被实际类型替换。 (在上面的swapTwoValues(_:_ :)示例中,第一次调用函数时,T被替换为Int,第二次调用时被替换为String。)
您可以通过在尖括号内写入多个类型参数名称来提供多个类型参数,并用逗号分隔。
命名类型参数
在大多数情况下,类型参数具有描述性名称,例如Dictionary <Key,Value>中的Key和Value以及Array <Element>中的元素,它告诉读者有关类型参数与其在泛型中使用的类型或函数之间的关系然而,当它们之间没有有意义的关系时,使用单个字母(例如T,U和V)命名它们是传统的,例如上面的swapTwoValues(_:_ :)函数中的T。
注意
总是给上层骆驼案例名称(如T和MyTypeParameter)的类型参数指明它们是类型的占位符,而不是值。
泛型类型
除了泛型函数外,Swift还允许您定义自己的泛型类型。这些是自定义类,结构和枚举,可以与任何类型一起使用,与Array和Dictionary类似。
本节介绍如何编写一个名为Stack的通用集合类型。堆栈 stack 是一组有序的值,类似于数组,但具有比Swift的Array类型更受限制的一组操作。数组允许在数组中的任何位置插入和移除新项目。但是,堆栈stack允许将新项目仅附加到集合的末尾(称为将新值推入堆栈)。同样,一个堆栈允许只从集合的末尾删除项目(称为从堆栈中弹出一个值)。
注意
UINavigationController类使用堆栈的概念来对其导航层次结构中的视图控制器建模。您可以调用UINavigationController类pushViewController(_:animated :)方法将视图控制器添加(或推送)到导航堆栈,以及从导航堆栈中删除(或弹出)视图控制器的popViewControllerAnimated(_ :)方法。无论何时需要严格的“先进先出”方法来管理集合,堆栈都是非常有用的集合模型。
下图显示了堆栈的推送和弹出行为:
目前在堆栈中有三个值。
第四个值被压入栈顶。
堆栈现在包含四个值,最近的一个在顶部。
弹出堆栈中的顶层项目。
弹出一个值后,堆栈再次保存三个值。
以下是如何编写堆栈的非通用版本,在这种情况下为一堆Int值:
struct IntStack {
var items = [Int]()
mutating func push(_item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
该结构使用名为items的数组属性来将值存储在堆栈中。 Stack提供了两种方法push和pop,可以在堆栈上和堆栈之间推送和弹出值。 这些方法被标记为变异,因为它们需要修改(或变异)结构的项目数组。
但是,上面显示的IntStack类型只能与Int值一起使用。 定义一个通用的Stack类会更有用,它可以管理任何类型的值的堆栈。
以下是相同代码的通用版本:
struct Stack<Element> {
var items = [Element]()
mutating func push(_item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
注意Stack的泛型版本与非泛型版本基本相同,但是使用名为Element的类型参数而不是实际的Int类型。该类型参数写在结构名称后面的一对尖括号(<Element>)中。
元素为稍后提供的类型定义一个占位符名称。这种未来类型可以在结构定义中的任何地方称为元素。在这种情况下,Element被用作三个地方的占位符:
创建一个名为items的属性,它使用Element类型的值的空数组进行初始化
指定push(_ :)方法有一个名为item的参数,该参数必须是Element类型
要指定pop()方法返回的值将是Element类型的值
因为它是一个泛型类型,Stack可用于在Swift中创建任何有效类型的堆栈,与Array和Dictionary类似。
通过在尖括号中写入要存储在堆栈中的类型来创建新的Stack实例。例如,要创建一个新的字符串堆栈,您可以编写Stack <String>():
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
下面是stackOfStrings在将这四个值推入堆栈后的样子:
从堆栈中弹出一个值将删除并返回最高值“cuatro”:
let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
以下是弹出最高值后堆栈的外观:
扩展一个通用类型
扩展泛型时,不提供类型参数列表作为扩展定义的一部分。 相反,原始类型定义中的类型参数列表在扩展的主体中可用,并且原始类型参数名称用于引用原始定义中的类型参数。
以下示例扩展了泛型Stack类型以添加名为topItem的只读计算属性,该属性返回堆栈中的顶层项目,而不弹出堆栈中的顶层项目:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
topItem属性返回一个可选的Element类型值。 如果堆栈为空,则topItem返回nil; 如果堆栈不是空的,topItem返回items数组中的最后一个项目。
请注意,此扩展未定义类型参数列表。 相反,Stack类型的现有类型参数名称Element在扩展中用于指示topItem计算属性的可选类型。
topItem计算属性现在可以与任何Stack实例一起使用,以访问和查询其顶层项目而不删除它。
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."
通用类型的扩展还可以包括扩展类型的实例必须满足以获得新功能的要求,正如在下面的通用Where子句的扩展中所讨论的。
类型约束
swapTwoValues(_:_ :)函数和堆栈类型可以使用任何类型。但是,对可以与泛型函数和泛型类型一起使用的类型强制执行某些类型约束有时很有用。类型约束指定类型参数必须从特定的类继承,或者符合特定的协议或协议组合。
例如,Swift的字典类型对可用作字典键的类型进行了限制。如字典所述,字典键的类型必须是可散列的。也就是说,它必须提供一种使自己具有唯一性的方式。字典需要它的键是可散列的,以便它可以检查它是否已经包含特定键的值。如果没有这个要求,Dictionary无法判断它是应该插入还是替换某个特定键的值,也不能为给定键已经在词典中找到值。
这个要求通过对Dictionary的键类型的类型约束来实施,该类型约束指定键类型必须符合Hashable协议,该协议是Swift标准库中定义的特殊协议。 Swift的所有基本类型(如String,Int,Double和Bool)默认都是可散列的。
您可以在创建自定义泛型时定义自己的类型约束,这些约束提供了泛型编程的很多功能。像Hashable这样的抽象概念在概念特征方面表征类型,而不是它们的具体类型。
类型约束语法
通过在类型参数的名称之后放置单个类或协议约束(用冒号分隔)作为类型参数列表的一部分来编写类型约束。泛型函数的类型约束的基本语法如下所示(尽管泛型类型的语法是相同的):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
上面的假设函数有两个类型参数。 第一个类型参数T有一个类型约束,它要求T是SomeClass的子类。 第二个类型参数U有一个类型约束,要求U符合协议SomeProtocol。
类型约束在行动
这里有一个叫做findIndex(ofString:in :)的非泛型函数,它被赋予一个String值来查找和找到它的String值的数组。 findIndex(ofString:in :)函数返回一个可选的Int值,如果找到它,它将成为数组中第一个匹配字符串的索引;如果找不到字符串,则返回nil:
func findIndex(ofStringvalueToFind: String, inarray: [String]) -> Int? {
for (index, value) inarray.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
find Index(ofString:in:)函数可用于在字符串数组中查找字符串值:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
然而,查找数组中的值的索引的原理并不仅适用于字符串。 您可以使用一些类型T的值替换字符串来替代泛型函数来编写相同的功能。
以下是您可能期望的写入findIndex(ofString:in :)的通用版本,名为findIndex(of:in :)。 请注意,该函数的返回类型仍然是Int?,因为该函数返回一个可选的索引号,而不是数组中的可选值。 不过要注意的是,这个函数不能编译,因为在这个例子之后解释的原因:
func findIndex<T>(ofvalueToFind: T, inarray:[T]) -> Int? {
for (index, value) inarray.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
这个函数不像上面写的那样编译。问题在于相等性检查,“if value == valueToFind”。并非Swift中的每个类型都可以与等号运算符(==)进行比较。例如,如果您创建自己的类或结构来表示复杂的数据模型,那么对于该类或结构而言,“等于”的含义不是Swift可以为您预测的。因此,不可能保证此代码适用于所有可能的类型T,并且在尝试编译代码时会报告相应的错误。
然而,所有的东西都不会丢失。 Swift标准库定义了一个名为Equatable的协议,该协议要求任何符合类型实现等于运算符(==)和不等于运算符(!=)来比较该类型的任何两个值。所有Swift的标准类型都自动支持Equatable协议。
任何类型的Equatable都可以安全地使用findIndex(of:in :)函数,因为它保证支持等于运算符。为了表达这个事实,当你定义函数时,你需要编写一个Equatable类型约束作为类型参数定义的一部分:
func findIndex<T: Equatable>(ofvalueToFind: T, inarray:[T]) -> Int? {
for (index, value) inarray.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
find Index(of:in :)的单一类型参数写为T:Equatable,意思是“任何符合Equatable协议的类型T.”
find Index(of:in :)函数现在编译成功,可以与任何类型的Equatable一起使用,例如Double或String:
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
关联类型
在定义协议时,将一个或多个关联类型声明为协议定义的一部分有时很有用。 关联的类型为占用的名称提供了一个用作协议一部分的类型。 在采用该协议之前,不会指定用于该关联类型的实际类型。 关联的类型由associatedtype关键字指定。
下面是一个名为Container的协议示例,该协议声明了一个名为Item的关联类型:
protocol Container {
associated typeItem
mutating func append(_item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container协议定义了任何容器必须提供的三个必需的功能:
必须可以使用append(_ :)方法向容器添加新项目。
必须可以通过返回Int值的count属性来访问容器中的项目数。
必须可以使用带有Int索引值的下标来检索容器中的每个项目。
该协议并未指定容器中的项目应如何存储或允许的类型。该协议仅指定任何类型必须提供的三个功能位才能被视为Container。符合类型可以提供额外的功能,只要满足这三个要求即可。
任何符合Container协议的类型都必须能够指定它所存储的值的类型。具体来说,它必须确保只有正确类型的项目才会添加到容器中,并且必须清楚其下标所返回项目的类型。
为了定义这些需求,Container协议需要一种方法来引用容器将容纳的元素的类型,而不知道特定容器的类型。 Container协议需要指定传递给append(_ :)方法的任何值必须与容器的元素类型具有相同的类型,并且容器下标返回的值将与容器的元素类型具有相同的类型。
为了实现这一点,Container协议声明了一个名为Item的关联类型,写为associatedtype Item。该协议没有定义什么项目 - 该信息留给任何符合类型提供。尽管如此,Item别名提供了一种方法来引用容器中的项目类型,并定义一个用于append(_:)方法和下标的类型,以确保强制执行任何Container的预期行为。
以下是来自早期的非通用IntStack类型的一个版本,它适用于符合Container协议:
struct IntStack: Container {
// original IntStack implementation
var items = [Int]()
mutating func push(_item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
type aliasItem = Int
mutating func append(_item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
IntStack类型实现了所有三个Container协议的要求,并且在每种情况下都包装了IntStack类型现有功能的一部分以满足这些要求。
此外,IntStack指定对于此Container的实现,要使用的相应Item是Int类型。 typealias Item = Int的定义将Item的抽象类型转换为Container实现的具体类型Int。
感谢Swift的类型推断,你实际上不需要声明Int的具体Item作为IntStack定义的一部分。因为IntStack符合Container协议的所有要求,所以Swift可以简单地通过查看append(_ :)方法的item参数的类型和下标的返回类型来推断使用的相应Item。事实上,如果你从上面的代码中删除了typealias Item = Int行,所有东西仍然有效,因为很明显Item应该使用什么类型。
您还可以使通用堆栈类型符合Container协议:
struct Stack<Element>: Container {
// original Stack<Element> implementation
var items = [Element]()
mutating func push(_item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
这次,类型参数Element用作append(_ :)方法的item参数的类型和下标的返回类型。 因此Swift可以推断出Element是适合用作此特定容器的Item的类型。
扩展现有类型以指定关联类型
您可以扩展现有类型以添加协议,如添加扩展协议一致性中所述。 这包括一个关联类型的协议。
Swift的Array类型已经提供了一个append(_ :)方法,一个count属性和一个带Int索引的下标来检索它的元素。 这三个功能符合Container协议的要求。 这意味着只需声明Array采用协议,就可以扩展Array以符合Container协议。 您可以使用空白扩展名来执行此操作,如使用扩展声明协议采用中所述:
extensionArray: Container {}
Array的现有append(_ :)方法和下标使Swift能够推断Item的适当类型,就像上面的通用Stack类型一样。 定义此扩展后,您可以使用任何Array作为容器。
使用类型注释来约束关联的类型
您可以将类型注释添加到协议中的关联类型中,以要求符合类型满足类型注释描述的约束。 例如,下面的代码定义了Container的一个版本,它要求容器中的项目可以相互等同。
protocol Container {
associated typeItem: Equatable
mutating func append(_item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
为了符合此版本的Container,容器的Item类型必须符合Equatable协议。
在其关联类型的约束中使用协议
协议可以作为其自身要求的一部分出现。 例如,这是一个改进Container协议的协议,添加了后缀(_ :)方法的要求。 后缀(_ :)方法从容器的末尾返回给定数量的元素,将它们存储在Suffix类型的实例中。
protocol SuffixableContainer: Container {
associated typeSuffix: SuffixableContainerwhereSuffix.Item == Item
func suffix(_size: Int) -> Suffix
}
在这个协议中,后缀是一个关联的类型,就像上面容器例子中的Item类型。 后缀有两个约束:它必须符合SuffixableContainer协议(当前正在定义的协议),其Item类型必须与容器的Item类型相同。 Item上的约束是一个通用的where子句,在下面的关联类型中使用通用Where子句进行了讨论。
这是以前的Stack类型的扩展,它增加了对SuffixableContainer协议的一致性:
extension Stack: SuffixableContainer {
func suffix(_size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
letsuffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
在上面的例子中,Stack的后缀关联类型也是Stack,所以Stack上的后缀操作返回另一个Stack。 或者,符合SuffixableContainer的类型可以具有与其自身不同的后缀类型 - 这意味着后缀操作可以返回不同的类型。 例如,下面是使用Stack <Int>作为其后缀类型而不是IntStack添加SuffixableContainer一致性的非泛型IntStack类型的扩展:
extension IntStack: SuffixableContainer {
func suffix(_size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack<Int>.
}
通用条款
如类型约束中所述,类型约束使您能够定义与通用函数,下标或类型关联的类型参数的需求。
定义关联类型的需求也很有用。你通过定义一个通用的where子句来做到这一点。通用的where子句使您能够要求关联类型必须符合特定协议,或者某些类型参数和关联类型必须相同。通用where子句从where关键字开始,后跟相关类型的约束或类型和关联类型之间的相等关系。您在类型或函数的主体的开始大括号之前编写通用where子句。
下面的例子定义了一个名为allItemsMatch的通用函数,该函数检查两个Container实例是否以相同的顺序包含相同的项目。如果所有项都匹配,则该函数返回一个布尔值,如果不匹配,则返回值为false。
要检查的两个容器不必是相同类型的容器(尽管它们可以),但它们必须保持相同类型的容器。此要求通过类型约束和通用where子句的组合来表示:
func allItemsMatch<C1: Container, C2: Container>
(_someContainer: C1, _anotherContainer: C2) -> Bool
whereC1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
该函数接受两个名为someContainer和anotherContainer的参数。 someContainer参数的类型为C1,另一个容器参数的类型为C2。 C1和C2都是调用函数时要确定的两种容器类型的类型参数。
以下要求放在函数的两个类型参数上:
C1必须符合Container协议(写为C1:Container)。
C2还必须符合容器协议(写为C2:容器)。
C1的项目必须与C2的项目相同(写作C1.Item == C2.Item)。
C1的Item必须符合Equatable协议(写为C1.Item:Equatable)。
第一个和第二个需求在函数的类型参数列表中定义,第三个和第四个需求在函数的通用where子句中定义。
这些要求意味着:
someContainer是C1类型的容器。
anotherContainer是C2类型的容器。
someContainer和anotherContainer包含相同类型的项目。
someContainer中的项目可以用不等于运算符(!=)来检查它们是否彼此不同。
第三个和第四个要求相结合意味着anotherContainer中的项目也可以用!=运算符进行检查,因为它们与someContainer中的项目完全相同。
这些要求使allItemsMatch(_:_ :)函数能够比较两个容器,即使它们属于不同的容器类型。
allItemsMatch(_:_ :)函数首先检查两个容器是否包含相同数量的项目。如果它们包含不同数量的项目,则它们无法匹配,并且该函数返回false。
在做这个检查之后,函数使用for-in循环和半开范围运算符(.. <)迭代someContainer中的所有项目。对于每个项目,函数检查来自someContainer的项目是否与anotherContainer中的相应项目不相等。如果两个项目不相等,则两个容器不匹配,并且该函数返回false。
如果循环完成而没有发现不匹配,则两个容器匹配,并且该函数返回true。
以下是allItemsMatch(_:_ :)函数的外观:
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// Prints "All items match."
上面的示例创建一个Stack实例来存储String值,并将三个字符串推入堆栈。 该示例还创建了一个Array实例,该实例使用包含与堆栈相同的三个字符串的数组字面值进行初始化。 即使堆栈和阵列属于不同的类型,它们都符合Container协议,并且都包含相同类型的值。 因此可以用这两个容器作为参数调用allItemsMatch(_:_ :)函数。 在上面的示例中,allItemsMatch(_:_ :)函数可以正确报告两个容器中的所有项目都匹配。
扩展与通用的where子句
您也可以使用通用的where子句作为扩展的一部分。 下面的例子从前面的例子中扩展了泛型栈结构来添加isTop(_ :)方法。
extension StackwhereElement: Equatable {
func isTop(_item: Element) -> Bool {
guardlettopItem = items.lastelse {
return false
}
return topItem == item
}
}
这个新的isTop(_ :)方法首先检查堆栈是否为空,然后将给定的项目与堆栈的最上面的项目进行比较。 如果您尝试在没有通用where子句的情况下执行此操作,则会出现问题:isTop(_ :)的实现使用==运算符,但Stack的定义不要求其项目可以相等,因此使用 ==运算符会导致编译时错误。 使用通用的where子句可让您向扩展添加新的需求,以便只有当堆栈中的项目可以相等时,扩展才会添加isTop(_ :)方法。
以下是isTop(_ :)方法的实际操作方式
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// Prints "Top element is tres."
如果您尝试在元素不相等的堆栈上调用isTop(_ :)方法,则会发生编译时错误。
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
您可以使用通用的where子句和协议的扩展名。 下面的例子从前面的例子中扩展了Container协议以添加一个startsWith(_ :)方法。
extension ContainerwhereItem: Equatable {
func startsWith(_item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
startsWith(_ :)方法首先确保容器至少有一个项目,然后检查容器中的第一个项目是否与给定的项目相匹配。 这个新的startsWith(_ :)方法可以与任何符合Container协议的类型一起使用,包括上面使用的堆栈和数组,只要容器的项目是可以等同的。
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
上例中的generic where子句要求Item符合协议,但您也可以编写一个通用where子句,该子句需要Item为特定类型。 例如:
extension ContainerwhereItem == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"
此示例将一个average()方法添加到Item类型为Double的容器。 它迭代容器中的项目以将它们相加,然后除以容器的计数来计算平均值。 它明确地将计数从Int转换为Double,以便能够进行浮点除法。
您可以将多个需求包含在作为扩展的一部分的通用where子句中,就像您可以为其他地方编写的通用where子句一样。 用逗号分隔列表中的每个要求。
与通用条款相关联的类型
您可以在关联的类型中包含通用的where子句。 例如,假设你想创建一个包含迭代器的Container版本,就像Sequence协议在标准库中使用的一样。 以下是你如何写的:
protocol Container {
associated typeItem
mutating func append(_item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associated typeIterator: IteratorProtocolwhereIterator.Element == Item
func makeIterator() -> Iterator
}
Iterator上的generic where子句要求迭代器必须遍历与容器项目相同的项目类型的元素,而不管迭代器的类型如何。 makeIterator()函数提供对容器迭代器的访问。
对于从另一个协议继承的协议,通过在协议声明中包含泛型where子句,可以为继承的关联类型添加约束。 例如,下面的代码声明了一个ComparableContainer协议,该协议要求Item符合Comparable:
protocolComparableContainer: ContainerwhereItem: Comparable { }
通用下标
下标可以是通用的,并且它们可以包括通用的where子句。 您在下标后面的尖括号中写入占位符类型名称,然后在下标正文的开始大括号之前写下一个通用where子句。 例如:
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
Container协议的这个扩展添加了一个下标,它接受一系列索引并返回一个包含每个给定索引处项目的数组。 这个通用下标的约束如下:
尖括号中的通用参数Indices必须是符合来自标准库的Sequence协议的类型。
下标采用单个参数索引,这是该指数类型的一个实例。
泛型where子句要求序列的迭代器必须遍历Int类型的元素。 这确保序列中的索引与用于容器的索引相同。
综合起来,这些约束意味着为indices参数传递的值是一个整数序列。