昨天介紹列舉和搭配 switch 語句的使用方式,以及如何用 for 來遍歷列舉的所有例項,今天將介紹列舉的關聯值 (Associated Values) 以及原始值 (Raw Values)。
上一節中的範例顯示了列舉的例項本身是如何定義(和型別化)的值。可以將常數或變數設置為 Planet.earth
,稍後檢查此值。但是,能夠在這些例項值旁邊存儲其他型別的值有時很有用。此附加資訊稱為關聯值,每次將該例項的值用在代碼中時,它都會有所變化。
我們可以定義 Swift 列舉來存儲任何給定型別的關聯值,並且如果需要,每種列舉例項的值型別可以不同。與這些類似的列舉在其他編程語言中稱為區別聯合 (discriminated unions)、標記聯合 (tagged unions) 或變體 (variants)。
例如,假設庫存追蹤系統需要通過兩種不同型別的條形碼來追蹤產品。某些產品標有 UPC 格式的一維條碼,使用數字 0 到 9,每個條碼都有一個系統數字,及五個製造商代碼數字和五個產品代碼數字,接著是校驗位用來驗證代碼是否已正確掃描:
其他產品使用 QR 碼格式的二維條碼進行標記,可以使用任何 ISO 8859-1 字符,並且可以編碼長達 2,953 個字元的字串:
在 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
及其整數值將被新的Barc ode.qrCode
及其字串值替換。Barcode
型別的常數和變數可以存儲成 .upc
或 .qrCode
(及其關聯值),但它們在任何時間內只能存儲其中一個。
我們可以使用 switch
語句檢查不同的條碼型別,類似於使用 switch
語句搭配列舉值中的示範例。但是,這次相關值將作為 switch
語句的一部分提取。將每個關聯值提取為常數(使用 let
前綴)或變數(使用 var
前綴)以在 switch case
的正文中使用:
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
的列舉的原始值被定義為 Character
型別,並被設置為一些更常見的 ASCII 控制字元。
原始值可以是字串、字元或任何整數或浮點數型別。每個原始值在其列舉宣告中必須是唯一的。
原始值與關聯值不同。當首次在代碼中定義列舉時,原始值將設置為預填充值,如上面的三個 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? 或 “Optional 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)")
}
// Prints "There isn't a planet at position 11"
此範例使用可選型別綁定嘗試訪問原始值為 11 的行星。if somePlanet = Planet(rawValue:11)
創建可選型別 Planet
,則將 somePlanet
設置為可選型別 Planet
的值(如果可以檢索)。在這種情況下,無法取得位置為 11 的行星,因此執行 else
分支。