Swift: Swift 泛型教程:从零掌握泛型函数与类型约束

泛型让你编写适应任意类型的灵活代码,避免为每种类型重复实现相同逻辑——像"万能模具"一样,倒入什么材料就产出什么形状。

1. 你将学到


2. 一个后端工程师的真实故事

(1) 痛点:为每种类型写相同逻辑

Charlie 在开发一个缓存服务时,需要实现一个"先进后出"的栈数据结构。他先为 Int 写了一个版本:

SWIFT
struct IntStack {
    private var items: [Int] = []
    mutating func push(_ item: Int) { items.append(item) }
    mutating func pop() -> Int? { items.popLast() }
}

但很快他需要支持 String、Double,甚至自定义 User 类型。Charlie 发现自己在不断复制粘贴——只是换了类型名,逻辑完全一样

(2) 泛型的解法

泛型把类型变成参数,一份代码适配所有类型:

SWIFT
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ item: Element) { items.append(item) }
    mutating func pop() -> Element? { items.popLast() }
}

现在 Stack<Int>Stack<String>Stack<User> 都用同一份实现,编译器保证类型安全。

(3) 收益:代码量减少 60%

维度 非泛型方案 泛型方案
代码行数 120 行(3 种类型) 20 行(1 个泛型)
新增类型成本 40 行复制粘贴 1 行 Stack<NewType>
类型安全 ✅ 每种类型独立 ✅ 编译时检查
维护成本 ❌ 改逻辑需改 3 处 ✅ 只改 1 处

3. 泛型函数 (Generic Functions)

泛型函数允许在函数定义中使用占位符类型,调用时才确定具体类型。

100%
graph LR
    A["func swap<T>(a: inout T, b: inout T)"] --> B["Called with Int"]
    A --> C["Called with String"]
    A --> D["Called with Double"]
    B --> E["T = Int: safe swap"]
    C --> F["T = String: safe swap"]
    D --> G["T = Double: safe swap"]

(1) 泛型参数语法

泛型参数写在函数名后的尖括号 <> 中,通常用大写字母 TUV 占位。

写法 含义 示例
<T> 单个泛型参数 func identity<T>(_ value: T) -> T
<T, U> 两个泛型参数 func pair<T, U>(_ a: T, _ b: U) -> (T, U)
<T: Equatable> 有约束的泛型 func isEqual<T: Equatable>(_ a: T, _ b: T) -> Bool

▶ 示例:交换两个变量的值

SWIFT
// ============================================
// 泛型函数:交换任意类型的两个值
// ============================================
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
print("x = \(x), y = \(y)")
var firstName = "Alice"
var lastName = "Bob"
swapValues(&firstName, &lastName)
print("firstName = \(firstName), lastName = \(lastName)")

输出:

TEXT 📖 仅展示
x = 20, y = 10
firstName = Bob, lastName = Alice

(2) 多泛型参数

函数可以有多个泛型参数,分别代表不同位置的不同类型。

▶ 示例:构建键值对

SWIFT
// ============================================
// 多个泛型参数构建键值对
// ============================================
func makePair<K, V>(_ key: K, _ value: V) -> (K, V) {
    return (key, value)
}
let pair1 = makePair("id", 1001)
let pair2 = makePair(3.14, "Pi")
print(pair1)
print(pair2)

输出:

TEXT 📖 仅展示
(id, 1001)
(3.14, Pi)

4. 泛型类型 (Generic Types)

泛型类型允许你用占位符定义结构体、类或枚举,使其能处理任意类型的数据。

100%
graph TB
    A["Stack<Element>"] --> B["push(Element)"]
    A --> C["pop() -> Element?"]
    A --> D["peek() -> Element?"]
    A --> E["count: Int"]
    B --> F["append to items"]
    C --> G["remove last from items"]
    D --> H["return items.last"]

(1) 泛型结构体

SWIFT
struct Stack<Element> { ... }
使用场景 写法 说明
Int 栈 Stack<Int> 只能 push/pop Int 值
String 栈 Stack<String> 只能 push/pop String 值
自定义类型栈 Stack<User> 只能 push/pop User 值

▶ 示例:泛型栈数据结构

SWIFT
// ============================================
// 泛型栈:支持任意类型的先进后出
// ============================================
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element? {
        items.popLast()
    }
    func peek() -> Element? {
        items.last
    }
    var count: Int { items.count }
}
var intStack = Stack<Int>()
intStack.push(10)
intStack.push(20)
intStack.push(30)
print("Pop: \(intStack.pop() ?? 0)")
var stringStack = Stack<String>()
stringStack.push("Alice")
stringStack.push("Bob")
print("Peek: \(stringStack.peek() ?? "")")

输出:

TEXT 📖 仅展示
Pop: 30
Peek: Bob

(2) 泛型扩展

扩展泛型类型时,不需要重复声明类型参数——直接用占位符名即可。

▶ 示例:为泛型栈添加 map 方法

SWIFT
// ============================================
// 通过扩展为 Stack 添加 map 功能
// ============================================
extension Stack {
    func map<T>(_ transform: (Element) -> T) -> Stack<T> {
        var result = Stack<T>()
        for item in items {
            result.push(transform(item))
        }
        return result
    }
}
var s = Stack<Int>()
s.push(1); s.push(2); s.push(3)
let doubled = s.map { $0 * 2 }
print("Count: \(doubled.count)")

输出:

TEXT 📖 仅展示
Count: 3

5. 类型约束与关联类型

