SwiftNote-泛型

360 阅读6分钟

1.定义泛型类型

除了定义泛型函数,Swift 允许自定义泛型类型。它们是可以用于任意类型的自定义类、结构体、枚举和 Array、 Dictionary 方式类似。

struct Stack<Element> {
    // 定义一个数组
    var items = [Element]()
    
    // 入栈
    mutating func push(_ item:Element) {
        items.append(item)
    }
    
    // 出栈
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

var strStack = Stack<String>()

strStack.push("James")
strStack.push("Rose")
strStack.push("Harden")
strStack.push("Durant")

print(strStack)
// Stack<String>(items: ["James", "Rose", "Harden", "Durant"])
strStack.pop() // 移除了 Durant
print(strStack.topItem ?? "空集合")
// Harden

在扩展一个泛型类型时,不需要在扩展的定义中提供类型形式参数列表

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items.last
    }
}

2.泛型中的类型约束

为什么需要类型约束? 泛型函数和泛型类型可以用于任意类型。但是有时在用于泛型函数的类型和泛型类型上,强制其遵循特定的类型约束很有用。类型约束指出一个类型形式参数必须继承自特定类,或者遵循一个特定的协议或者多个协议。 例如在 Dictionary 中就对其 key 这个泛型做了类型约束,要求其必须是可哈希的,也就是说它必须提供一种使其可以唯一表示的方法。如果这个 key 不是可哈希的(不唯一确定的),那么 Dictionary 就不能区分该插入还是替换这个 key 对应的值,也不能在字典中查找已经给定的键的值。

类型约束的语法: 在一个类型形式参数名称后面放置一个类或者协议作为形式参数列表的一部分,并用冒号隔开,以写出一个类型约束。

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

示例如下:

func firstIndex<T: Equatable>(of valueToFind:T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

3.关联类型

在 Swift 中为协议使用泛型就必须使用关联类型。这点和结构体、枚举以及类使用尖括号声明泛型类型不同。 定义一个协议时,有时在协议定义里声明一个或多个关联类型是很有用的。关联类型给协议中用到的类型一个占位符名称。直到采纳协议时才指定用于该关联类型的实际类型。关联类型通过 associatedtype 关键字指定。

protocol Container {
    // 声明关联类型
    associatedtype ItemType
    
    // 声明一个 append 的函数
    mutating func append(item: ItemType)
    
    // 声明一个只读属性 count
    var count: Int {get}
    
    // 声明下标方法, 返回一个 ItemType 类型的值或者对象
    subscript(i: Int) -> ItemType {get}
}

struct IntStack: Container {
    
    var items = [Int]()
    
    // 实现 Container 协议中的一些属性, 方法等等
    // 指明关联类型为 Int,其实删除这行代码也是可以的, 得益于 Swift 的类型推断, 我们可以指定关联类型就是 Int
    typealias ItemType = Int
    
    // 实现 Container 协议中的 append 方法
    mutating func append(item: Int) {
        items.append(item)
    }
    
    // 实现 Container 协议中的 count 只读属性
    var count: Int {
        return items.count
    }
    
    // 实现 Container 协议中的 subscript 方法
    subscript(i: Int) -> Int {
        return items[i]
    }
}

var intStack = IntStack()
intStack.append(item: 111)
intStack.append(item: 666)
intStack.append(item: 888)
    
print(intStack.count) // 3
print(intStack[1])    // 666

关联类型的约束 可以在协议里给关联类型添加约束来要求遵循的类型满足约束。

protocol Container {
// 要求关联类型 遵循 Equatable 协议
associatedtype Item: Equatable
    mutating func append(item: ItemType)
    var count: Int {get}
    subscript(i: Int) -> Item {get}
}

4.为泛型定义要求:where 字句

如类型约束中描述的一样,类型约束允许你在泛型函数或泛型类型相关的类型形式参数上定义要求。 类型约束在为关联类型定义要求时也很有用。通过定义一个泛型 where 字句来实现。泛型 where 字句让你能够要求一个关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。泛型 where 字句以 where 关键字开头,后接关联类型的约束或类型和关联类型一致的关系。泛型 where 字句写在一个类型或函数体的左半个大括号前面。

func allItemsMatch<C1: Container, C2: Container>(container: C1, anotherContainer:C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
    
    if container.count != anotherContainer.count {
        return false
    }
    
    for i in 0..<container.count {
        if container[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}

var intStack = IntStack()
intStack.append(item: 111)
intStack.append(item: 666)
intStack.append(item: 888)
    
var anotherStack = IntStack()
anotherStack.append(item: 111)
anotherStack.append(item: 666)
anotherStack.append(item: 888)
let isSame = allItemsMatch(container: intStack, anotherContainer: anotherStack)
print(isSame ? "相同" : "不匹配") // 相同 

带有泛型 where 字句的扩展 你同时也可以使用泛型的 where 字句来作为扩展的一部分。

// 泛型 where 字句扩展结构体 Stack
// 限制 Stack 的元素遵循 Equatable 协议
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

// 泛型 where 字句扩展协议 Container
// 限制 Container 的关联类型遵循 Equatable 协议
extension Container where ItemType: Equatable {
    func startWith(_ item: ItemType) -> Bool {
        return count >= 1 && self[0] == item
    }
}

// 泛型 where 字句扩展协议 Container
// 限制 Container 的关联类型为 Double 类型
extension Container where ItemType == Double {
    func average() -> Double {
        if count > 0 {
            var sum = 0.0
            for index in 0..<count {
                sum += self[index]
            }
            return sum / Double(count)
        } else {
            return 0.0
        }
    }
}

5.泛型下标

下标可以是泛型的,它们可以包含泛型 where 字句。你可以在 subscript 后用尖括号来写类型占位符,你还可以在下标代码花括号前写泛型的 where 字句。

// 扩展 Container 协议, 新增下标方法,这个下标参数是一个泛型类型并且这个泛型类型遵循 Sequence 协议, 另外要求这个序列的类型是 Int(通过 where 字句做的限制)
extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [ItemType] where Indices.Iterator.Element == Int {
        var result = [ItemType]()
        for index in indices {
            result.append(self[index])
        }
        return result
    }
}

var intStack = IntStack()
intStack.append(item: 111)
intStack.append(item: 666)
intStack.append(item: 888)
print(intStack[[1,2]]) // [666, 888]

6.泛型编程思想

泛型编程--编写以类型作为参数的一个模板函数,在调用时再将参数实例化为具体的数据类型。

与针对问题和数据的面向对象的方法不同,泛型编程中强调的是算法。是一类通用的参数化算法,他们对各种数据类型和各种数据结构都能以相同的方式进行工作,从而实现源代码级的软件重用。

例如:不管(容器)是数组、队列、链表还是堆栈,不管里面的元素(类型)是字符串、整数、浮点数还是对象,都可以使用通用的(迭代器)方法来遍历容器内的所有元素、获取指定元素的值、添加或删除元素,从而实现排序、检索、复制、合并等这种操作和算法。

泛型编程是一种面向算法的多态技术。