您的位置:首页 > 移动开发 > Swift

[翻译]Swift编程语言—— 泛型

2015-06-17 11:08 441 查看

泛型

泛型能让你根据你的需要,写出适用于任何类型的、灵活的、可重用的函数和类型。写出能够避免重复而且清晰概要的代码。

泛型是Swfit最重要的恶性之一,许多Swift的标准库都采用了泛型。实际上,在语言引导(Lanagage Guide)中,你已经使用过泛型了,尽管没有意识。比如Swift的Array和Dictionary类型都是泛型集合。在Swift中,可以创建一个存放Int数值的数组,也可以创建一个存放String数值的数组,甚至可以创建一个可以存放任意类型的数组。类似的,也可以创建一个存储任意指定类型的字典,对类型没有任何限制。

泛型要解决的问题

这里有一个标准的,非泛型的函数叫做swapTwoInts,这个函数会将两个Int数值交换:

func​ ​swapTwoInts​(​inout​ ​a​: ​Int​, ​inout​ ​b​: ​Int​) {
​    ​let​ ​temporaryA​ = ​a
​    ​a​ = ​b
​    ​b​ = ​temporaryA
​}


这个函数使用了in-out参数来交换a和b的值,这在 In-Out参数(In-Out Parameters)一节已经说明。

swapTwoInts 函数将b的初始值换给a,a的初始值换给b。可以调用这个方法来对两个Int变量进行值交换:

var​ ​someInt​ = ​3
​var​ ​anotherInt​ = ​107
​swapTwoInts​(&​someInt​, &​anotherInt​)
​println​(​"someInt is now ​\(​someInt​)​, and anotherInt is now ​\(​anotherInt​)​"​)
​// prints "someInt is now 107, and anotherInt is now 3"


swapTwoInts 是有效的,但是它只能适用Int数值。如果想将两个String数值或者两个Double数值进行交换,就需要再写两个函数,就像下面的swapTwoStrings 和swapTwoDoubles 函数一样:

​func​ ​swapTwoStrings​(​inout​ ​a​: ​String​, ​inout​ ​b​: ​String​) {
​    ​let​ ​temporaryA​ = ​a
​    ​a​ = ​b
​    ​b​ = ​temporaryA
​}
​
​func​ ​swapTwoDoubles​(​inout​ ​a​: ​Double​, ​inout​ ​b​: ​Double​) {
​    ​let​ ​temporaryA​ = ​a
​    ​a​ = ​b
​    ​b​ = ​temporaryA
​}


你会发现swapTwoInts、swapTwoStrings和swapTwoDoubles 的函数体都是相同的。唯一不同的就是它们可以接受的参数类型分别是Int,String和Double。

写一个能够将任何类型的两个值交换的函数,将是更有效而且灵活的。使用泛型可以做到。(下面将会提供这个方法的泛型版本)

NOTE

上面所有的三个函数中,最重要的内容是a和b的类型是一样的。如果a和b不是相同的类型,那么将不能够进行交换。Swift是类型安全的语言,不允许一个String类型的变量和一个Double类型的变量相互交换值。尝试这样做会导致编译时错误。

泛型函数

泛型函数适用于任何类型。这里有一个上面swapTwoInts 函数的泛型版本,叫做swapTwoValues:

func​ ​swapTwoValues​<​T​>(​inout​ ​a​: ​T​, ​inout​ ​b​: ​T​) {
​    ​let​ ​temporaryA​ = ​a
​    ​a​ = ​b
​    ​b​ = ​temporaryA
​}


swapTwoValues 的函数体和swapTwoInts 一样。但是,swapTwoValues 的第一行和swapTwoInts的有稍许不同。下面是它们两个的对比:

func​ ​swapTwoInts​(​inout​ ​a​: ​Int​, ​inout​ ​b​: ​Int​)
​func​ ​swapTwoValues​<​T​>(​inout​ ​a​: ​T​, ​inout​ ​b​: ​T​)


