Swift5.4 语言指南(十) 枚举
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9728366.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
一个枚举定义了一个通用型的一组相关的值,使您能够工作在你的代码中的类型安全方式的值。
如果您熟悉C,您将知道C枚举将相关名称分配给一组整数值。Swift中的枚举更加灵活,不必为每种枚举都提供值。如果为每种枚举情况提供了一个值(称为原始值),则该值可以是字符串,字符或任何整数或浮点类型的值。
或者,枚举案例可以指定要存储的任何类型的关联值以及每个不同的案例值,这与其他语言中的并集或变体很相似。您可以将一组常见的相关案例定义为一个枚举的一部分,每个案例都有一组与之相关的不同类型的适当类型的值。
Swift中的枚举本身就是一流的类型。它们采用了传统上仅由类支持的许多功能,例如提供枚举当前值的附加信息的计算属性,以及提供与枚举所表示的值相关的功能的实例方法。枚举还可以定义初始值设定项以提供初始大小写值。可以扩展以扩展其功能,使其超出其最初的实现;并可以遵循协议以提供标准功能。
有关这些功能的更多信息,请参见属性,方法,初始化,扩展和协议。
枚举语法
您用enum
关键字引入枚举,并将它们的整个定义放在一对大括号内:
- enum SomeEnumeration {
- // enumeration definition goes here
- }
这是指南针的四个主要点的示例:
- enum CompassPoint {
- case north
- case south
- case east
- case west
- }
在枚举定义的值(例如north
,south
,east
,和west
)是其枚举的情况下。您可以使用case
关键字引入新的枚举案例。
笔记
与C和Objective-C之类的语言不同,Swift枚举案例默认情况下未设置整数值。在CompassPoint
上面的,例如north
,south
,east
和west
不等于隐式0
,1
,2
和3
。取而代之的是,不同的枚举情况本身就是具有明确定义的类型的值CompassPoint
。
多个案例可以单行显示,并以逗号分隔:
- enum Planet {
- case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
- }
每个枚举定义都定义一个新类型。与Swift中的其他类型一样,它们的名称(例如CompassPoint
和Planet
)以大写字母开头。给枚举类型使用单数而不是复数的名称,以使它们看起来是不言而喻的:
- var directionToHead = CompassPoint.west
directionToHead
当使用的可能值之一初始化时,将推断出的类型CompassPoint
。一旦directionToHead
声明为CompassPoint
,您可以CompassPoint
使用较短的点语法将其设置为其他值:
- directionToHead = .east
的类型directionToHead
是已知的,因此您可以在设置其值时删除该类型。当使用显式类型的枚举值时,这使得代码更具可读性。
将枚举值与switch语句匹配
您可以使用以下switch
语句来匹配单个枚举值:
- directionToHead = .south
- switch directionToHead {
- case .north:
- print("Lots of planets have a north")
- case .south:
- print("Watch out for penguins")
- case .east:
- print("Where the sun rises")
- case .west:
- print("Where the skies are blue")
- }
- // Prints "Watch out for penguins"
您可以按以下方式读取此代码:
“考虑的价值directionToHead
。如果相等.north
,请打印。如果相等,请打印。”"Lots of planets have a north"
.south
"Watch out for penguins"
…等等。
如“控制流”中所述,switch
在考虑枚举的情况时,声明必须是详尽无遗的。如果省略case
for .west
,则此代码不会编译,因为它不会考虑完整的CompassPoint
案例列表。要求详尽无遗,以确保不会意外省略枚举用例。
如果不适合case
为每个枚举案例提供,则可以提供一个default
案例以涵盖未明确解决的所有案例:
- let somePlanet = Planet.earth
- switch somePlanet {
- case .earth:
- print("Mostly harmless")
- default:
- print("Not a safe place for humans")
- }
- // Prints "Mostly harmless"
遍历枚举案例
对于某些枚举,收集所有该枚举的案例很有用。您可以通过在枚举名称后写来启用此功能。Swift将所有案例的集合公开为枚举类型的属性。这是一个例子:: CaseIterable
allCases
- enum Beverage: CaseIterable {
- case coffee, tea, juice
- }
- let numberOfChoices = Beverage.allCases.count
- print("\(numberOfChoices) beverages available")
- // Prints "3 beverages available"
在上面的示例中,您编写Beverage.allCases
了访问包含所有Beverage
枚举案例的集合的权限。您可以allCases
像使用任何其他集合一样使用-集合的元素是枚举类型的实例,因此在这种情况下,它们是Beverage
值。上面的示例计算了有多少个案例,下面的示例使用for
循环遍历所有案例。
- for beverage in Beverage.allCases {
- print(beverage)
- }
- // coffee
- // tea
- // juice
上面示例中使用的语法将枚举标记为符合CaseIterable
协议。有关协议的信息,请参见协议。
关联价值
上一节中的示例说明了枚举的情况如何本身就是已定义(和键入)的值。您可以将常量或变量设置为Planet.earth
,然后稍后检查该值。但是,有时可以将其他类型的值与这些case值一起存储。此附加信息称为关联值,每次您将该案例用作代码中的值时,它都会有所不同。
您可以定义Swift枚举来存储任何给定类型的关联值,并且如果需要,每种枚举的值类型可以不同。类似于这些的枚举在其他编程语言中称为“区分联合”,“标记联合”或“变体”。
例如,假设库存跟踪系统需要通过两种不同类型的条形码来跟踪产品。某些产品以UPC格式标有一维条形码,并使用将该数字标0
为9
。每个条形码都有一个数字系统数字,后跟五个制造商代码数字和五个产品代码数字。这些后面跟一个校验位,以验证代码是否已正确扫描:
其他产品带有QR码格式的2D条形码标签,可以使用任何ISO 8859-1字符,并且可以编码最长为2953个字符的字符串:
对于库存跟踪系统而言,将UPC条形码存储为四个整数的元组以及将QR码条形码存储为任意长度的字符串非常方便。
在Swift中,定义两种类型产品条形码的枚举可能看起来像这样:
- enum Barcode {
- case upc(Int, Int, Int, Int)
- case qrCode(String)
- }
可以理解为:
“定义称为枚举类型Barcode
,这可能需要或者是的值upc
与类型相关联的值(Int
,Int
,Int
,Int
),或的值qrCode
与类型相关联的值String
。”
此定义不提供任何实际值Int
或String
值-只是定义了常量和变量等于或时可以存储的关联值的类型。Barcode
Barcode.upc
Barcode.qrCode
然后,您可以使用以下两种类型之一创建新的条形码:
- var productBarcode = Barcode.upc(8, 85909, 51226, 3)
本示例创建一个名为的新变量,productBarcode
并为其分配值为,并将其Barcode.upc
元组值关联为。(8, 85909, 51226, 3)
您可以为同一产品分配不同类型的条形码:
- productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
此时,原始Barcode.upc
值及其整数值将替换为新Barcode.qrCode
值及其字符串值。类型的常量和变量Barcode
可以存储a.upc
或a .qrCode
(以及它们的关联值),但是在任何给定时间只能存储其中之一。
您可以使用switch语句检查不同的条形码类型,类似于将枚举值与Switch语句匹配中的示例。但是,这次,关联值被提取为switch语句的一部分。您可以将每个关联值提取为常量(带有let
前缀)或变量(带有var
前缀),以在switch
案例的正文中使用:
- switch productBarcode {
- case .upc(let numberSystem, let manufacturer, let product, let check):
- print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
- case .qrCode(let productCode):
- print("QR code: \(productCode).")
- }
- // Prints "QR code: ABCDEFGHIJKLMNOP."
为了简单起见,如果枚举案例的所有关联值都提取为常量,或者全部提取为变量,则可以在案例名称前放置一个var
或let
注解:
- switch productBarcode {
- case let .upc(numberSystem, manufacturer, product, check):
- print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
- case let .qrCode(productCode):
- print("QR code: \(productCode).")
- }
- // Prints "QR code: ABCDEFGHIJKLMNOP."
原始值
关联值中的条形码示例显示了枚举的情况如何声明它们存储了不同类型的关联值。作为关联值的替代方法,可以在枚举案例中预先填充默认值(称为原始值),这些默认值都属于同一类型。
这是一个将原始ASCII值与命名枚举大小写一起存储的示例:
- enum ASCIIControlCharacter: Character {
- case tab = "\t"
- case lineFeed = "\n"
- case carriageReturn = "\r"
- }
在这里,被称为的枚举的原始值ASCIIControlCharacter
被定义为type Character
,并被设置为一些更常见的ASCII控制字符。Character
值在“字符串和字符”中进行了描述。
原始值可以是字符串,字符或任何整数或浮点数类型。每个原始值在其枚举声明中必须唯一。
笔记
原始值是不一样的关联值。首次在代码中定义枚举时,原始值将设置为预填充的值,例如上面的三个ASCII代码。特定枚举情况的原始值始终是相同的。当根据枚举的一种情况创建新的常量或变量时,将设置关联值,并且每次都可以不同。
隐式分配的原始值
使用存储整数或字符串原始值的枚举时,不必为每种情况显式分配原始值。如果您不这样做,Swift会自动为您分配值。
例如,当整数用于原始值时,每种情况的隐式值都比前一种情况大一。如果第一种情况未设置值,则其值为0
。
下面的枚举是对先前Planet
枚举的改进,它使用整数原始值来表示每个行星从太阳起的阶数:
- enum Planet: Int {
- case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
- }
在上面的示例中,Planet.mercury
显式原始值为1
,Planet.venus
隐式原始值为2
,依此类推。
如果将字符串用于原始值,则每种情况的隐式值都是该情况名称的文本。
下面的枚举是对先前CompassPoint
枚举的改进,使用字符串原始值来表示每个方向的名称:
- enum CompassPoint: String {
- case north, south, east, west
- }
在上面的示例中,CompassPoint.south
具有的隐式原始值"south"
,依此类推。
您可以使用其rawValue
属性访问枚举案例的原始值:
- let earthsOrder = Planet.earth.rawValue
- // earthsOrder is 3
- let sunsetDirection = CompassPoint.west.rawValue
- // sunsetDirection is "west"
从原始值初始化
如果您使用原始值类型定义枚举,则枚举会自动接收一个初始化器,该初始化器采用原始值类型的值(称为rawValue
)作为参数,并返回枚举用例或nil
。您可以使用此初始化程序尝试创建枚举的新实例。
本示例从其原始值识别天王星7
:
- let possiblePlanet = Planet(rawValue: 7)
- // possiblePlanet is of type Planet? and equals Planet.uranus
但是,并非所有可能的Int
值都会找到匹配的行星。因此,原始值初始化程序始终返回可选的枚举情况。在上面的示例中,possiblePlanet
类型为Planet?
或“可选” Planet
。
笔记
原始值初始化程序是一个失败的初始化程序,因为并非每个原始值都将返回枚举情况。有关更多信息,请参见Failable Initializers。
如果您尝试寻找位置为的行星,则原始值初始值设定项返回11
的可选Planet
值将是nil
:
- let positionToFind = 11
- if let somePlanet = Planet(rawValue: positionToFind) {
- switch somePlanet {
- case .earth:
- print("Mostly harmless")
- default:
- print("Not a safe place for humans")
- }
- } else {
- print("There isn't a planet at position \(positionToFind)")
- }
- // Prints "There isn't a planet at position 11"
本示例使用可选绑定尝试访问原始值为的行星11
。该语句创建一个可选的,并设置为该可选的值(如果可以检索的话)。在这种情况下,无法检索位置为的行星,因此将执行分支。if let somePlanet = Planet(rawValue: 11)
Planet
somePlanet
Planet
11
else
递归枚举
甲递归枚举是具有枚举作为一个或一个以上的枚举案件相关联的值的另一个实例的枚举。您可以通过在枚举案例indirect
之前编写来表明它是递归的,这告诉编译器插入必要的间接层。
例如,这是一个存储简单算术表达式的枚举:
- enum ArithmeticExpression {
- case number(Int)
- indirect case addition(ArithmeticExpression, ArithmeticExpression)
- indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
- }
您也可以indirect
在枚举开始之前编写代码,以为具有关联值的所有枚举用例启用间接寻址:
- indirect enum ArithmeticExpression {
- case number(Int)
- case addition(ArithmeticExpression, ArithmeticExpression)
- case multiplication(ArithmeticExpression, ArithmeticExpression)
- }
该枚举可以存储三种算术表达式:素数,两个表达式的加法以及两个表达式的乘法。将addition
与multiplication
案件有同样的算术表达式,这些相关的值有可能嵌套表达式相关的值。例如,该表达式在乘法的右侧具有一个数字,而在乘法的左侧具有另一个表达式。因为数据是嵌套的,所以用于存储数据的枚举也需要支持嵌套-这意味着该枚举需要递归。下面的代码显示了为创建的递归枚举:(5 + 4) * 2
ArithmeticExpression
(5 + 4) * 2
- let five = ArithmeticExpression.number(5)
- let four = ArithmeticExpression.number(4)
- let sum = ArithmeticExpression.addition(five, four)
- let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
递归函数是使用具有递归结构的数据的直接方法。例如,下面是一个计算算术表达式的函数:
- func evaluate(_ expression: ArithmeticExpression) -> Int {
- switch expression {
- case let .number(value):
- return value
- case let .addition(left, right):
- return evaluate(left) + evaluate(right)
- case let .multiplication(left, right):
- return evaluate(left) * evaluate(right)
- }
- }
- print(evaluate(product))
- // Prints "18"
该函数通过简单地返回相关值来评估纯数字。它通过在左侧评估表达式,在右侧评估表达式,然后将它们相加或相乘,来评估加法或乘法。