类型约束限制泛型参数必须满足特定条件。关联类型是协议中的"泛型占位符",由实现者指定具体类型。

100%
graph TB
    A["<T: Equatable>"] --> B["Can use =="]
    A --> C["func findIndex<T: Equatable>(of: T, in: [T]) -> Int?"]
    B --> D["Int =="]
    B --> E["String =="]
    B --> F["Custom with Equatable"]

(1) 类型约束

约束写法 含义 使用场景
<T: Equatable> T 必须可比较相等 查找元素、去重
<T: Hashable> T 必须可哈希 作为 Dictionary 的 key
<T: Comparable> T 必须可比较大小 排序、求最大最小值
<T: Codable> T 必须可编码解码 JSON 序列化

▶ 示例:使用 Equatable 约束查找元素

SWIFT
// ============================================
// 约束泛型参数必须可比较相等
// ============================================
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (index, item) in array.enumerated() {
        if item == value {
            return index
        }
    }
    return nil
}
let numbers = [10, 20, 30, 40]
if let index = findIndex(of: 30, in: numbers) {
    print("Found at index \(index)")
}
let names = ["Alice", "Bob", "Charlie"]
if let index = findIndex(of: "Bob", in: names) {
    print("Found at index \(index)")
}

输出:

TEXT 📖 仅展示
Found at index 2
Found at index 1

(2) 关联类型与 where 子句

关联类型 associatedtype 让协议支持泛型——协议本身不指定具体类型,由实现者决定。

▶ 示例:带关联类型的协议与 where

SWIFT
// ============================================
// 关联类型协议 + where 子句
// ============================================
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
struct Box<T>: Container {
    typealias Item = T
    private var items: [T] = []
    mutating func append(_ item: T) { items.append(item) }
    var count: Int { items.count }
    subscript(i: Int) -> T { items[i] }
}
func allItemsMatch<C1: Container, C2: Container>(
    _ c1: C1, _ c2: C2
) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
    guard c1.count == c2.count else { return false }
    for i in 0..<c1.count {
        if c1[i] != c2[i] { return false }
    }
    return true
}
var box1 = Box<Int>()
box1.append(1); box1.append(2); box1.append(3)
var box2 = Box<Int>()
box2.append(1); box2.append(2); box2.append(3)
print("All match: \(allItemsMatch(box1, box2))")

输出:

TEXT 📖 仅展示
All match: true

6. 完整示例:泛型队列与缓存系统

SWIFT
// ============================================
// 完整示例:泛型队列 + 限长缓存
// 功能:通用队列、缓存封装、统计
// ============================================
import Foundation
// 1. 泛型队列
struct Queue<Element> {
    private var items: [Element] = []
    mutating func enqueue(_ item: Element) { items.append(item) }
    mutating func dequeue() -> Element? { items.isEmpty ? nil : items.removeFirst() }
    var count: Int { items.count }
}
// 2. 泛型缓存(带容量限制)
class Cache<Key: Hashable, Value> {
    private var storage: [Key: Value] = [:]
    private let capacity: Int
    init(capacity: Int = 100) {
        self.capacity = capacity
    }
    func set(_ value: Value, for key: Key) {
        if storage.count >= capacity {
            storage.removeFirst()
        }
        storage[key] = value
    }
    func get(for key: Key) -> Value? { storage[key] }
    var count: Int { storage.count }
}
// 3. 使用示例
var queue = Queue<String>()
queue.enqueue("Task 1")
queue.enqueue("Task 2")
queue.enqueue("Task 3")
print("Queue count: \(queue.count)")
print("Dequeue: \(queue.dequeue() ?? "")")
let cache = Cache<String, Int>(capacity: 3)
cache.set(42, for: "answer")
cache.set(100, for: "score")
print("Cached answer: \(cache.get(for: "answer") ?? 0)")
print("Cache count: \(cache.count)")

输出:

TEXT 📖 仅展示
Queue count: 3
Dequeue: Task 1
Cached answer: 42
Cache count: 2

❓ 常见问题

Q 泛型和 Any 有什么区别?
A 泛型保留类型信息,编译时类型安全;Any 在运行时检查类型。泛型是"编译器知道具体类型",Any 是"编译器放弃类型检查"。
Q 什么时候用泛型参数 vs 协议约束?
A 泛型参数适用于"类型可任意但操作一致"的场景;协议约束适用于"类型必须满足特定接口"的场景。两者常结合使用。
Q 关联类型和泛型参数有什么区别?
A 关联类型定义在协议内部,由实现者指定具体类型;泛型参数定义在函数/类型上,由调用者指定。关联类型相当于"协议的泛型参数"。
Q 泛型数组 Array 是怎么实现的?
A 标准库的 Array 就是一个泛型结构体 Array<Element>,Element 是泛型参数。所有数组操作(append、sort、map)都是泛型方法。
Q 泛型会影响性能吗?
A 不会。Swift 使用"泛型特化"(Specialization),编译器为每个具体类型生成专门版本的代码,运行时无泛型开销。

📖 小节


📝 作业

  1. 基础题: 写一个泛型函数 findMax<T: Comparable>,接受两个参数并返回较大的值。测试 Int、Double、String 三种类型。
  2. 进阶题: 扩展第 4 节的 Stack 类型,添加 filter 方法,接受 (Element) -> Bool 闭包,返回过滤后的新栈。
  3. 挑战题: 使用泛型实现一个 RingBuffer<Element> 环状缓冲区,支持 write(_:)read() -> Element? 方法,固定容量,写入满时自动覆盖最旧数据。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