泛型版本的函数使用了一个占位(placeholder )类型名字(例子中叫做T)替代了实际类型的名字(比如Int、String或者Double)。占位类型名字没有规定T需要是什么类型,只规定了a和b的类型必须是相同的类型T,不论T是什么类型。每当swapTwoValues 函数被调用时,实际的类型将会替代T。

另外一个区别是,泛型函数的名字(swapTwoValues)后面用一对尖括号()放了占位类型名称(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"


NOTE

swapTwoValues 函数功能在通用函数swap中有了,swap是Swift标准库中的一部分,你的apps中可以直接使用。如果需要swapTwoValues 函数的功能,直接使用Swift中已经存在的swap函数就行了。

类型参数

在上面的swapTwoValues 例子中,占位类型T就是一个类型参数。类型参数指定和命名一个占位类型,写在在函数名字之后的一对尖括号内(比如)。

一旦指定了一个类型参数,就可以使用它定义函数的参数类型(比如swapTwoValues 函数的a和b参数),或者作为函数的返回值类型,或者作为函数体内的类型注释(type annotaiton)。这些情形下,当函数被调用的时候,代表类型参数的占位类型会被实际类型替换掉。(在上面的swapTwoValues 函数中,第一次调用该函数时会将T替换为int,第二次调用该函数时会替换为String)

可以在尖括号内书写多个类型参数,用逗号分割它们即可。

命名类型参数

当一个泛型函数或者泛型类型只引用一个占位类型(比如前文的swapTwoValues 泛型函数、比如一个只存储单一类型值的泛型集合(就像Array)),通常是用单一字符T给类型参数命名。其实,你可以使用任何合法的标示符作为类型参数名称。

如果定义复杂的泛型函数、或者有多个参数的泛型类型,最好使用具有表现力的类型参数名称。比如,Swift的Dictionary类型就有两个类型参数——一个是它的键,另一个是它的值。如果由你来定义Dictionary,为了在使用时能记得它们各自的含义,你也会给它们用Key和Vlaue命名。

NOTE

通常会用UpperCamelCase (大骆驼拼写法)命名(比如T和Key)占位类型,这样表明它们是类型而不是值。

泛型类型

泛型函数之外,Swift允许自定义泛型类型。这些自定义的类、结构体和枚举可以用于任何类型,与Array和Dictionary一样。

本节展示了如何写一个泛型集合类型:Stack(译者:栈)。栈是一些规则的值的集合,类似一个数组,但是比Swfit的Array类型操作限制更多。数组允许向数组的任意位置添加新元素和在任意位置删除元素。但是栈只允许在其末尾添加新元素(就是所说的将一个新的值压入(pushing)栈内)。同样,也只允许从其末尾删除一个元素(就是所说的从栈内弹出一个值)。

NOTE

栈的这种观念在UINavigationController 类中得到了应用,UINavigationController 类表现的是在导航层级中的视图控制器的集合。可以调用UINavigationController 类的pushViewController:animated:方法添加(或者说压入)一个视图控制器到导航栈,调用它的 popViewControllerAnimated:方法从导航栈移除(或者说弹出)一个视图控制器。当需要严格的“后进先出(last in,first out)”模式管理一个集合,可以采用栈。

下面的图标展示了栈的压入和弹出行为:



1:栈内当前有三个值。

2:第四个值被“压”到栈的顶端。

3:现在栈内有四个值,最新的一个在最上面。

4:最上面的元素被移除,或者说“弹出”。

5:在弹出一个值后,栈再次有三个值了。

下面是如何不用泛型写一个栈,这里使用的是Int类型的栈:

​struct​ ​IntStack​ {
​    ​var​ ​items​ = [​Int​]()
​    ​mutating​ ​func​ ​push​(​item​: ​Int​) {
​        ​items​.​append​(​item​)
​    }
​    ​mutating​ ​func​ ​pop​() -> ​Int​ {
​        ​return​ ​items​.​removeLast​()
​    }
​}


这个结构体使用了一个Array属性items存储栈中的值。Stack提供了两个方法,push和pop,分别用来对栈压入和弹出值。这些方法被用mutating标记,因为这两个方法会修改(或改变)结构体的items数组。

上面的IntStack类型只能存放Int类型的值。定义一个泛型版本的Stack类将会是非常有用的,那样栈可以存放任意类型的值。

这里有一个泛型版本的栈:

struct​ ​Stack​<​T​> {
​    ​var​ ​items​ = [​T​]()
​    ​mutating​ ​func​ ​push​(​item​: ​T​) {
​        ​items​.​append​(​item​)
​    }
​    ​mutating​ ​func​ ​pop​() -> ​T​ {
​        ​return​ ​items​.​removeLast​()
​    }
​}


这个版本和非泛型版本本质上是相同的,但是用占位类型参数T替代了实际的类型Int。这里的类型参数写在一对尖括号内,紧跟在结构体名字后面。

T为稍后会提供的“某个类型T”定义了一个占位名字。在这个结构体的定义中,可以使用”T”指代将来的类型。在这个例子中,T有三次被作为占位符使用:

创建一个叫做items的属性,这个属性被用一个空的T类型的数组初始化

指定push方法有唯一的一个参数item,item的类型必须是T

指定pop方法返回的值类型是T

因为是泛型类型的,Stack可以被用来创建一个支持任意Swift允许类型的栈,就像Array和Dictionary一样。

在尖括号内写入栈可以存放的类型,就可以创建一个新的Stack实例了。比如下面,想要创建一个存储字符串的栈,可以这样写: Stack():

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​: ​T​? {
​        ​return​ ​items​.​isEmpty​ ? ​nil​ : ​items​[​items​.​count​ - ​1​]
​    }
​}


