iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 14
1

枚舉 (Enumerations)

enum 介紹

enum 定義了一組相關值的通用類型,並讓你能夠在代碼中以類型安全的方式處理這些值。

enum 在 Swift 語言中相較於其他語言更加靈活了,而且他不必為 enum 中每種 case 都提供值。如果我們為 enum 每個 case 都提供值,則該值可以是String,Character 或任何 Int 或是 Float-point 類型的值。
而且,枚舉中的情況可以指定每個不同情況下的值儲存任何類型的關聯值。你可以定義一組有關聯 case 的合集作為 enum 的一部分,每個 enum 有著一組不同合適類型的合集值。

Swift 中的枚舉是具有自己權限的 first-class 類型,他們採用許多慣例上只被 class 中的支援的方式,像是計算屬性提供有關 enum 目前值的附加訊息,而且實例方法提供提供 enum 表示值有關聯的功能。枚舉也可以定義初始器來提供初始 case 中的值;並可以擴大到超出原本能夠實現範圍的功能,並且符合協議以提供標準功能。


enum 語法

你可以透過下列方式定義一個 enum

enum EnumerationName { // 輸入 enum 名稱
    //在此定義 enum 的 case
}

在此我們建立一個方向的枚舉 (上下左右) :

enum Direction {
    case up
    case down
    case left
    case right
}

你也可以在一個 case 設多個值,每個值必須由逗號 (,) 分開:

enum Letter {
    case A, B, C, D, E
    case F, G, H, I, J
}

你當然也可以宣告 Direction 中的 case 給一個變數,當你與 Direction 中可用的某一值一同初始化時 direction1 的類型會被推斷出來。之後如果需要改變 direction1 中的值,只需在後面加上一個點 (.) 就會跳出 Direction 中的 case 。(因為 direction1 的類型是已知的)

https://ithelp.ithome.com.tw/upload/images/20180102/20107701Hub0lrnmXn.png
https://ithelp.ithome.com.tw/upload/images/20180102/20107701Tc7yPW1tRw.png


使用 Switch 語句來匹配 enum 值

你可以用 switch 語句來匹配每一個單獨的 enum 值:
https://ithelp.ithome.com.tw/upload/images/20180102/20107701YDJ3lJuI0I.png

判斷 direction1 中的值,如果為 .up 則印出 ⬆️ , .down 則印出 ⬇️ ...以此類推。

如果你無法為所有的情況設一個 case , 你也可以用 default 來包含其他無法預設的情況:
https://ithelp.ithome.com.tw/upload/images/20180102/20107701dVj0PLbse8.png


關聯值 (Associated Values)

你可以定義 Swift 中的枚舉來儲存任何你自定類型的關聯值,如果你需要值的類型可以根據每種不同的 case 做改變。下面我們舉一個商品編碼,可以使用不同的 code 來呈現:

enum Code {
    case numCode(Int, Int, Int, Int) //由四個 Int 值所組成
    case nameCode(String,Int) //由一個 String 及一個 Int 值組成
}

之後我們可以把他宣告到一個變數中:

var productCode = Code.nameCode("蘋果", 20)

這個 productCode 變數被賦予了一個 Code.nameCode 中的值 關聯了值為 ("蘋果", 20) 的元組

同樣的產品可以被分配一個不同類型的 Code,宣告之後我們也可以更改他的內容:

productCode = .numCode(830, 775, 08, 20)

接下來我們可以用 switch 來判斷現在產品的 Code 為哪種格式,並將他的內容印出, 我們可以對 case 中的對應值為他們宣告成一個常數或變數,讓他可以在 switch 中的 case 中被使用:

https://ithelp.ithome.com.tw/upload/images/20180102/20107701GMWALvRCwl.png

如果一個 case 中都是被宣告成常數或者是變數的話,我們可以在 case 的名稱前面加上 let 或是 var 即可,使得程式碼更簡潔。
https://ithelp.ithome.com.tw/upload/images/20180102/20107701alcYX5iWyH.png


原始值 (Raw Values)

上面的關聯值的範例說明了 enum 中的 case 是如何聲明它們存儲不同類型的相關值的。作為相關值的另一種選擇,enum 中的 case 也可以用相同類型的默認值預先輸入。

enum numEnglish:Int {
    case one = 1
    case two = 2
    case three = 3
}

隱式指定的原始值 (Implicitly Assigned Raw Values)

當你使用 enum 來儲存整數或是字串作為的原始值時,你不必明確的分配一個原始值給每個 case ,當你不這麼做時,Swift 將會自動幫你分配值。

舉個例子,當你使用整數當原始值時,每個 case 的隱含值都比前一個 case 大一,如果第一個情況沒有設定值,那麼他的值就為 0 。

我們使用英文字母順序來作為例子:

