别再只会用类了!Swift 递归枚举,解决树形结构的另一种思路

0 阅读2分钟
indirect enum Tree<T> {
    case leaf(T)
    case node(Tree, Tree)
}

变量.leaf(x): Tree<T> 的内存大致如下
+--------+
|  ptr | ------> +---------------------+
+--------+          | tag: leaf           |
                    | payload: T = x      |
                    +---------------------+

变量 .node(L, R): Tree<T>的内存大致如下
+--------+
|  ptr   | ------>  +---------------------+
+--------+          | tag: node           |
                    | left:  Tree (ptr)   |
                    | right: Tree (ptr)   |
                    +---------------------+
                         | 
                         v 
                    (子树又是 ptr→box …)
//T 再大,你手里的 Tree 外壳大小也基本不变(仍是“一个指针量级”)
//不论leaf还是node都是一个指针,指向堆中真正的tag和payload一体的struct
enum Tree<T> {
    case leaf(T)
    indirect case node(Tree, Tree)
}

变量 .leaf(x): Tree<T>的内存大致如下
+----------------------+
| tag: leaf            |
| payload: T = x       |   <-- 通常都在这一坨里,无额外“整棵树的 box”
+----------------------+
变量 .node(L, R): Tree<T>的内存大致如下
+----------------------+
| tag: node            |
| ptr ────────────────────────|
+----------------------+      |
                              v
                    +----------------------+
                    | 子树 L: Tree<T>      |  (可能是内联 leaf,也可能是 node 的 ptr...)
                    | 子树 R: Tree<T>      |
                    +----------------------+

在 Swift 中,indirect 关键字用于枚举,允许枚举的 case 拥有同类型的关联值,从而支持递归枚举。

在没有 indirect 的情况下,枚举的大小需要在编译时确定,而递归枚举会使得编译器无法计算其大小,因为它在理论上可以无限嵌套。或者这么说,编译器需要知道每个类型的确切大小,但递归定义让这个计算无法终止。

使用 indirect 后,编译器会通过引入一层间接层(通常是指针)来存储关联值,这样枚举的大小就固定了(因为指针的大小是固定的),从而允许递归。

具体来说,indirect 可以用于整个枚举,也可以只用于特定的 case。

例如,上面的 Tree 枚举定义了一个二叉树结构:

  • leaf 表示叶子节点,存储一个值。

  • node 表示内部节点,有两个子树(左子树和右子树)。

由于 node 的关联值类型是 Tree 本身,所以这是一个递归枚举。

使用 indirect 后,编译器会为这些递归的 case 使用引用(指针)来存储,这样枚举值的大小就是指针的大小,而不是无限大。

另外,你也可以将 indirect 关键字放在整个枚举前面,这样枚举的所有 case 都是间接的

使用indirect并不会节省空间,反而会增加一些开销(因为需要额外的堆分配和指针)。实际上,使用indirect意味着我们将数据存储到堆上,并通过指针来引用它。这样,枚举值的大小就变成了一个指针的大小(64位机器上为8字节),而不管实际数据有多大

关注公众号iOS开发小挖,查看更多