Swift學習筆記-枚舉(Enumerations)

本頁內容包含:javascript

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

若是你熟悉 C 語言,你就會知道,在 C 語言中枚舉將枚舉名和一個整型值相對應。Swift 中的枚舉更加靈活,沒必要給每個枚舉成員提供一個值。若是給枚舉成員提供一個值(稱爲「原始」值),則該值的類型能夠是字符串,字符,或是一個整型值或浮點數。html

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

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

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

枚舉語法

使用enum關鍵詞來建立枚舉而且把它們的整個定義放在一對大括號內:express

enum SomeEnumeration { // 枚舉定義放在這裏 }

下面是指南針四個方向的例子:swift

enum CompassPoint { case North case South case East case West }

枚舉中定義的值(如 NorthSouthEastWest)是這個枚舉的成員值(或成員)。case關鍵詞表示一行新的成員值將被定義。安全

注意: 和 C 和 Objective-C 不一樣,Swift 的枚舉成員在被建立時不會被賦予一個默認的整型值。在上面的CompassPoint例子中,NorthSouthEastWest不會隱式地賦值爲0123。相反,這些枚舉成員自己就有完備的值,這些值是已經明肯定義好的CompassPoint類型。數據結構

多個成員值能夠出如今同一行上,用逗號隔開:

enum Planet { case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune }

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

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)中介紹的那樣,在判斷一個枚舉類型的值時,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 格式的一維條形碼,它使用數字 0 到 9。每個條形碼都有一個表明「數字系統」的數字,該數字後接 5 個表明「生產代碼」的數字,接下來是5位「產品代碼」。最後一個數字是「檢查」位,用來驗證代碼是否被正確掃描:

其餘商品上標有 QR 碼格式的二維碼,它可使用任何 ISO 8859-1 字符,而且能夠編碼一個最多擁有 2953 個字符的字符串:

對於庫存跟蹤系統來講,可以把 UPC-A 碼做爲四個整型值的元組,和把 QR 碼做爲一個任何長度的字符串存儲起來是方便的。

在 Swift 中,使用以下方式定義兩種商品條碼的枚舉:

enum Barcode {
  case UPCA(Int, Int, Int, Int) case QRCode(String) }

以上代碼能夠這麼理解:

「定義一個名爲Barcode的枚舉類型,它能夠是UPCA的一個相關值(IntIntIntInt),或者是QRCode的一個字符串類型(String)相關值。」

這個定義不提供任何IntString的實際值,它只是定義了,當Barcode常量和變量等於Barcode.UPCABarcode.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)

相關值小節的條形碼例子中演示了一個枚舉的成員如何聲明它們存儲不一樣類型的相關值。做爲相關值的另外一種選擇,枚舉成員能夠被默認值(稱爲原始值)賦值,其中這些原始值具備相同的類型。

這裏是一個枚舉成員存儲 ASCII 碼的例子:

enum ASCIIControlCharacter: Character { case Tab = "\t" case LineFeed = "\n" case CarriageReturn = "\r" }

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

原始值能夠是字符串,字符,或者任何整型值或浮點型值。每一個原始值在它的枚舉聲明中必須是惟一的。

注意:
原始值和相關值是不相同的。當你開始在你的代碼中定義枚舉的時候原始值是被預先填充的值,像上述三個 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賦了初值1Planet.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)

若是在定義枚舉類型的時候使用了原始值,那麼將會自動得到一個初始化方法,這個方法將原始值類型做爲參數,返回值是枚舉成員或nil。你可使用這種初始化方法來建立一個新的枚舉變量。

這個例子經過原始值7從而建立枚舉成員:

let possiblePlanet = Planet(rawValue: 7) // possiblePlanet 類型爲 Planet? 值爲 Planet.Uranus

然而,並不是全部可能的Int值均可以找到一個匹配的行星。正由於如此,構造函數能夠返回一個可選的枚舉成員。在上面的例子中,possiblePlanetPlanet?類型,或「可選的Planet」。

注意: 原始值構造器是一個可失敗構造器,由於並非每個原始值都有與之對應的枚舉成員。更多信息請參見可失敗構造器

若是你試圖尋找一個位置爲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"

該函數若是遇到純數字,就直接返回該數字的值。若是遇到的是加法或乘法運算,則分別計算左邊表達式和右邊表達式的值,而後相加或相乘。

相關文章
相關標籤/搜索