enum AlphabeticalOrder:Int {
    case A = 1, B , C , D ,F ,G
}

那麼 A 有一個明確的原始值為 1 ,那麼 B 的隱含原始值就為 2 , C 為 3 ...以此類推。

當字符串被用於原始值,那麼每一個成員的隱式原始值則是那個成員的名稱,我們使用前面提過的方向作為使用範例。

enum Direction:String {
    case up, down, left, right
}

這個例子中 Direction.up 的隱含原始值為 up, .down 的隱含原始值為 down。...以此類推

之後你可以透過 rawValue 來查看 enum 中的 case 的原始值:
https://ithelp.ithome.com.tw/upload/images/20180102/20107701aCBFEKP47j.png


從原始值初始化 (Initializing from a Raw Value)

如果你定義了一個使用原始值的 enum 類型,那麼枚舉就會自動收到一個可以接受原始值類型的值的初始化器(叫做 rawValue的形式參數)然後返回一個枚舉成員或者 nil,你可以使用這個初始化器來嘗試創建一個 enum 的新實例。

我們可以從它的原始值 5 來辨認出字母 F :
https://ithelp.ithome.com.tw/upload/images/20180102/20107701tBg18PqtNe.png

總之,不是所有可能的值都會對應到一個 case 。因此原始值的初始化器總是返回可選的枚舉成員。因此我們 alphabeticalOrder 的類型是 alphabeticalOrder ,或者是 Optional alphabeticalOrder。

原始值初始化器是一個可失敗初始化器,因為不是所有原始值都將返回一個 enum 的 case

如果你嘗試尋找超出範圍的字母,那麼被原始值初始化器返回的 Optional alphabeticalOrder 值將會是 nil:
https://ithelp.ithome.com.tw/upload/images/20180102/20107701bOIcFAJMw0.png


遞歸枚舉 (Recursive Enumerations)

遞歸枚舉是擁有另一個枚舉作為枚舉 case 關聯值的枚舉。當編譯器操作遞歸枚舉時必須插入間接尋址層。你可以在聲明枚舉 case 之前使用indirect 關鍵字來明確表示它是遞歸的。我們使用一個加減運算來當範例:

你可以再 case 之前加上一個 indirect 表示他是遞歸的

enum Calculation {
    case number(Int)
    indirect case add(Calculation,Calculation)
    indirect case sub(Calculation,Calculation)
}

或者你可以將 indirect 寫在 enum 前面表示整個 case 都是可以遞歸的

indirect enum Calculation {
    case number(Int)
    case add(Calculation,Calculation)
    case sub(Calculation,Calculation)
}

在上面的例子中,我們儲存三種數學運算表達式:單一的數字,有兩個兩個表達式的加法,以及有兩個表達式的乘法。我們兩個 case (add 和 mul) 擁有同樣是數學表達式的關聯值——這些關聯值讓嵌套表達式成為可能。由於數據是內嵌的,用來儲存數據的枚舉同樣需要支持內嵌——這就是說枚舉需要遞歸。下邊的程式碼創建了 ( 5 + 2 ) * 2 遞歸枚舉:

let five = Calculation.number(5)
let two = Calculation.number(2)
let sum = Calculation.add(five , two)
let product = Calculation.mul(sum, Calculation.number(2))

遞歸函數是一種操作遞歸結構數據的簡單方法。

https://ithelp.ithome.com.tw/upload/images/20180102/20107701QTYqmLGWxs.png

這個函數通過直接返回關聯值來判斷普通數字。它通過衡量表達式 a 和 b 判斷是加法還是乘法,然後對它們加或者乘。


補充

剛入門的時候,常常好奇為什麼 A 方法不能用另外一種 B 方法做,其中 enum 就是一個例子,因為當時學習的時候覺得他與 array 大同小異,但是使用起來才會發現它們的是有差別的,這邊我使用一個簡單的例子,建立方法不同,但內容是相同的資料。

https://ithelp.ithome.com.tw/upload/images/20180102/20107701TlnMqwPVOy.png

這邊我們看得出來,想調用 array 中的 apple 值必須了解他的序在哪才能調用,但是 enum 中我們可以使用一個點 (.) 加上名稱 appple 就能調用它,不僅不用知道他的排序,還能直接使用名稱調用它。
而且假如我們今天要將它存入一個變數中,array 無法限制它的存取值,因為只要類型為 String 的他都能夠儲存。而使用 enum 時,我們可以將他限制在 appple,banana,cat 三種情況中。


上一篇
Day-13 Swift 語法(9) - 不同的 Closures 方式
下一篇
Day-15 Swift 語法(11) - Class 與 Struct 的愛恨交織
系列文
Swift 菜鳥的30天30

尚未有邦友留言

立即登入留言