原档:
枚举定义了一个普通类型的一组相关值,使你可以在代码中以一种安全的方式来使用这些值。
如果你熟悉 C 语言,你就会知道,在 C 语言中枚举将枚举名和一个整型值相对应。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。
此外,枚举成员可以指定任何类型的相关值存储到枚举成员值中,就像其他语言中的联合体(unions)和变体(variants)。你可以定义一组通用的相关成员作为枚举的一部分,每一个成员都有不同的一组与它相关的适当类型的数值。
在 Swift 中,枚举类型是一等公民(first-class)。它们采用了很多传统上只被类(class)所支持的特征,例如计算型属性(computed properties),用于提供关于枚举当前值的附加信息, 实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始的实现基础上扩展它们的功能;可以遵守协议(protocols)来提供标准的功能。
1、枚举的语法
1 enum CompassPoint { 2 case North 3 case South 4 case East 5 case West 6 }
枚举中定义的值(例如 North
,South
,East
和West
)是枚举的成员值(或者成员)。case
关键词用来定义新的一行成员值。
注意:和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的CompassPoints
例子中,North
,South
,East
和West
不会隐式地赋值为了0
,1
,2
和3
。相反,这些不同的枚举成员在显示定义的类型CompassPoint
中拥有各自不同的值。
多个成员值可以出现在同一行上,用逗号隔开:
1 enum Planet { 2 case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune 3 }
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如CompassPoint
和Planet
)必须以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:
1 directionToHead = .East
directionToHead
的类型可以在它被CompassPoint
的一个值初始化时推断出来。一旦directionToHead
被声明为一个CompassPoint
,你可以使用点语法(.)将其设置为另一个CompassPoint
的值:
1 directionToHead = .East
2、匹配枚举值和Switch
用Switch语句匹配枚举值:
1 directionToHead = .South 2 switch directionToHead { 3 case .North: 4 print("Lots of planets have a north") 5 case .South: 6 print("Watch out for penguins") 7 case .East: 8 print("Where the sun rises") 9 case .West: 10 print("Where the skies are blue") 11 } 12 // prints "Watch out for penguins"
在判断一个枚举类型的值时,switch
语句必须穷举所有情况。如果忽略了.West
这种情况,上面那段代码将无法通过编译,因为它没有考虑到CompassPoint
的全部成员。强制性全部穷举的要求确保了枚举成员不会被意外遗漏。
不需要匹配每个成员时,用default代替剩下的情况:
1 let somePlanet = Planet.Earth 2 switch somePlanet { 3 case .Earth: 4 print("Mostly harmless") 5 default: 6 print("Not a safe place for humans") 7 } 8 // prints "Mostly harmless"
3、相关值(Associated Values)
你可以为Planet.Earth
设置一个常量或者变量,并且在赋值之后查看这个值。不管怎样,如果有时候能够把其他类型的相关值和成员值一起存储起来会很有用。这能让你存储成员值之外的自定义信息,并且当你每次在代码中使用该成员时允许这个信息产生变化。
定义枚举的时候,可以为每个成员定义任何类型的相关值,并且各个成员的相关值类型可以不同。枚举的这种特性跟其他语言中的可辨识联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。
有些商品上标有 UPC-A 格式的一维t条形码,它使用数字 0 到 9。每一个条形码都有一个代表“数字系统”的数字,该数字后接 5 个代表“生产代码”的数字,接下来是5位“产品代码”。最后一个数字是“检查”位,用来验证代码是否被正确扫描。
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO8859-1 字符,并且可以编码一个最多拥有 2,953 字符的字符串。
对于库存跟踪系统来说,能够把 UPC-A 码作为四个整型值的元组,和把 QR 码作为一个任何长度的字符串存储起来是方便的。
在 Swift 中,使用如下方式定义两种商品条码的枚举:
1 enum Barcode { 2 case UPCA(Int, Int, Int, Int) 3 case QRCode(String) 4 }
这个定义不提供任何Int
或String
的实际值,它只是定义了,当Barcode
常量和变量等于Barcode.UPCA
或Barcode.QRCode
时,相关值的类型。
然后可以使用任何一种条码类型创建新的条码:
1 var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
同一个商品可以被分配给一个不同类型的条形码:
1 productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
不同的条形码类型可以使用一个 switch 语句来检查,相关值可以被提取作为 switch 语句的一部分:
1 switch productBarcode { 2 case .UPCA(let numberSystem, let manufacturer, let product, let check): 3 print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).") 4 case .QRCode(let productCode): 5 print("QR code: \(productCode).") 6 } 7 // prints "QR code: ABCDEFGHIJKLMNOP."
如果一个枚举成员的所有相关值被提取为常量,或者它们全部被提取为变量,为了简洁,你可以只放置一个var
或者let
标注在成员名称前:
1 switch productBarcode { 2 case let .UPCA(numberSystem, manufacturer, product, check): 3 print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).") 4 case let .QRCode(productCode): 5 print("QR code: \(productCode).") 6 } 7 // prints "QR code: ABCDEFGHIJKLMNOP."
4、原始值(Raw Values)
除了相关值,枚举成员还可以被默认值(称为原始值)赋值,其中这些原始值具有相同的类型。
1 enum ASCIIControlCharacter: Character { 2 case Tab = "\t" 3 case LineFeed = "\n" 4 case CarriageReturn = "\r" 5 }
原始值可以是字符串,字符,或者任何整型值或浮点型值。在枚举声明中每个原始值必须是唯一的。
注意:原始值和相关值是不相同的。当代码中定义枚举的时候原始值是被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终是同一个。相关值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次这么做的时候,它的值可以是不同的。
(1)原始值的隐式赋值
在使用原始值为整数或者字符串类型的枚举时,不需要显式的为每一个成员赋值,Swift将会自动为你赋值。
使用整数时,如果第一个成员的原始值没有赋值,则其默认为0,后面的成员逐次加1。
1 enum Planet: Int { 2 case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune 3 }
上例中, Planet.Mercury
的原始值显式赋值为1
, Planet.Venus的原始值
隐式赋值为2,依此类推。
使用String时,每个成员的原始值默认为成员的名字。
1 enum CompassPoint: String { 2 case North, South, East, West 3 }
上例中,CompassPoint.South的原始值默认为”South“,依此类推。
可以用rawValue属性访问枚举的原始值:
1 let earthsOrder = Planet.Earth.rawValue 2 // earthsOrder is 3 3 4 let sunsetDirection = CompassPoint.West.rawValue 5 // sunsetDirection is "West"
(2)使用原始值来初始化
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,原始值类型作为参数,返回枚举成员或者nil
。你可以使用这种初始化方法来创建一个新的枚举变量。
1 let possiblePlanet = Planet(rawValue: 7) 2 // possiblePlanet is of type Planet? and equals Planet.Uranus
并非所有可能的Int
值都可以找到一个匹配的行星。因此,构造函数总是返回一个可选的枚举成员。在上面的例子中,possiblePlanet
是Planet?
类型。
注意:原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。
如果你试图寻找一个位置为9的行星,通过参数为rawValue
构造函数返回的可选Planet
值将是nil
:
1 let positionToFind = 9 2 if let somePlanet = Planet(rawValue: positionToFind) { 3 switch somePlanet { 4 case .Earth: 5 print("Mostly harmless") 6 default: 7 print("Not a safe place for humans") 8 } 9 } else { 10 print("There isn‘t a planet at position \(positionToFind)") 11 } 12 // prints "There isn‘t a planet at position 9"
5、递归枚举
当可能的情况数目是固定的时候,使用枚举来数据建模是很方便的,例如,考虑整数运算的运算符。
算数表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式(5 + 4) * 2
乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也要支持这种嵌套——这表示枚举类型需要支持递归。
递归枚举(recursive enumeration)
是一种枚举类型,在枚举中,有一个或多个枚举成员拥有该枚举的其他成员作为相关值。使用递归枚举时,编译器会插入一个中间层。你可以在枚举成员前加上indirect
来表示这成员可递归。
下面是一个存储简单运算的枚举:
1 enum ArithmeticExpression { 2 case Number(Int) 3 indirect case Addition(ArithmeticExpression, ArithmeticExpression) 4 indirect case Multiplication(ArithmeticExpression, ArithmeticExpression) 5 }
也可以在开头就加上indirect关键字:
1 indirect enum ArithmeticExpression { 2 case Number(Int) 3 case Addition(ArithmeticExpression, ArithmeticExpression) 4 case Multiplication(ArithmeticExpression, ArithmeticExpression) 5 }
上面定义的枚举类型可以存储三种算数表达式:纯数字、两个表达式的相加、两个表达式相乘。Addition
和 Multiplication
成员的相关值也是算数表达式————这些相关值使得嵌套表达式成为可能。
递归函数可以很直观地使用具有递归性质的数据结构。例如,下面是一个计算算数表达式的函数:
1 func evaluate(expression: ArithmeticExpression) -> Int { 2 switch expression { 3 case .Number(let value): 4 return value 5 case .Addition(let left, let right): 6 return evaluate(left) + evaluate(right) 7 case .Multiplication(let left, let right): 8 return evaluate(left) * evaluate(right) 9 } 10 } 11 12 // evaluate (5 + 4) * 2 13 let five = ArithmeticExpression.Number(5) 14 let four = ArithmeticExpression.Number(4) 15 let sum = ArithmeticExpression.Addition(five, four) 16 let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2)) 17 print(evaluate(product)) 18 // prints "18"
该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法元算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。