翻譯:yankuangshi
校對:shinyzhu

枚舉(Enumerations)


本頁內容包含:

枚舉定義了一個通用類型的一組相關的值,使你可以在你的代碼中以一個安全的方式來使用這些值。

如果你熟悉 C 語言,你就會知道,在 C 語言中枚舉指定相關名稱為一組整型值。Swift 中的枚舉更加靈活,不必給每一個枚舉成員提供一個值。如果一個值(被認為是「原始」值)被提供給每個枚舉成員,則該值可以是一個字符串,一個字符,或是一個整型值或浮點值。

此外,枚舉成員可以指定任何類型的相關值存儲到枚舉成員值中,就像其他語言中的聯合體(unions)和變體(variants)。你可以定義一組通用的相關成員作為枚舉的一部分,每一組都有不同的一組與它相關的適當類型的數值。

在 Swift 中,枚舉類型是一等(first-class)類型。它們採用了很多傳統上只被類(class)所支持的特徵,例如計算型屬性(computed properties),用於提供關於枚舉當前值的附加信息,□實例方法(instance methods),用於提供和枚舉所代表的值相關聯的功能。枚舉也可以定義構造函數(initializers)來提供一個初始成員值;可以在原始的實現基礎上擴展它們的功能;可以遵守協議(protocols)來提供標準的功能。

欲瞭解更多相關功能,請參見屬性(Properties)方法(Methods)構造過程(Initialization)擴展(Extensions)協議(Protocols)

枚舉語法

使用enum關鍵詞並且把它們的整個定義放在一對大括號內:

  1. enum SomeEnumeration {
  2. // enumeration definition goes here
  3. }

以下是指南針四個方向的一個例子:

  1. enum CompassPoint {
  2. case North
  3. case South
  4. case East
  5. case West
  6. }

一個枚舉中被定義的值(例如 NorthSouthEastWest)是枚舉的成員值(或者成員)。case關鍵詞表明新的一行成員值將被定義。

注意:
不像 C 和 Objective-C 一樣,Swift 的枚舉成員在被創建時不會被賦予一個默認的整數值。在上面的CompassPoints例子中,NorthSouthEastWest不是隱式的等於0123。相反的,這些不同的枚舉成員在CompassPoint的一種顯示定義中擁有各自不同的值。

多個成員值可以出現在同一行上,用逗號隔開:

  1. enum Planet {
  2. case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
  3. }

每個枚舉定義了一個全新的類型。像 Swift 中其他類型一樣,它們的名字(例如CompassPointPlanet)必須以一個大寫字母開頭。給枚舉類型起一個單數名字而不是複數名字,以便於讀起來更加容易理解:

  1. var directionToHead = CompassPoint.West

directionToHead的類型被推斷當它被CompassPoint的一個可能值初始化。一旦directionToHead被聲明為一個CompassPoint,你可以使用更短的點(.)語法將其設置為另一個CompassPoint的值:

  1. directionToHead = .East

directionToHead的類型已知時,當設定它的值時,你可以不再寫類型名。使用顯式類型的枚舉值可以讓代碼具有更好的可讀性。

匹配枚舉值和Switch語句

你可以匹配單個枚舉值和switch語句:

  1. directionToHead = .South
  2. switch directionToHead {
  3. case .North:
  4. println("Lots of planets have a north")
  5. case .South:
  6. println("Watch out for penguins")
  7. case .East:
  8. println("Where the sun rises")
  9. case .West:
  10. println("Where the skies are blue")
  11. }
  12. // 輸出 "Watch out for penguins」

你可以如此理解這段代碼:

「考慮directionToHead的值。當它等於.North,打印「Lots of planets have a north」。當它等於.South,打印「Watch out for penguins」。」

等等依次類推。

正如在控制流(Control Flow)中介紹,當考慮一個枚舉的成員們時,一個switch語句必須全面。如果忽略了.West這種情況,上面那段代碼將無法通過編譯,因為它沒有考慮到CompassPoint的全部成員。全面性的要求確保了枚舉成員不會被意外遺漏。

當不需要匹配每個枚舉成員的時候,你可以提供一個默認default分支來涵蓋所有未明確被提出的任何成員:

  1. let somePlanet = Planet.Earth
  2. switch somePlanet {
  3. case .Earth:
  4. println("Mostly harmless")
  5. default:
  6. println("Not a safe place for humans")
  7. }
  8. // 輸出 "Mostly harmless」

相關值(Associated Values)

上一小節的例子演示了一個枚舉的成員是如何被定義(分類)的。你可以為Planet.Earth設置一個常量或則變量,並且在之後查看這個值。不管怎樣,如果有時候能夠把其他類型的相關值和成員值一起存儲起來會很有用。這能讓你存儲成員值之外的自定義信息,並且當你每次在代碼中使用該成員時允許這個信息產生變化。

你可以定義 Swift 的枚舉存儲任何類型的相關值,如果需要的話,每個成員的數據類型可以是各不相同的。枚舉的這種特性跟其他語言中的可辨識聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)相似。

例如,假設一個庫存跟蹤系統需要利用兩種不同類型的條形碼來跟蹤商品。有些商品上標有 UPC-A 格式的一維碼,它使用數字 0 到 9。每一個條形碼都有一個代表「數字系統」的數字,該數字後接 10 個代表「標識符」的數字。最後一個數字是「檢查」位,用來驗證代碼是否被正確掃瞄:

枚舉(Enumerations) - 图1

其他商品上標有 QR 碼格式的二維碼,它可以使用任何 ISO8859-1 字符,並且可以編碼一個最多擁有 2,953 字符的字符串:

枚舉(Enumerations) - 图2

