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

泛型

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

泛型是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​],​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函数正确的报告了这两个容器的内容是一样的这个情况。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。