> 1.0 翻譯:[yankuangshi](https://github.com/yankuangshi)?校對:[shinyzhu](https://github.com/shinyzhu)
>
> 2.0 翻譯+校對:[futantan](https://github.com/futantan)
本頁內容包含:
[TOC=2]
_枚舉_定義了一個通用類型的一組相關值,使你可以在你的代碼中以一種安全的方式來使用這些值。
如果你熟悉 C 語言,你就會知道,在 C 語言中枚舉將枚舉名和一個整型值相對應。Swift 中的枚舉更加靈活,不必給每一個枚舉成員提供一個值。如果給枚舉成員提供一個值(稱為“原始”值),則該值的類型可以是字符串,字符,或是一個整型值或浮點數。
此外,枚舉成員可以指定任何類型的相關值存儲到枚舉成員值中,就像其他語言中的聯合體(unions)和變體(variants)。你可以定義一組通用的相關成員作為枚舉的一部分,每一組都有不同的一組與它相關的適當類型的數值。
在 Swift 中,枚舉類型是一等公民(first-class)。它們采用了很多傳統上只被類(class)所支持的特征,例如計算型屬性(computed properties),用于提供關于枚舉當前值的附加信息,?實例方法(instance methods),用于提供和枚舉所代表的值相關聯的功能。枚舉也可以定義構造函數(initializers)來提供一個初始值;可以在原始的實現基礎上擴展它們的功能;可以遵守協議(protocols)來提供標準的功能。
欲了解更多相關信息,請參見[屬性(Properties)](http://wiki.jikexueyuan.com/project/swift/chapter2/10_Properties.html),[方法(Methods)](http://wiki.jikexueyuan.com/project/swift/chapter2/11_Methods.html),[構造過程(Initialization)](http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html),[擴展(Extensions)](http://wiki.jikexueyuan.com/project/swift/chapter2/20_Extensions.html)和[協議(Protocols)](http://wiki.jikexueyuan.com/project/swift/chapter2/21_Protocols.html)。
## 枚舉語法
使用`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 的枚舉成員在被創建時不會被賦予一個默認的整型值。在上面的`CompassPoints`例子中,`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")
}
// 輸出 "Watch out for penguins”
~~~
你可以這樣理解這段代碼:
“判斷`directionToHead`的值。當它等于`.North`,打印`“Lots of planets have a north”`。當它等于`.South`,打印`“Watch out for penguins”`。”
等等以此類推。
正如在[控制流(Control Flow)](http://wiki.jikexueyuan.com/project/swift/chapter2/05_Control_Flow.html)中介紹的那樣,在判斷一個枚舉類型的值時,`switch`語句必須窮舉所有情況。如果忽略了`.West`這種情況,上面那段代碼將無法通過編譯,因為它沒有考慮到`CompassPoint`的全部成員。強制性全部窮舉的要求確保了枚舉成員不會被意外遺漏。
當不需要匹配每個枚舉成員的時候,你可以提供一個默認`default`分支來涵蓋所有未明確被提出的枚舉成員:
~~~
let somePlanet = Planet.Earth
switch somePlanet {
case .Earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// 輸出 "Mostly harmless”
~~~
## 相關值(Associated Values)
上一小節的例子演示了如何定義(分類)枚舉的成員。你可以為`Planet.Earth`設置一個常量或者變量,并且在賦值之后查看這個值。不管怎樣,如果有時候能夠把其他類型的_相關值_和成員值一起存儲起來會很有用。這能讓你存儲成員值之外的自定義信息,并且當你每次在代碼中使用該成員時允許這個信息產生變化。
你可以定義 Swift 的枚舉存儲任何類型的相關值,如果需要的話,每個成員的數據類型可以是各不相同的。枚舉的這種特性跟其他語言中的可辨識聯合(discriminated unions),標簽聯合(tagged unions),或者變體(variants)相似。
例如,假設一個庫存跟蹤系統需要利用兩種不同類型的條形碼來跟蹤商品。有些商品上標有 UPC-A 格式的一維t條形碼,它使用數字 0 到 9。每一個條形碼都有一個代表“數字系統”的數字,該數字后接 5 個代表“生產代碼”的數字,接下來是5位“產品代碼”。最后一個數字是“檢查”位,用來驗證代碼是否被正確掃描:

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

對于庫存跟蹤系統來說,能夠把 UPC-A 碼作為三個整型值的元組,和把 QR 碼作為一個任何長度的字符串存儲起來是方便的。
在 Swift 中,使用如下方式定義兩種商品條碼的枚舉:
~~~
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
~~~
以上代碼可以這么理解:
“定義一個名為`Barcode`的枚舉類型,它可以是`UPCA`的一個相關值(`Int`,`Int`,`Int`,`Int`),或者是`QRCode`的一個字符串類型(`String`)相關值。”
這個定義不提供任何`Int`或`String`的實際值,它只是定義了,當`Barcode`常量和變量等于`Barcode.UPCA`或`Barcode.QRCode`時,相關值的類型。
然后可以使用任何一種條碼類型創建新的條碼,如:
~~~
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
~~~
以上例子創建了一個名為`productBarcode`的變量,并且賦給它一個`Barcode.UPCA`的相關元組值`(8, 85909, 51226, 3)`。
同一個商品可以被分配給一個不同類型的條形碼,如:
~~~
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
~~~
這時,原始的`Barcode.UPCA`和其整數值被新的`Barcode.QRCode`和其字符串值所替代。條形碼的常量和變量可以存儲一個`.UPCA`或者一個`.QRCode`(連同它的相關值),但是在任何指定時間只能存儲其中之一。
像以前那樣,不同的條形碼類型可以使用一個 switch 語句來檢查,然而這次相關值可以被提取作為 switch 語句的一部分。你可以在`switch`的 case 分支代碼中提取每個相關值作為一個常量(用`let`前綴)或者作為一個變量(用`var`前綴)來使用:
~~~
switch productBarcode {
case .UPCA(let numberSystem, let manufacturer, let product, let check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
print("QR code: \(productCode).")
}
// 輸出 "QR code: ABCDEFGHIJKLMNOP."
~~~
如果一個枚舉成員的所有相關值被提取為常量,或者它們全部被提取為變量,為了簡潔,你可以只放置一個`var`或者`let`標注在成員名稱前:
~~~
switch productBarcode {
case let .UPCA(numberSystem, manufacturer, product, check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .QRCode(productCode):
print("QR code: \(productCode).")
}
// 輸出 "QR code: ABCDEFGHIJKLMNOP."
~~~
## 原始值(Raw Values)
在[相關值](http://wiki.jikexueyuan.com/project/swift/chapter2/08_Enumerations.html#raw_values)小節的條形碼例子中演示了一個枚舉的成員如何聲明它們存儲不同類型的相關值。作為相關值的另一種選擇,枚舉成員可以被默認值(稱為原始值)賦值,其中這些原始值具有相同的類型。
這里是一個枚舉成員存儲 ASCII 碼的例子:
~~~
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
~~~
在這里,`ASCIIControlCharacter`的枚舉類型的原始值類型被定義為字符型`Character`,并被設置了一些比較常見的 ASCII 控制字符。字符值的描述請詳見[字符串和字符](http://wiki.jikexueyuan.com/project/swift/chapter2/03_Strings_and_Characters.html)部分。
原始值可以是字符串,字符,或者任何整型值或浮點型值。每個原始值在它的枚舉聲明中必須是唯一的。
> 注意:
> 原始值和相關值是不相同的。當你開始在你的代碼中定義枚舉的時候原始值是被預先填充的值,像上述三個 ASCII 碼。對于一個特定的枚舉成員,它的原始值始終是相同的。相關值是當你在創建一個基于枚舉成員的新常量或變量時才會被設置,并且每次當你這么做得時候,它的值可以是不同的。
### 原始值的隱式賦值(Implicitly Assigned Raw Values)
在使用原始值為整數或者字符串類型的枚舉時,不需要顯式的為每一個成員賦值,這時,Swift將會自動為你賦值。
例如,當使用整數作為原始值時,隱式賦值的值依次遞增1。如果第一個值沒有被賦初值,將會被自動置為0。
下面的枚舉是對之前`Planet`這個枚舉的一個細化,利用原始整型值來表示每個 planet 在太陽系中的順序:
~~~
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"
~~~
### 使用原始值來初始化(Initializing from a Raw Value)
### 使用原始值初始化枚舉變量(Initializing from a Raw Value)
如果在定義枚舉類型的時候使用了原始值,那么將會自動獲得一個初始化方法,這個方法將原始值類型作為參數,返回枚舉成員或者`nil`。你可以使用這種初始化方法來創建一個新的枚舉變量。
這個例子通過原始值`7`從而創建枚舉成員:
~~~
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 類型為 Planet? 值為 Planet.Uranus
~~~
然而,并非所有可能的`Int`值都可以找到一個匹配的行星。正因為如此,構造函數可以返回一個_可選_的枚舉成員。在上面的例子中,`possiblePlanet`是`Planet?`類型,或“可選的`Planet`”。
> 注意:
> 原始值構造器是一個可失敗構造器,因為并不是每一個原始值都有與之對應的枚舉成員。更多信息請參見[可失敗構造器](http://wiki.jikexueyuan.com/project/swift/chapter3/05_Declarations#failable_initializers)
如果你試圖尋找一個位置為9的行星,通過參數為`rawValue`構造函數返回的可選`Planet`值將是`nil`:
~~~
let positionToFind = 9
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 9
~~~
這個范例使用可選綁定(optional binding),通過原始值`9`試圖訪問一個行星。`if let somePlanet = Planet(rawValue: 9)`語句獲得一個可選`Planet`,如果可選`Planet`可以被獲得,把`somePlanet`設置成該可選`Planet`的內容。在這個范例中,無法檢索到位置為`9`的行星,所以`else`分支被執行。
## 遞歸枚舉(Recursive Enumerations)
在對操作符進行描述的時候,使用枚舉類型來對數據建模很方便,因為需要考慮的情況固定可枚舉。操作符可以將兩個由數字組成的算數表達式連接起來,例如,將`5`連接成復雜一些的表達式`5+4`.
算數表達式的一個重要特性是,表達式可以嵌套使用。例如,表達式`(5 + 4) * 2`乘號右邊是一個數字,左邊則是另一個表達式。因為數據是嵌套的,因而用來存儲數據的枚舉類型也許要支持這種嵌套————這表示枚舉類型需要支持遞歸。
`遞歸枚舉(recursive enumeration)`是一種枚舉類型,表示它的枚舉中,有一個或多個枚舉成員擁有該枚舉的其他成員作為相關值。使用遞歸枚舉時,編譯器會插入一個中間層。你可以在枚舉成員前加上`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`成員的相關值也是算數表達式————這些相關值使得嵌套表達式成為可能。
遞歸函數可以很直觀地使用具有遞歸性質的數據結構。例如,下面是一個計算算數表達式的函數:
~~~
func evaluate(expression: ArithmeticExpression) -> Int {
switch expression {
case .Number(let value):
return value
case .Addition(let left, let right):
return evaluate(left) + evaluate(right)
case .Multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// 計算 (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))
print(evaluate(product))
// 輸出 "18"
~~~
該函數如果遇到純數字,就直接返回該數字的值。如果遇到的是加法或乘法元算,則分別計算左邊表達式和右邊表達式的值,然后相加或相乘。
- 介紹
- 歡迎使用 Swift
- 關于 Swift
- Swift 初見
- Swift 版本歷史記錄
- Swift1.0 發布內容
- Swift 教程
- 基礎部分
- 基本運算符
- 字符串和字符
- 集合類型
- 控制流
- 函數
- 閉包
- 枚舉
- 類和結構體
- 屬性
- 方法
- 下標腳本
- 繼承
- 構造過程
- 析構過程
- 自動引用計數
- 可選鏈
- 錯誤處理
- 類型轉換
- 嵌套類型
- 擴展
- 協議
- 泛型
- 權限控制
- 高級操作符
- 語言參考
- 關于語言參考
- 詞法結構
- 類型
- 表達式
- 語句
- 聲明
- 特性
- 模式
- 泛型參數
- 語法總結
- 蘋果官方Blog官方翻譯
- Access Control 權限控制的黑與白
- 造個類型不是夢-白話Swift類型創建
- WWDC里面的那個“大炮打氣球”
- Swift與C語言指針友好合作
- 引用類型和值類型的恩怨
- 訪問控制和Protected
- 可選類型完美解決占位問題