Swift 泛型:<T> 类型参数
编写代码时,经常遇到逻辑完全相同,只是数据类型不同的函数。例如,一个交换两个整数的函数,和一个交换两个字符串的函数,内部代码一模一样。为了避免复制粘贴代码,Swift 提供了泛型。通过使用 <T> 类型参数,可以编写出灵活且可复用的代码。
1. 理解类型占位符 <T>
<T> 是一个占位符,告诉编译器:“这里暂时不指定具体类型,等到调用这个函数时再决定”。T 只是一个惯例,你也可以用 Element、ItemType 或其他名字,但通常使用大写字母 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. 编写第一个泛型函数
将具体的 Int 或 String 替换为占位符 T。占位符必须放在函数名后面的尖括号 <T> 中。
定义函数如下:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
在上述代码中:
<T>声明了一个名为T的类型参数。_ a: inout T指定参数a的类型为T。_ b: inout T指定参数b的类型也为T。- Swift 推断出
a和b必须是相同的类型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> 约束下的检查逻辑:
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 标准库中的 Array 和 Dictionary 就是泛型集合。
创建一个泛型栈结构:
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 不仅要存在,还必须能进行比较。

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