24.5. 泛型类型

除了泛型函数,Swift 还允许自定义泛型类型。这些自定义类、结构体和枚举可以适用于任意类型,类似于 ArrayDictionary

本节将向你展示如何编写一个名为 Stack(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。

注意

栈的概念已被 UINavigationController 类用来构造视图控制器的导航结构。你通过调用 UINavigationControllerpushViewController(_:animated:) 方法来添加新的视图控制器到导航栈,通过 popViewControllerAnimated(_:) 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。

下图展示了入栈(push)和出栈(pop)的行为:

  1. 现在有三个值在栈中。
  2. 第四个值被压入到栈的顶部。
  3. 现在栈中有四个值,最近入栈的那个值在顶部。
  4. 栈中最顶部的那个值被移除出栈。
  5. 一个值移除出栈后,现在栈又只有三个值了。

下面展示如何编写一个非泛型版本的栈,以 Int 型的栈为例:

struct IntStack {
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

这个结构体在栈中使用一个名为 items 的数组属性来存储值。栈提供了两个方法:push(_:)pop(),用来向栈中压入值以及从栈中移除值。这些方法被标记为 mutating,因为它们需要修改结构体的 items 数组。

上面的 IntStack 结构体只能用于 Int 类型。不过,可以定义一个泛型 Stack 结构体,从而能够处理任意类型的值。

下面是相同代码的泛型版本:

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

注意,Stack 基本上和 IntStack 相同,只是用占位类型参数 Element 代替了实际的 Int 类型。这个类型参数包裹在紧随结构体名的一对尖括号里()。

Element 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 Element 来引用。在这个例子中,Element 在如下三个地方被用作占位符:

  • 创建 items 属性,使用 Element 类型的空数组对其进行初始化。
  • 指定 push(_:) 方法的唯一参数 item 的类型必须是 Element 类型。
  • 指定 pop() 方法的返回值类型必须是 Element 类型。

由于 Stack 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 ArrayDictionary 那样。

你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 Stack 实例。例如,要创建一个 String 类型的栈,可以写成 Stack()

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串

下图展示了 stackOfStrings 如何将这四个值压栈:

移除并返回栈顶部的值“cuatro”,即出栈:

let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为“cuatro”,现在栈中还有 3 个字符串

下图展示了如何将顶部的值出栈: