列舉為一組相關的值定義了一個共同的型別,使我們在程式碼中以型別安全的方式來使用這些值。
Swift中的列舉非常靈活,不必給每一個列舉成員提供一個值。如果給列舉成員提供一個原始值,則該值的型別可以是字串,字元,或者是一個整形值或者浮點值。
此外,列舉成員可以指定任意型別的關聯值儲存到列舉成員中,就像其他語言中的聯合體和變數。可以在一個列舉中定義一組相關的列舉成員,每一個列舉成員都可以有適當型別的關聯值。
在Swift中,列舉型別是一等型別。它們採用了很多在傳統上只被類所支援的特性,例如計算屬性,用於提供列舉值的附加資訊,例項方法,用於提供和列舉值相聯的功能。列舉也可以定義建構函式來提供一個初始值,可以在原始實現的基礎上擴充套件它們的功能,還可以遵循協議來提供標準功能。
本文涉及到的內容有:列舉語法
、使用Switch語句匹配列舉值
、關聯值
、原始值
、遞迴列舉
。
列舉語法
使用enum
關鍵詞來建立列舉並且把它們的整個定義放在一對大括號內,如下:
enum CompassPoint {
case north
case south
case east
case west
}
複製程式碼
列舉中定義的值(如:north
、south
、east
和west
)是這個列舉的成員。可以使用case
關鍵字來定義一個新的列舉成員值。
注: Swift的列舉成員子在被建立時不會被賦予一個預設的整形值。在上面的CompassPoint
例子中,north
、south
、eadt
和west
不會被隱式的賦值為0
、1
、2
和3
。相反,這些列舉成員本身就是完備的值,這些值的型別是已經明確定義好的CompassPoint
型別。
多個成員值可以出現在同一行上,用逗號隔開,如下:
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
複製程式碼
每個列舉定義了一個全新的型別。像Swift中其他型別一樣。它們的名字應該以一個大寫字母開頭。給列舉型別起一個單數名字而不是複數名字,以便於:
var directionToHead = CompassPoint.west
directionToHead = .east
複製程式碼
當申明的變數的型別被已知時,再次為其賦值可以省略列舉型別名。
使用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")
}
// 列印 "Watch out for penguins”
複製程式碼
注: 上述列舉中並未使用default
分支,因為列舉了所有情況,便可以省略default
分支,不然會編譯錯誤
關聯值
可以定義Swift列舉來儲存任意型別的關聯值,如果需要的話,每個列舉成員的關聯值型別可以各不相同。列舉的這種特性跟其他語言中的可識別聯合,標籤聯合或者變體相似。
例如,假設一個庫存跟蹤系統需要利用兩種不同型別的條形碼來跟蹤商品。有些商品上標有使用0
到9
的數字的URC格式的一維條形碼。另外一種是QR碼格式的二維碼,它可以使用任何ISO 8859-1 字元,並且可以編碼一個最多擁有2953個字元的字串,在Swift中,使用如下方式定義表示兩種商品條形碼的列舉:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
複製程式碼
以上程式碼可以這麼理解:“定義一個名為Barcode
的列舉型別,它的一個成員值是具有(Int, Int, Int, Int)
型別關聯值的upc
,另一個成員值是具有String
型別關聯值的qrCode
。”
這個定義不提供任何Int
或String
型別的關聯值,它只是定義了,當Barcode
常量和變數等於Barcode.upc
或Barcode.quCode
時,可以儲存的相關值的型別。
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
複製程式碼
像先前那樣,可以使用一個switch語句來檢查不同的條形碼型別。然而,這一次,關聯值可以被提取出來作為switch語句一部分。你可以在switch
的case分支程式碼中提取每個關聯值作為一個常量(用let
字首)或者作為一個變數(用var
字首)來使用:
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).")
}
// 列印 "QR code: ABCDEFGHIJKLMNOP."
複製程式碼
如果一個列舉成員的所有關聯值都被提取為常量,或者都被提取為常量,為了簡潔,你可以只在成員名稱前標註一個let
或者var
比如:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// 輸出 "QR code: ABCDEFGHIJKLMNOP."
複製程式碼
原始值
作為關聯值的替代選擇,列舉成員可以被原始值(預設值)預填充,這些原始值的型別必須相同。 如下:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
複製程式碼
列舉型別 ASCIIControlCharacter
的原始值型別被定義為 Character
,並設定了一些比較常見的 ASCII 控制字元。
原始值可以是字串、字元,或者任意整形值或浮點值。每個原始值在列舉宣告中必須是唯一的。
注: 原始值和關聯值是不同的。原始值是在定義列舉時被預填充的值,像上述三個 ASCII 碼。對於一個特定的列舉成員,它的原始值始終不變。關聯值是建立一個基於列舉成員的常量或變數時才設定的值,列舉成員的關聯值可以變化。
原始值的隱藏賦值
在使用原始值為整數護著字串型別的列舉時,不需要顯式的為每一個列舉成員設定原始值,Swift會自動賦值。
例如,當使用整數作為原始值時,隱式賦值的依次遞增1
,如果第一個列舉成員沒有設定原始值,其原始值為0
。
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
複製程式碼
在上面的例子中,Plant.mercury
的顯式原始值為 1
,Planet.venus
的隱式原始值為 2
,依次類推。
而當使用字串作為列舉型別的原始值時,每個列舉成員的隱式原始值為該列舉成員的名稱。下面的例子是 CompassPoint
列舉的細化,使用字串型別的原始值來表示各個方向的名稱:
enum CompassPoint: String {
case north, south, east, west
}
複製程式碼
上面例子中,CompassPoint.south
擁有隱式原始值 south
,依次類推。
使用列舉成員的 rawValue
屬性可以訪問該列舉成員的原始值:
let earthsOrder = Planet.earth.rawValue
// earthsOrder 值為 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection 值為 "west"
複製程式碼
使用原始值初始化列舉例項
如果在定義列舉型別的時候使用了原始值,那麼將會自動獲得一個初始化方法,這個方法接受一個叫做rawValue
的引數,引數型別即為原始值型別,返回值則是列舉成員或者nil
。
這個例子利用原始值7
建立了列舉成員uranus
如下:
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 型別為 Planet? 值為 Planet.uranus
複製程式碼
然而,並非所有Int
值都能可以找到一個匹配的行星。因此原始值構造器總是返回一個可選的列舉成員。在上面的例子中,possiblePlanet
是Planet?
型別,或者說可選的Planet
。
注: 原始值構造器是一個可失敗的構造器。因為並不是每一個原始值都有與之對應的列舉成員。如果你試圖尋找一個位置為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)")
}
// 輸出 "There isn't a planet at position 11
複製程式碼
遞迴列舉
遞迴列舉是一種列舉型別,它有一個或多個列舉成員使用該列舉型別的例項作為關聯值。使用遞迴列舉時,編譯器會插入一個間接層。你可以在列舉成員前加上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))
// 列印 "18"
複製程式碼
該函式如果遇到純數字,就直接返回該數字的值。如果遇到的是加法或乘法運算,則分別計算左邊表示式和右邊表示式的值,然後相加或相乘