Swift學習:枚舉(Enumerations)

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

  若是你熟悉 C 語言,你會知道在 C 語言中,枚舉會爲一組整型值分配相關聯的名稱。swift 中的枚舉更加靈活,沒必要給每個枚舉成員提供一個值。若是給枚舉成員提供了一個值(稱爲「原始」值),則改值的類型能夠是 字符串、字符或是一個整型值或浮點數。swift

  此外,枚舉成員能夠指定任意類型的關聯值存儲到枚舉成員中,就像其餘語言中的聯合體(unions) 和變體(variants)。你能夠在一個枚舉中定義一組相關的枚舉成員,每個枚舉成員均可以有適當類型的關聯值。安全

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

  枚舉語法less

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

  enum SomeEnumeration {lua

    // 枚舉定義放在這裏code

  }遞歸

  下面是用枚舉表示指南針四個方向的例子:ip

  enum CompassPoint {

    case north

    case south

    case east

    case west

  }

  枚舉中定義的值(如 north\south\east\west) 是這個枚舉的成員值(或成員)。你可使用 case 關鍵字來定義一個新的枚舉成員的值。

  注意:

  與 C 和 Objective-C 不一樣,swift 的枚舉成員在被建立時不會被賦予一個默認的整型值。在上面的 CompassPoint 例子中,north\south\east 和 west 不會被隱式賦值爲 0、一、2 和 3。相反,這些枚舉成員自己就是完備的值,這些值的類型是已經明肯定義好的 CompassPoint 類型。

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

  enum Planet {

    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune

  }

  每一個枚舉定義一個全新的類型。像 swift 中其餘類型同樣,它們的名字(例如 CompassPoint 和 Planet)應該以一個大寫字母開頭。給枚舉類型起一個單數名字而不是複數名字,以便於更加容易理解:

  var directionToHead = CompassPoint.west

  dirertionToHead 的類型能夠在它被 CompassPoint 的某個值初始化時推斷出來。一旦 directionToHead 被聲明爲 CompassPoint 類型,你可使用更簡短的點語法將其設置爲另外一個 CompassPoint 的值:

  directionToHead = .east

  當 directionToHead 的類型已知時,再次爲其賦值能夠省略枚舉類型名。在使用具備顯式類型的枚舉值時,這種寫法讓代碼具備更好的可讀性。

  使用 Switch 語句來匹配枚舉值

  你可使用 switch 語句來匹配單個枚舉值:

  directionToHead = .south 

  switch directionToHead {

    case .north:

      print("")

    case .south:

      print("")

    case .east:

      print("")

    case .west:

      print("")

   }

  正如在控制流中介紹的那樣,在判斷一個枚舉類型的時候,switch 語句必須窮舉全部狀況。若是忽略了 .west 這種狀況,上面那段代碼將沒法經過編譯,由於它沒有考慮到 CompassPoint 的所有成員。強制窮舉確保了枚舉成員不會被意外遺漏。

  當不須要匹配每一個枚舉成員的時候,能夠提供一個 default 分支來涵蓋全部未明確處理的枚舉成員:

  let somePlanet = Planet.earth

  switch somePlanet {

    case .earth:

      pritn("")

    default:

      print("Not a safe place for humans")

  }

  關聯值

  上一小節的例子演示瞭如何定義和分類枚舉的成員。能夠爲 Planet.earth 設置一個變量或常量,並在賦值以後查看這個值。然而,有時候可以把其餘類型的關聯值和成員值一塊兒存儲起來會頗有用。這能讓你連同成員值一塊兒存儲額外的自定義信息,而且你每次在代碼中使用該枚舉成員時,還能夠修改這個關聯值。

  你能夠定義 swift 枚舉來存儲任意類型的關聯值,若是須要的話,每一個枚舉成員的關聯值類型能夠各不相同。枚舉的這種特性跟其餘語言中的可識別聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)類似。

  例如,假設一個庫存跟蹤系統須要利用兩種不一樣類型的條形碼來跟蹤商品。

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

  enum Barcode {

    case upc(Int, Int, Int, Int)

    case qrCode(String)

  }

  以上代碼能夠這麼理解:

  「定義一個名爲 Barcode 的枚舉類型,它的一個成員值是具備 (Int, Int, Int, Int)類型關聯的 upc, 另外一個成員值是具備 String 類型關聯值的 qrCode。」

  這個定義不提供任何 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 和其整數關聯值被新的 Barcode.qrCode 和其字符串關聯值所代替。Barcode 類型的常量和變量能夠存儲一個 .upc 或者一個 .qrCode (連同它們的關聯值),可是在同一時間只能存儲這兩個值中的一個。

  像先前那樣,可使用一個 switch 語句來檢查不一樣的條形碼類型。然而,這一次,關聯值能夠被提取出來做爲 switch 語句的一部分。你能夠在 switch 的 case 分支中提供每一個關聯值做爲一個常量(用 let 前綴)或者做爲一個變量(用 var 前綴)來使用:

  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).")

  }

  若是一個枚舉成員的全部關聯值都被提取爲常量,或者都被提取爲變量,爲了簡潔,你能夠只在成員名稱前標註一個 let 或 var:

    switch productBarcode {

      case let .upc(numberSystem, manufacturer, product, check):

        ...

      case let .qrCode(productCode):

        ...

    }

  原始值

  在關聯值小節的條形碼例子中,演示瞭如何聲明存儲不一樣類型關聯值的枚舉成員。做爲關聯值的替代選擇,枚舉成員能夠被默認值(稱爲原始值)預填充,這些原始值的類型必須相同。

  這是一個使用 ASCII 碼做爲原始值的枚舉:

  enum ASCIIControlCharacter: Character {

    case tab = "\t"

    case lineFeed = "\n"

    case carriageReturn = "\r"

  }

  枚舉類型 ASCIIControlCharacter 的原始值類型被定義爲 Character, 並設置了一些比較常見的 ASCII 控制字符。

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

  注意:

  原始值和關聯值是不一樣的。原始值是在定義枚舉時預先填充的值,像上述三個 ASCII 碼。對於一個特定的枚舉成員,它的原始值始終不變。關聯值是建立一個基於枚舉成員的常量或變量時才設置的值,枚舉成員的關聯值能夠變化。

  原始值的隱式賦值

  在使用原始值爲整數或者字符串類型的枚舉時,不須要顯式的爲每個枚舉成員設置原始值,swift 將會自動爲你賦值。

  例如,當使用整數做爲原始值時,隱式賦值的值依次遞增 1。若是第一個枚舉成員沒有設置原始值,其原始值將爲 0.

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

  enum Plant: Int {

    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune

  }

  在上面的例子中,Plant.mercury 的顯式原始值爲 1 ,Planet.venus 的隱式原始值爲 2,依次類推。

  當使用字符串做爲枚舉類型的原始值時,每一個枚舉成員的隱式原始值爲該枚舉成員的名稱。

  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」

  使用原始值初始化枚舉實例

  若是在定義枚舉類型的時候使用了原始值,那麼將會自動得到一個初始化方法,這個方法接收一個叫作 rawValue 的參數,參數類型即爲原始值類型,返回值則是枚舉成員或 nil。你可使用這個初始化方法來建立一個新的枚舉實例。

  這個例子利用原始值 7 建立了枚舉成員 uranus:

  let possiblePlanet = Planet(rawValue: 7)

  // possiblePlanet 類型爲 Planet? 值爲 Planet.uranus

  然而,並不是全部 Int 值均可以找到一個匹配的行星。所以,原始值構造器老是返回一個可選的枚舉成員。在上面的例子中,possiblePlanet 是 Planet? 類型,或者說是 「可選的 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 \(positionFind)")

  }

  這個例子使用了可選綁定(optional binding),試圖經過原始值 11 來訪問一個行星。if let somePlanet = Planet(rawValue: 11) 語句建立了一個可選 Planet,若是可選 Planet 的值存在,就會賦值給 somePlanet。在這個例子中,沒法檢索到位置爲 11 的行星,因此 else 分支被執行。

  遞歸枚舉

  遞歸枚舉是一種枚舉類型,它有一個或多個枚舉成員使用該枚舉類型的實例做爲關聯值。使用遞歸枚舉時,編譯器會插入一個間接層。你能夠在枚舉成員前加上 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 的關聯值也是算術表達式--這些關聯值使得嵌套表達式成爲可能。例如,表達式 (5 + 4)* 2 , 乘號右邊是一個數字,左邊則是另外一個表達式。由於數據是嵌套的。於是用來存儲數據的枚舉類型也須要支持這種嵌套--這意味着枚舉類型須要支持遞歸。下面的代碼展現了使用 ArithmeticExpression 這個遞歸枚舉建立表達式(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))

  要操做具備遞歸性質的數據結構,使用遞歸函數是一種直接了當的方式。例如,下面是一個對算術表達式求值的函數:

  func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {

      case let .number(value):

        return value

      case let .addition(left, right):

        return evaluate(left) + evaluate(right)

      case let .multiplication(left, right):

        return evaluate(left) * evaluate(right)

      }

    }

  print(evaluate(product))

  // 打印 18

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

END

相關文章
相關標籤/搜索