文章目录

Swift 泛型:<T> 类型参数

发布于 2026-04-17 01:23:37 · 浏览 13 次 · 评论 0 条

Swift 泛型:<T> 类型参数

编写代码时,经常遇到逻辑完全相同,只是数据类型不同的函数。例如,一个交换两个整数的函数,和一个交换两个字符串的函数,内部代码一模一样。为了避免复制粘贴代码,Swift 提供了泛型。通过使用 <T> 类型参数,可以编写出灵活且可复用的代码。


1. 理解类型占位符 <T>

<T> 是一个占位符,告诉编译器:“这里暂时不指定具体类型,等到调用这个函数时再决定”。T 只是一个惯例,你也可以用 ElementItemType 或其他名字,但通常使用大写字母 T(代表 Type)。

观察以下两段代码,它们执行相同的逻辑,但类型不同:

// 交换两个整数
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
}

合并这两个函数为一个泛型版本。


2. 编写第一个泛型函数

将具体的 IntString 替换为占位符 T。占位符必须放在函数名后面的尖括号 <T> 中。

定义函数如下:

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

在上述代码中:

  1. <T> 声明了一个名为 T 的类型参数。
  2. _ a: inout T 指定参数 a 的类型为 T
  3. _ b: inout T 指定参数 b 的类型也为 T
  4. Swift 推断ab 必须是相同的类型 T

调用该函数:

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在是 107, anotherInt 现在是 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在是 "world", anotherString 现在是 "hello"

3. 添加类型约束

有时候,泛型 T 不能代表“任意类型”,必须满足某些条件,比如必须遵守某个协议。这被称为类型约束。

假设我们要编写一个查找数组中最大值的函数。如果 T 是任意类型,编译器不知道如何比较两个 T 的大小(例如,两个 UIView 实例谁大?)。因此,必须约束 T 必须遵守 Comparable 协议。

修改函数定义,在冒号后添加协议名:

func findMax<T: Comparable>(_ array: [T]) -> T? {
    guard let first = array.first else { return nil }
    var maxVal = first

    for item in array {
        if item > maxVal { // 只有遵守 Comparable 协议才能使用 >
            maxVal = item
        }
    }
    return maxVal
}

上述代码中的 <T: Comparable> 意味着T 必须是遵守了 Comparable 协议的类型。

调用该函数:

let intNumbers = [1, 5, 2, 9, 3]
if let maxInt = findMax(intNumbers) {
    print("最大整数是: \(maxInt)")
}

let doubleNumbers = [1.5, 3.2, 2.8]
if let maxDouble = findMax(doubleNumbers) {
    print("最大浮点数是: \(maxDouble)")
}

4. 理解类型约束检查流程

当调用带有类型约束的泛型函数时,Swift 编译器会执行一系列检查。以下流程图描述了 <T: Comparable> 约束下的检查逻辑:

graph TD A["调用函数 findMax(array)"] --> B["推断数组元素类型为 TypeX"] B --> C{"TypeX 是否遵守\nComparable 协议?"} C -- 是 --> D["函数执行成功\nT 被替换为 TypeX"] C -- 否 --> E["编译报错:\nTypeX 不符合约束条件"]

5. 关联多个类型参数

如果函数涉及两种不同的类型,可以使用多个类型参数,用逗号分隔。

例如,创建一个函数,将字典转换为键和值的元组数组。键和值可能是不同的类型。

定义双参数泛型函数:

func dictToTuples<Key, Value>(_ dict: [Key: Value]) -> [(Key, Value)] {
    return dict.map { ($0.key, $0.value) }
}

这里 <Key, Value> 声明了两个占位符。

调用该函数:

let scores = ["Alice": 99, "Bob": 85]
let scoreTuples = dictToTuples(scores)
// 类型为 [(String, Int)]

let heights = ["Tower": 300.5, "House": 10.0]
let heightTuples = dictToTuples(heights)
// 类型为 [(String, Double)]

6. 在类型定义(类/结构体)中使用 <T>

泛型不仅限于函数,也可以用于类(class)和结构体(struct)。Swift 标准库中的 ArrayDictionary 就是泛型集合。

创建一个泛型栈结构:

struct Stack<Element> {
    var items: [Element] = []

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element? {
        return items.popLast()
    }
}

使用该泛型结构体:

var stringStack = Stack<String>()
stringStack.push("第一层")
stringStack.push("第二层")
let poppedString = stringStack.pop() // "第二层"

var intStack = Stack<Int>()
intStack.push(100)
intStack.push(200)
let poppedInt = intStack.pop() // 200

Stack<String>() 中,Element替换String。在 Stack<Int>() 中,Element替换Int


7. 使用 where 子句进行更精确的约束

当类型参数之间的关系比较复杂时,可以使用 where 子句在函数签名末尾添加额外要求。

例如,要求两个数组的元素类型必须相同,并且该类型必须遵守 Equatable 协议,以便比较它们是否包含完全相同的元素。

定义where 的函数:

func checkArraysEqual<T: Equatable>(_ a: [T], _ b: [T]) -> Bool {
    return a == b
}

// 使用 where 子句的等价写法(展示 where 的用法)
func checkAllItemsMatch<T>(_ a: [T], _ b: [T]) -> Bool where T: Equatable {
    return a == b
}

调用该函数:

let listA = [1, 2, 3]
let listB = [1, 2, 3]

if checkArraysEqual(listA, listB) {
    print("两个数组内容完全一致")
}

where T: Equatable 强制规定T 不仅要存在,还必须能进行比较。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文