topItem属性返回一个可选的T类型的值。如果栈是空的,topItem将会返回nil;如果栈不是空的,topItem返回在items数组中的最后一项。

需要注意的是扩展没有定义一个类型参数列表。而是使用了Stack类型的已经存在的类型参数名称T,它被用来表述topItem计算属性的可选类型。

topItem计算属性可以应用在任意的Stack实例,通过它可以不经过弹出操作就获得栈顶元素:

​if​ ​let​ ​topItem​ = ​stackOfStrings​.​topItem​ {
​    ​println​(​"The top item on the stack is ​\(​topItem​)​."​)
​}
​// prints "The top item on the stack is tres."


类型约束(Type Constraints)

swapTwoValues 函数和Stack类型适用于任何类型。但是,有时需要对泛型方法和反省类型的适用类型进行强制类型约束(Type Constraints)。类型约束限制了一个类型参数必须继承自一个超类、或遵循一个特定的协议或者协议组。

比如,Swfit的Dictionary类型就对于那些类型可以作为字典的键就有限制。就像 字典(Dictionaries)一节描述的一样,字典的键必须是hashable类型的。也就是说,字典的键的类型必须提供它自身可以被唯一标识的方式。Dictionary要求它的键是hashable类型的,这样字典就可以判断是否已经包含了特定键对应的值。没有这个要求,Dictionary不能判断是要插入特定键对应的新值还是替换,也不能根据给定的键找到对应的值。

这些通过Dictionary类型的键的类型约束被强制要求了,指定键必须遵循Hashable协议,这个协议是在Swift 标准类库定义的。所有的Swift的基本类型(比如String、Int、Double和Bool)都默认是Hashable类型的。

当创建自己的泛型类型时可以定义自己的类型约束,这样会提供许多泛型编程的能力。像Hashable类型这样的抽象概念是根据它们的概念特性而来的,而非具体类型。

类型约束的语法

在类型参数列表中,一个类型参数名称后放置一个唯一的类或者协议的名字,用冒号分割它们,这样就定义了类型约束。下面展示了一个泛型函数上的类型约束基本语法(语法和泛型类型一样):

​func​ ​someFunction​<​T​: ​SomeClass​, ​U​: ​SomeProtocol​>(​someT​: ​T​, ​someU​: ​U​) {
​    ​// function body goes here
​}