對於庫存跟蹤系統來說,能夠把 UPC-A 碼作為三個整型值的元組,和把 QR 碼作為一個任何長度的字符串存儲起來是方便的。

在 Swift 中,用來定義兩種商品條碼的枚舉是這樣子的:

  1. enum Barcode {
  2. case UPCA(Int, Int, Int)
  3. case QRCode(String)
  4. }

以上代碼可以這麼理解:

「定義一個名為Barcode的枚舉類型,它可以是UPCA的一個相關值(IntIntInt),或者QRCode的一個字符串類型(String)相關值。」

這個定義不提供任何IntString的實際值,它只是定義了,當Barcode常量和變量等於Barcode.UPCABarcode.QRCode時,相關值的類型。

然後可以使用任何一種條碼類型創建新的條碼,如:

  1. var productBarcode = Barcode.UPCA(8, 85909_51226, 3)

以上例子創建了一個名為productBarcode的新變量,並且賦給它一個Barcode.UPCA的相關元組值(8, 8590951226, 3)。提供的「標識符」值在整數字中有一個下劃線,使其便於閱讀條形碼。

同一個商品可以被分配給一個不同類型的條形碼,如:

  1. productBarcode = .QRCode("ABCDEFGHIJKLMNOP")

這時,原始的Barcode.UPCA和其整數值被新的Barcode.QRCode和其字符串值所替代。條形碼的常量和變量可以存儲一個.UPCA或者一個.QRCode(連同它的相關值),但是在任何指定時間只能存儲其中之一。

像以前那樣,不同的條形碼類型可以使用一個 switch 語句來檢查,然而這次相關值可以被提取作為 switch 語句的一部分。你可以在switch的 case 分支代碼中提取每個相關值作為一個常量(用let前綴)或者作為一個變量(用var前綴)來使用:

  1. switch productBarcode {
  2. case .UPCA(let numberSystem, let identifier, let check):
  3. println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
  4. case .QRCode(let productCode):
  5. println("QR code with value of \(productCode).")
  6. }
  7. // 輸出 "QR code with value of ABCDEFGHIJKLMNOP.」

如果一個枚舉成員的所有相關值被提取為常量,或者它們全部被提取為變量,為了簡潔,你可以只放置一個var或者let標注在成員名稱前:

  1. switch productBarcode {
  2. case let .UPCA(numberSystem, identifier, check):
  3. println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
  4. case let .QRCode(productCode):
  5. println("QR code with value of \(productCode).")
  6. }
  7. // 輸出 "QR code with value of ABCDEFGHIJKLMNOP."

原始值(Raw Values)

Associated Values小節的條形碼例子中演示了一個枚舉的成員如何聲明它們存儲不同類型的相關值。作為相關值的替代,枚舉成員可以被默認值(稱為原始值)預先填充,其中這些原始值具有相同的類型。

這裡是一個枚舉成員存儲原始 ASCII 值的例子:

  1. enum ASCIIControlCharacter: Character {
  2. case Tab = "\t"
  3. case LineFeed = "\n"
  4. case CarriageReturn = "\r"
  5. }

在這裡,稱為ASCIIControlCharacter的枚舉的原始值類型被定義為字符型Character,並被設置了一些比較常見的 ASCII 控制字符。字符值的描述請詳見字符串和字符Strings and Characters部分。

注意,原始值和相關值是不相同的。當你開始在你的代碼中定義枚舉的時候原始值是被預先填充的值,像上述三個 ASCII 碼。對於一個特定的枚舉成員,它的原始值始終是相同的。相關值是當你在創建一個基於枚舉成員的新常量或變量時才會被設置,並且每次當你這麼做得時候,它的值可以是不同的。

原始值可以是字符串,字符,或者任何整型值或浮點型值。每個原始值在它的枚舉聲明中必須是唯一的。當整型值被用於原始值,如果其他枚舉成員沒有值時,它們會自動遞增。

下面的枚舉是對之前Planet這個枚舉的一個細化,利用原始整型值來表示每個 planet 在太陽系中的順序:

  1. enum Planet: Int {
  2. case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
  3. }

自動遞增意味著Planet.Venus的原始值是2,依次類推。

使用枚舉成員的rawValue屬性可以訪問該枚舉成員的原始值:

  1. let earthsOrder = Planet.Earth.rawValue
  2. // earthsOrder is 3

通過參數為rawValue構造函數創建特定原始值的枚舉。這個例子通過原始值7識別Uranus

  1. let possiblePlanet = Planet(rawValue: 7)
  2. // possiblePlanet is of type Planet? and equals Planet.Uranus

然而,並非所有可能的Int值都可以找到壹個匹配的行星。正因為如此,構造函數可以返回壹個可選的枚舉成員。在上面的例子中,possiblePlanetPlanet?類型,或“可選的Planet”。

如果妳試圖尋找壹個位置為9的行星,通過參數為rawValue構造函數返回的可選Planet值將是nil

  1. let positionToFind = 9
  2. if let somePlanet = Planet(rawValue: positionToFind) {
  3. switch somePlanet {
  4. case .Earth:
  5. println("Mostly harmless")
  6. default:
  7. println("Not a safe place for humans")
  8. }
  9. } else {
  10. println("There isn't a planet at position \(positionToFind)")
  11. }
  12. // 輸出 "There isn't a planet at position 9

這個範例使用可選綁定(optional binding),通過原始值9試圖訪問一個行星。if let somePlanet = Planet(rawValue: 9)語句獲得一個可選Planet,如果可選Planet可以被獲得,把somePlanet設置成該可選Planet的內容。在這個範例中,無法檢索到位置為9的行星,所以else分支被執行。