首页 / Swift 编程语言教程 / 枚举基础

Swift 编程语言教程

枚举基础

本教程共 93 篇 · 第 47 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程枚举enumrawValueCaseIterableswitch

本节目标:读完后你能用 enum 把一组相关的值归类成类型,理解 Swift 枚举成员默认不带整数编号,并会用原始值(rawValue)和 CaseIterable 来生成与遍历枚举。

生活中有很多”非此即彼、有限几种”的场景:方向只有东南西北,星期只有周一到周日,支付方式只有微信、支付宝、银行卡。如果你用整数 0、1、2 去表示这些状态,编译器并不知道 0 代表什么,写错了也不会报警。等到程序跑起来、界面显示出一个莫名其妙的数字,你才意识到状态传错了。枚举就是为解决这类问题而生的:它把”有限几种可能”变成编译器认得的类型。

1-1 枚举到底解决什么问题

枚举(enumerations,简称 enum)让你把一组相关、但互斥的可能值,归到一个自定义类型之下。这样做最大的好处是类型安全:一旦某个变量被声明为这个枚举类型,它就只能取这几样值里的一个,传错值编译器直接拦下。

举个反例:如果你用 Int 表示方向,写 var dir = 9 编译器毫无意见,可 9 根本不是合法方向。换成枚举,写 var dir = CompassPoint.west 之后,任何不是那四个成员的值都塞不进去。这种”把非法状态在编译期就挡掉”的思路,是 Swift 设计哲学的核心,也是枚举最大的价值。

Tip

给枚举起名用单数而不是复数,比如 CompassPoint 而不是 CompassPoints。这样读起来像”一个点”,更自然,也和 Swift 其他类型(String、Int)的命名习惯一致。

和 C 语言不同,Swift 的枚举非常灵活:它的每个成员本身就是一个完整的值,不必依附整数。也正因如此,枚举在 Swift 里是”一等公民”,它可以有自己的计算属性、方法、初始化器,还能遵循协议。这些进阶能力我们后面几章慢慢展开。

1-2 用 enum 定义成员

定义枚举用 enum 关键字,成员用 case 引导。看一个表示方位的例子:

enum CompassPoint {
    case north
    case south
    case east
    case west
}

这里的 north、south、east、west 叫做枚举的”成员”(case)。每个成员都是一个独立的值。多个成员也能写在一行,用逗号隔开,这在成员较多时能让代码更紧凑:

enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
Warning

初学者最容易踩的坑:以为 Swift 枚举成员默认等于 0、1、2、3,像 C 那样。其实不是。CompassPoint.north 并不等于 0,它就是一个类型为 CompassPoint 的值。别拿整数去比较它,也别指望它能当整数参与运算。

类型名要用大驼峰(首字母大写),这是 Swift 对所有自定义类型的约定,和标准库里的 String、Int 一致。成员名一般用小驼峰,这只是社区习惯,编译器不强制,但统一风格能让团队读起来不累。

1-3 用点语法读写枚举值

定义一个枚举类型后,就可以声明变量或常量:

var directionToHead = CompassPoint.west

因为赋值时类型已经明确,后面再改值可以省略类型名,直接用点语法:

directionToHead = .east

这种写法可读性很高:编译器已经知道 directionToHead 是 CompassPoint,你写 .east 它就明白指的是 CompassPoint.east。当枚举类型在上下文里已经清楚时,这种”只写点加成员名”的简写能省掉大量重复,是 Swift 代码里极常见的写法。

1-4 用 switch 匹配枚举

枚举和 switch 是绝配。Swift 要求 switch 必须”穷尽”所有成员,少写一个就不让编译,从根上杜绝漏判:

directionToHead = .south
switch directionToHead {
case .north:
    print("北极附近星球很多")
case .south:
    print("小心企鹅")
case .east:
    print("太阳升起的方向")
case .west:
    print("天空很蓝的地方")
}

如果某些情况你不想逐一写,可以加一个 default 分支兜底:

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("基本无害")
default:
    print("对人类不太友好")
}

强制穷尽是 Swift 枚举的一大安全特性。当你以后给枚举加了一个新成员,所有没处理它的 switch 都会立刻报错,逼你考虑新情况。很多大型项目靠这条规则,在扩展状态机时不会漏掉分支。

1-5 原始值 rawValue

有些时候,你确实想给每个成员绑一个固定的默认值,比如字符、字符串、整数。这种默认值叫做”原始值”(raw value)。原始值类型必须统一,可以是 String、Character、Int 或 Float,且每个成员的值在枚举内必须唯一。

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

这三类原始值都是字符类型,分别绑了常见的 ASCII 控制符。原始值适合做”和外部系统对接”的映射:比如网络接口返回的字符串 “north”,你需要转成枚举再处理,原始值就派上用场。

如果不想逐个写,Swift 能自动帮你填。整数从 0 起逐个加 1;字符串则默认用成员名当值:

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
enum CompassPoint: String {
    case north, south, east, west
}

上面 Planet.mercury 原始值是 1,venus 是 2;CompassPoint.south 原始值就是字符串 “south”。用 rawValue 属性读出来:

let earthsOrder = Planet.earth.rawValue   // 3
let sunsetDirection = CompassPoint.west.rawValue  // "west"

还能反过来,用原始值构造枚举。注意这个构造器是”可失败的”:未必每个原始值都有对应成员,所以它返回可选类型:

let possiblePlanet = Planet(rawValue: 7)  // Planet?,等于 .uranus
if let somePlanet = Planet(rawValue: 11) {
    print("找到了")
} else {
    print("位置 11 没有对应行星")
}
Note

原始值和后面要学的”关联值”是两码事。原始值在定义时就固定不变;关联值则是每次创建变量时才带的额外数据,下次还能不一样。这一点第 48 章会讲清楚。两者不能混用:一个枚举要么用原始值,要么用关联值,二者选其一。

1-6 CaseIterable 与遍历

有时你想拿到”这个枚举一共有哪些成员”的清单,比如做个下拉选择、或做单元测试覆盖所有可能。只要在枚举名后面写上 : CaseIterable,Swift 就自动提供一个 allCases 集合:

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
print("有 \(Beverage.allCases.count) 种饮料")  // 3 种饮料
for beverage in Beverage.allCases {
    print(beverage)
}

这背后是让枚举遵循了 CaseIterable 协议。对于会动态变化的清单,这种写法比手写数组省心得多——你新增成员时,遍历会自动包含它,不用改别处。

1-7 枚举与常量的取舍

看到这里你可能会问:用一组 let 常量(如 let north = "north")不也能表示方向吗?差别在类型。常量只是字符串,彼此之间没有任何约束;枚举把这些值收进一个类型,编译器能替你检查”传的是不是合法成员”。当可能值是有限的、封闭的,枚举几乎总是比裸常量更优。

1-8 小结

枚举是把有限可能值归成类型的安全工具。定义用 enum 和 case,成员默认不是整数。点语法让赋值简洁,switch 强制穷尽避免漏判。原始值(rawValue)给成员绑固定默认值,并能双向转换。CaseIterable 让你轻松遍历全部成员,而比起裸常量,枚举的封闭性带来编译期保障。下一章我们给枚举成员”挂”上额外数据,进入关联值枚举。