上面假设的函数有两个类型参数。第一个类型参数T,有一个类型约束,要求T必须是SomeClass的子类。第二个类型参数U,有一个类型约束,要求U必须遵循SomeProtocol协议。

类型约束实战

这里有一个非泛型的函数findStringIndex,这个函数会从给定的String类型数组中找到给定的String。这个函数返回一个可选的Int值,这个值或者是该字符串匹配数组内容的第一个位置索引,或者就是nil(没有找到):

func​ ​findStringIndex​(​array​: [​String​], ​valueToFind​: ​String​) -> ​Int​? {
​    ​for​ (​index​, ​value​) ​in​ ​enumerate​(​array​) {
​        ​if​ ​value​ == ​valueToFind​ {
​            ​return​ ​index
​        }
​    }
​    ​return​ ​nil
​}


findStringIndex 函数用来在字符串类型的数组中查找一个字符串:

let​ ​strings​ = [​"cat"​, ​"dog"​, ​"llama"​, ​"parakeet"​, ​"terrapin"​]
​if​ ​let​ ​foundIndex​ = ​findStringIndex​(​strings​, ​"llama"​) {
​    ​println​(​"The index of llama is ​\(​foundIndex​)​"​)
​}
​// prints "The index of llama is 2"


在数组中查找一个匹配值的这套原则不仅仅适用于字符串类型。可以用泛型写一个具有同样功能的函数,通过用不确定的类型T替换字符串类型这样的方式。

这里就是一个期待的泛型版本的findStringIndex,叫做findIndex。它的返回值仍然是Int?,这是因为函数返回一个可惜俺的索引值,而不是数组中的可选值。警告:这个函数不能编译通过,具体的原因在后面解释:

​func​ ​findIndex​<​T​>(​array​: [​T​], ​valueToFind​: ​T​) -> ​Int​? {
​    ​for​ (​index​, ​value​) ​in​ ​enumerate​(​array​) {
​        ​if​ ​value​ == ​valueToFind​ {
​            ​return​ ​index
​        }
​    }
​    ​return​ ​nil
​}


这个函数不能编译通过。问题行“if value == valueToFind”进行了比较操作。并不是所有的Swift类型都可以采用等于操作符(==)进行比较。对于自己定义的表现复杂数据模型的类或者结构体,“等于”的含义和Swift为你猜测的就不会一样了。正因为如此,不能确保这样的代码对所有可能的类型T都适用,在尝试编译这些代码时,对应的错误会报告。

尽管如此,还有一线希望。Swift标准库中定义了一个叫做Equatable的协议,这个协议要求所有遵循它的类型都实现等于操作符(==)和不等于操作符(!=),用来比较两个不同类型的值。所有的Swift的标准类型自动都遵循Equatable 协议。

任何只要遵循Equatable 协议的类型都可以在findIndex函数中安全的使用,因为这些类型肯定支持等于操作符。为了做到这点,在定义函数的时候,需要在类型参数定义中写一个Equatable 协议的类型约束:

func​ ​findIndex​<​T​: ​Equatable​>(​array​: [​T​], ​valueToFind​: ​T​) -> ​Int​? {
​    ​for​ (​index​, ​value​) ​in​ ​enumerate​(​array​) {
​        ​if​ ​value​ == ​valueToFind​ {
​            ​return​ ​index
​        }
​    }
​    ​return​ ​nil
​}


这里,findIndex 函数的唯一的一个类型参数被写作:T: Equatable,它的意思是“任何遵循Equatable协议的T类型”。

现在findIndex函数编译成功,可以对遵循Equatable协议的任何类型进行操作了,比如Double或者String:

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
​let​ ​stringIndex​ = ​findIndex​([​"Mike"​, ​"Malcolm"​, ​"Andrea"​], ​"Andrea"​)
​// stringIndex is an optional Int containing a value of 2


关联类型(Associated Types)

当定义一个协议时,有时需要在协议定义中声明一个或多个关联类型。一个关联类型为在协议中用到的类型提供了占位名字(或者称为别名)。直到协议被实现的时候关联类型才会被指定为实际的类型。使用typealias关键字指定关联类型。

