swift - # 泛型 和 关联类型
泛型的作用
swapTwoInts(::)、swapTwoStrings(::) 和 swapTwoDoubles(::) 函数体是一样的,唯一的区别是它们接受的参数类型(Int、String 和 Double)。 在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
注意 在上面三个函数中,a 和 b 类型必须相同。如果 a 和 b 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 String 类型的变量和一个 Double 类型的变量互换值。试图这样做将导致编译错误。
泛型函数
泛型函数可适用于任意类型。
1.泛型版本的函数使用占位符类型名(这里叫做 T ) 占位符类型名并不关心 T 具体的类型,但它要求 a 和b 必须是相同的类型
2.泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(swapTwoValues(::))后面跟着占位类型名(T)
例如:下面是函数 swapTwoInts(::) 的泛型版本,命名为 swapTwoValues(::):
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
注意 上面定义的 swapTwoValues(::) 函数是受 swap(::) 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 swapTwoValues(::) 函数的功能,你可以使用已存在的 swap(::) 函数。
泛型类型
除了泛型函数,Swift 还允许自定义泛型类型。这些自定义类、结构体和枚举可以适用于任意类型,类似于 Array 和 Dictionary 例如:
struct IntStack {
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
上面的 IntStack 结构体只能用于 Int 类型。不过,可以定义一个泛型 Stack 结构体,从而能够处理任意类型的值。
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
泛型扩展
当对泛型类型进行扩展时,不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用 例
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
类型约束
类型约束指定 类型参数必须继承自指定类、遵循特定的协议或协议组合。
定义: 在一个类型参数名后面放置一个 类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
上面这个函数有两个类型参数。第一个类型参数 T 必须是 SomeClass 子类;第二个类型参数 U 必须符合 SomeProtocol 协议。
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {/// 必须遵循Equatable 才能用==
return index
}
}
return nil
}
关联类型
定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的 实际类型 在协议被遵循时才会被指定。关联类型通过 associatedtype 关键字来指定。 例如:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
这是前面非泛型版本 IntStack 类型,使其遵循 Container 协议:
struct IntStack: Container {
// IntStack 的原始实现部分
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 协议的实现部分
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
你也可以让泛型 Stack 结构体遵循 Container 协议:
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
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 的类型。
给关联类型添加约束
Container 协议, 要求关联类型 Item 必须遵循 Equatable 协议:
protocol Container {
associatedtype Item: Equatable ///关联类型遵循协议
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}