Swift ——泛型与集合

本文详细介绍了Swift中的泛型,包括为何需要泛型、泛型语法、类型擦除原理,以及Sequence与Collection的实现细节。通过实例展示了泛型在环形数组、MutableCollection等集合类型中的应用,帮助读者深入理解Swift泛型和集合类型的使用与设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 为什么会有泛型

下面的 multiNumInt 是个非泛型函数,主要用于计算两个数的乘积

func multiNumInt(_ x: Int, _ y: Int) -> Int{
 return x * y
}

multiNum 函数很实用,但是它只能用于 Int 值。如果我们想计算两个 Double 或者 其他类型的乘积的值,我们需要在写一些函数,比如 multiNumDouble函数:

func multiNumDouble(_ x: Double, _ y: Double) -> Double{ 
return x * y
}

但是我们发现, 函数体是一样的。唯一的区别是它 们接收值类型不同( Int和double)。

这个时候我们想找到一个可以计算任意类型值的函数怎么办?泛型正是能让我们写出这样函数的语法。

2. 泛型语法注意要点

我们先来看一下泛型的基本写法 ,首先我我们要指定一个占位符 T ,紧挨着写在函数名后面的 一对尖括号(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须);其次我们就可以 使用 T 来替换任意定义的函数形式参数

func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T{
 return x * y
}

这是一个标准的非泛型版的数据结构,如果我们想要改造一下怎么做?

struct Stack{
    private var items = [Int]()
    mutating func push(_ item:Int) {
        items.append(item)
    }
    
    mutating func pop(_ item:Int) -> Int? {
        if items.isEmpty {return nil}
        return items.removeLast()
    }
}

改写成泛型后成为这样的结构体

struct Stack<T>{
    private var items = [T]()
    mutating func push(_ item:T) {
        items.append(item)
    }
    
    mutating func pop(_ item:T) -> T? {
        if items.isEmpty {return nil}
        return items.removeLast()
    }
}

基本上和栈的数据结构相关都有相同的操作,这个时候我们免不了抽取相同的行为定一个协议,这里我们定义协议的时候需要指明当前类型,那么能不能给 Protocol 也上一个泛 型?

protocol StackProtocol {
    var itemCount: Int{ get }
    mutating func pop() -> Int?
    func index(of index: Int) -> Int
}

这样写就会出现一个错误,系统提示 Protocol 不支持泛型参数,需要我们使用关联类型来代替。
在这里插入图片描述
这样就可以了
在这里插入图片描述
那么在实现的时候,就可以让遵循结构的来指明这个关联类型的类型。


struct LGStack: StackProtocol{
    typealias T = Int
    private var items = [T]()
    
    var itemCount: Int{ get{
        return items.count }
    }
    
    mutating func push(_ item: T){
        items.append(item)
    }
    
    mutating func pop() -> T?{
        if items.isEmpty { return nil }
        return items.removeLast()
    }
    
    func index(of index: Int) -> T {
        return items[index]
    }
}

同样的我们也可以给当前的关联类型添加约束,比如我们要求 T 必须都要遵循 FixWidthInteger,那么传入的T必须是遵循协议的类型,否则就会报错

在这里插入图片描述

我们也可以直接在约束中使用协议,在这个协议里, Even 是一个关联类型,就像上边例子中 StackProtocol 的 Item 类型一样。 Even 拥有两个约束:它必须遵循 EvenProtocol 协议(就是当前定义的协议),以及它的 T 类型必须是和容器里的 T 类型相同。 T 的约束是一个 where 分句。

protocol EvenProtocol: StackProtocol{
    associatedtype Even: EvenProtocol where Even.Item == Item
    func pushEven(_ item: Int) -> Even
}

在上面的例子中我们出现了一个 where 的协议,或者指定的类型形式参数和关联类型必须相同。泛型 Where 分句以 Where 关键字开 头,后接关联类型的约束或类型和关联类型一致的关系。

这个函数有两个形式参数,stack1 和 stack2。stack1 形式参数是 T1 类型,stack2 形式参 数是 T2 类型。T1 和 T2 是两个容器类型的类型形式参数,它们的类型在调用函数时决定。那么这个时候,传进来的泛型就需要满足三个条件

  • 遵循StackProtocol协议
  • T1 和T2类型相同
  • T1的数据遵循Equatable
func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1, _ stack2:T2) -> Bool where T1.T == T2.T,T1.T:Equatable{
    guard stack1.itemCount == stack2.itemCount else {
        return false
    }
    for i in 0 ..< stack1.itemCount {
    
        if stack1.index(of: Int(i)) != stack2.index(of: Int(i)) {
            return false
        }
    }
    return true
}

3. 类型擦除

前面我们已经了解了什么是协议,什么是泛型。接下来我们了解一下什么是类型擦除。首先我们 先来看一个例子(这里我们定义了一个泛型协议)

protocol DataFetch {
    associatedtype dataType
    func fetch(completion: ((Result<DataType, Error>) -> Void)?)
    
}

紧接着我们需要请求一个用户数据

struct UserData: DataFetch {
    typealias dataType = User
    func fetch(completion: ((Result<dataType, Error>) -> Void)?)  {
        let user = User(userId: 1001, name: "Kody")
        completion?(.success(user))
    }
}

这时候我们需要使用我们当前的 UserData 获取我们当前的 User 数据,运行一下,你会发现一个报错信息.也就意味着 DataFetch 只能用作泛型约束,不能用作具体类型!Swift是类型安全的,编译器无法确定dataType 的具体类型是什么,那么编译器就会报错

class ViewController {
    let userdata:DataFetch
    init(){
        userdata.fetch() { (result) in
            
        }
    }
  
}
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值