关联类型实战(Associated Types in Action)

这里有一个叫做Container的协议,它声明了一个关联类型ItemType:

protocol​ ​Container​ {
​    ​typealias​ ​ItemType
​    ​mutating​ ​func​ ​append​(​item​: ​ItemType​)
​    ​var​ ​count​: ​Int​ { ​get​ }
​    ​subscript​(​i​: ​Int​) -> ​ItemType​ { ​get​ }
​}


Container协议定义了三个需要提供的功能:

必须可以通过append方法容器添加一个新元素。

必须可以通过count属性得到一个表示容器内所有元素个数的Int值。

必须可以通过带入一个Int类型的索引值的下标能能够得到容器中的对应元素。

这个协议没有指定容器中的元素如何被存储,也没有指定容器能够接纳什么类型。这个协议仅仅指定了这样三个功能,要求所有遵循它的类型都必须要提供。只要满足了这三个要求,协议的实现类也可以提供额外的功能。

任何遵循Container协议的类型必须能够指定它里面可以存储的类型。除此之外,还要确保只有类型正确的元素才可以添加到容器中,确保通过下标返回的元素类型必须是明确的。

为了定义这些要求,Container协议需要有一个途径能够引用容器将来可以存储的元素类型,而不必知道具体容器中元素的类型。Container协议需要规定传递给append方法的值类型要和容器元素的类型一致,需要规定通过容器的下标返回的值的类型要和容器的元素类型一致。

为了做到这些,Container协议声明了一个联合类型叫做ItemType,写作typealias ItemType。协议没有定义ItemType是谁的别名——那些信息留给协议的实现类型去提供了。尽管如此,ItemType别名还是提供了引用Container中元素类型的途径,还定义了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
​    ​typealias​ ​ItemType​ = ​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实现中ItemType的类型是Int。定义“typealias ItemType = Int”将抽象的ItemType类型转换为了在这个Container协议的实现中的具体的Int类型。

得益于Swift的类型推测功能,在IntStack定义中,不必声明一个具体Int类型给ItemType。因为IntStack遵循了所有的Container协议的要求,Swift可以推测出ItemType的恰当类型,仅仅通过查看append方法的item参数类型和下标的返回类型就可以了。实际上,即使删掉“typealias ItemType = Int”这行,因为Itemtype是什么类型已经很明白了,程序依然可以运行。

同样可以写一个遵循Container协议的泛型版本的Stack:

​struct​ ​Stack​<​T​>: ​Container​ {
​    ​// original Stack<T> implementation
​    ​var​ ​items​ = [​T​]()
​    ​mutating​ ​func​ ​push​(​item​: ​T​) {
​        ​items​.​append​(​item​)
​    }
​    ​mutating​ ​func​ ​pop​() -> ​T​ {
​        ​return​ ​items​.​removeLast​()
​    }
​    ​// conformance to the Container protocol
​    ​mutating​ ​func​ ​append​(​item​: ​T​) {
​        ​self​.​push​(​item​)
​    }
​    ​var​ ​count​: ​Int​ {
​        ​return​ ​items​.​count
​    }
​    ​subscript​(​i​: ​Int​) -> ​T​ {
​        ​return​ ​items​[​i​]
​    }
​}


这次,占位类型参数T被作为append方法的item参数类型使用,被作为下标的返回类型使用。Swift 因此可以推断出被用作这个特定容器的 ItemType 的 T 的合适类型。

为了指定关联类型来扩展一个已经存在类型(Extending an Existing Type to Specify an Associated Type)

可以扩展一个已经存在的类,使其遵循一个协议,这在 使用扩展添加对协议的支持 (Adding Protocol Conformance with an extension)有描述。这其中包括了联合类型。

Swfit的Array类型已经提供了一个append方法,一个count属性和一个根据Int索引获取元素的下标。这三个功能已经和Container协议的要求匹配了。这就意味着可以通过声明Array遵循Container 协议来扩展Array。像在 通过扩展声明遵循协议(Declaring Protocol Adoption with an extension)描述的一样,可以用一个空的扩展做到上面的想法:

​extension​ ​Array​: ​Container​ {}

数组已经存在的append方法和下标使得Swfit可以推测出ItemType实际的类型,这点和上面的泛型版本的Stack类型一样。在做过这个扩展后,就可以像使用Container一样行使用Array了。

Where 从句(Where Clauses)

类型约束,就像在 类型约束(Type Constraints)描述的一样,使得你可以定义与泛型函数(或者类型)相关的类型参数要求。

同样,定义对于关联类型的要求也是非常有用的。要做到这样,可以通过类型参数类别中的wehre从句(where clauses)来实现。where从句中可以要求关联类型遵循特定的协议、可以要求特定的类型参数和关联类型是相同的。where从句的写法是这样的:在参数列表值后紧跟一个where关键字,其后跟上一个或多个对于关联类型的约束,(可选)一个或多个对于类型和关联类型之间的等价判断。

下面的例子定义了一个泛型函数,叫做allItemsMatch,它会检查两个Container实例中是否以同样的顺序包含同样的元素。这个函数会在所有元素都匹配的情况下返回true,一旦有不同则返回false。

将要被检查的两个容器不必是相同类型的容器(尽管它们也可以是),但是他们必须是存放了相同类型的元素。这些要求通过类型约束和where从句的组合能够实现:

func​ ​allItemsMatch​<
​    ​C1​: ​Container​, ​C2​: ​Container
​    ​where​ ​C1​.​ItemType​ == ​C2​.​ItemType​, ​C1​.​ItemType​: ​Equatable​>
​    (​someContainer​: ​C1​, ​anotherContainer​: ​C2​) -> ​Bool​ {
​
​        ​// 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 are equivalent
​        ​for​ ​i​ ​in​ ​0​..<​someContainer​.​count​ {
​            ​if​ ​someContainer​[​i​] != ​anotherContainer​[​i​] {
​                ​return​ ​false
​            }
​        }
​
​        ​// all items match, so return true
​        ​return​ ​true
​
​}


这个函数带了两个参数分别是someContainer和anotherContainer。someContainer参数是C1类型的,anotherContainer参数是C2类型的。C1和C2都是函数调用时要比较的两个容器类型的占位类型参数。

函数的类型参数列表对于这两个类型参数有如下要求:

C1必须遵循Container协议(写作:C1:Container)

C2必须同样遵循Container协议(写作:C2:container)

C1的ItemType和C2的ItemType必须是一样的

C1的ItemType必须遵循Equatable协议(写作:C1.ItemType:Equatable)

第三条和第四条作为where从句的一部分被定义,写在where关键字之后,作为函数类型参数列表的一部分。

这些要求的意思:

someContainer是一个存放C1类型的容器

anotherCntainer是一个存放C2类型的容器

someContainer和anotherContainer容器存放相同类型的元素

someContainer 中的元素可以相互之间用不等于操作符(!=)进行比较。

第三和第四个要求的组合意思就是anotherContainer同样可以用不等于操作符(!=)来进行比较,因为她里面存储的类型和someContainer中的一样。

这些要求使得allItemsMatch 函数能够比较两个容器,尽管它们是不同的容器类型。

allItemsMatch 函数一开始就比较两个容器包含元素的数量。如果数量不等,这两个容器就不可能匹配,所以这种情况函数返回false。

在做了数量检查后,函数用童工for-in循环和半开区间操作符(half-open range operator)(..<)遍历所有在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​) {
​    ​println​(​"All items match."​)
​} ​else​ {
​    ​println​(​"Not all items match."​)
​}
​// prints "All items match."


上面的例子创建了一个Stack实例,用来存储String值,像其中压入三个字符串。另外通过字面初始化创建了一个包含三个与栈内字符串相同的字符串的Array实例。尽管栈和数组是不同的类型,但它们都遵循Container协议,还包含相同类型的值。因此可以用这两个容器做参数调用allItemsMatch函数。上面的例子中,allItemMatch函数正确的报告了这两个容器的内容是一样的这个情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编程语言