swift 列舉詳解
列舉為一系相關聯的值定義了一個公共的組型別.同時能夠讓你在程式設計的時候在型別安全的情況下去使用這些值。 如果你對C語言很熟悉,你肯定知道在C語言中列舉型別就是一系列具有被指定有關聯名稱的的整數值.但在Swift中列舉型別就更加靈活了,並且你不必給列舉型別中的每個成員都賦值。如果把一個值(假設值為”raw”)提供給所有的列舉型別當中的成員,那麼這個值可以是一個字串,一個字元,一個整數或者說是一個浮點數. 作為選擇,列舉中的成員可以被特別指定為任何不同於其他成員的可以儲存的型別,就像是其他語言當中的聯合或者變體。你能夠將一些相關聯的成員定義成一個公共的集合作為列舉的一部分,每個部分都有不同的集合型別並且使用了恰當的型別。
這裡有一個例子,定義了一個包含四個方向的羅盤:
enum CompassPoint {
case North
case Sourth
case East
case West
}
列舉中定義的變數(像上例中North, South, East, West)是列舉的成員變數(或者說成員).關鍵字case是用來標明這一行將要定義一個新的成員變數
注意:與C或者Objective-C不同的是,在Swift語言中列舉型別的成員初始的時候不會被預設賦值成整數值,在CompassPoint這個例子中,North, South, East, West預設不會隱式的等於0,1,2,3。取而代之的是不同的列舉成員將要用什麼型別以及賦值什麼值都是可以自己控制的,可以在定義CompassPoint這個列舉的時候指定.
多個成員還可以用一行來定義,他們之間用逗號分割:
enum Plant{
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
每個列舉的定義都是定義了一個全新的型別,就像Swfit中的其他的型別一樣,列舉的名稱(像上邊的CompassPoint, Plant)應該是以一個大寫字母開頭,讓他們是單數型別而不是複數型別,從而讓他們可以不言而喻:
var directionToHead = CompassPoint.Sourth
當directionToHead在初始化過程中被賦值成CompassPoint中的某一個可能的值的時候,它的型別就可以被推測出來來了。一旦directionToHead被宣告成是CompassPoint型別,那麼你就可以簡短的使用逗號表示式來給它賦值成其他的CompassPoint當中的值了:
directionToHead = .East
directionToHead的型別是已知的了,所以你可以忽略它的型別來給他賦值了。這樣使得在使用顯示型別的列舉值時程式碼具有很高的可讀性。
使用Switch語句來匹配列舉值
你可以通過switch語句來訪問單獨的某個列舉值:
directionToHead = .South
switch directionToHead {
case .North:
print("Lots of planet have a north")
case .Sourth:
print("Watch out for penguins")
case .East:
print("Where the sun rises")
case .West:
print("Where the skies are blue")
}
你可以這樣閱讀這段程式碼:考慮directionToHead的值,如果它等於.North那麼就輸出”Lost of planets have a north”,如果它等於.South,那麼就輸出”Watch out for penguins”。
就和在控制流程那一章所講,一個switch語句被用到判斷列舉值的時候,必須要包括所有的列舉成員。假設.West被忽略了,將會導致編譯出錯,因為它沒有考慮到列舉的所有的列舉成員,我們需要全面性的確保列舉的所有成員不被忽略掉.(這個地方我已經驗證過了,建議大家也驗證下)
如果給考慮每個列舉的成員不合適,你可以提供一個default來覆蓋其他沒有明確處理的成員:
directionToHead = .East
switch directionToHead {
case .North:
print("Lots of planet have a north")
case .Sourth:
print("Watch out for penguins")
case .West:
print("Where the skies are blue")
default:
print("Not a safe place for humans")
}
關聯值
在上一節的示例中顯示了一個列舉的成員是如何在自己的權利界定(和型別)的值。你可以設定一個常量或變數的值為Planet.Earth ,然後檢查這個值。然而,如果在保留成員值的同時能夠儲存其它型別的關聯值將會變得更有意義。這使您能夠在儲存成員值的同時儲存額外的自定義資訊,並且允許每次你在程式碼中使用這些成員值的時候改變這些關聯值。在Swift中當你定義一個列舉成員的時候,你可以給他關聯任何的型別,而且如果需要的話每個成員可以有不同的關聯型別。列舉型別的這個特性和其他語言當中的辨別聯合,標記聯合或者變體很像。
舉個例子,設想一個庫存跟蹤系統想要通過兩種不同的條形碼來跟蹤產品。一些產品用UPC-A格式的一維條形碼標識的,使用0到9的數字。每個條形碼當中有一個標識“數字系統“的數字,然後是10個“識別符號”數字,最後邊一個用來做“檢查”的數字,以確保這個條形碼被正確的掃描識別:
另一些產品是用QR編碼格式的二維碼標識的,這種條形碼可以使用任何ISO 8859-1的字元而且最大可以編碼一個2953字元長度的字串:
在Swift語言中,一個可以定義兩個格式的產品條形碼的列舉看起來是這樣的:
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
你可以這樣閱讀這段程式碼:定義了一個叫做Barcode的列舉型別,它可以有一個UPCA成員,這個成員關聯了一個包含三個整型數值的元組,同時這個列舉型別還有一個QRCode成員,關聯了一個字串。
這個定義不會生成任何的整型或者字串值,他只是定義了當一個不可變變數或者變數等於Barcode.UPCA或者Barcode.QRCode的時候它被關聯的值的型別
這樣一來可以用任意其中一個型別來生成一個新的條形碼了:
var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
這個例子生成了一個新的變數叫做productBarcode,這個變數被關聯了一個Barcode.UPCA型別的元組,這個元組的值為(8, 8590951226, 3),被提供做“標識”的值以及下劃線分割了85909_51226,這樣做是為了更好的被以條形碼讀出來。
同樣的產品還可以被賦值為另一個條形碼型別:
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
在這一點上,原來的Barcode.UPCA以及關聯到的整型值被一個新型別Barcode.QRCode以及與其關聯的字串給替換了。Barcode型別的不可變變數以及可變變數可以儲存.UPCA或者.QRCode型別(同時還有與其相關聯的值),但是每次都只能儲存這兩個型別當中的一個。
同樣,這兩個不同的型別可以用Switch語句來做檢查。同時,在switch語句中他們相關聯的值也可以被獲取到。你可以把關聯的值當做不可變變數(用let來開頭),或者可變變數(用var開頭)以在switch的控制體當中使用。
switch productBarcode {
case .UPCA(let numberSystem, let identifier, let check):
print("UPC-A with value of \(numberSystem),\(identifier),\(check)")
case.QRCode(let productCode):
print("QR code with value of \(productCode)")
}
如果一個列舉成員關聯的所有值都被當做不可變變數或者可變變數來使用,那麼你可以在成員名稱之前只放一個let或者var來達到目的,簡要示例:
switch productBarcode {
case let .UPCA(numberSystem, identifier, check):
print("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case let .QRCode(productCode):
print("QR code with value of \(productCode).")
}
// 輸出 "QR code with value of ABCDEFGHIJKLMNOP.”
原始值
在上一節當中條形碼的例子展示了一個列舉型別的成員怎麼宣告他們可以儲存不同型別的關聯值。不同於關聯值,列舉型別的成員還可以預設定預設值(我們叫他原始值),這些值的型別是相同的。
這裡有一個列舉成員儲存一個ASCII值的例子:
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
這裡定義了一個原始值為字元型別的列舉型別ASCIIControlCharacter,而且包含了一些我們常用的控制字元。關於字元型別,你可以在字串和字元那個章節找到更多的描述。
請注意,原始值與關聯值不同。原始值應該是在你定義列舉的程式碼中被設定為預填充值的,就像上述三個ASCII碼。對於一個特定的列舉成員的原始值始終是相同的。關聯值是當你建立一個基於列舉的成員的新的常量或變數的時候設定的,並且每次都可以是不同的。
原始值可以是字串,字元或者其他任何的整型或者浮點型等數字型別。每個原始值在他屬的列舉型別定義的時候都應該是不同的。如果原始值是整數型別,那麼當其他列舉成員沒有設定原始值的時候,他們的原始值是這個整型原始值自增長設定的。
下邊這個列舉型別是對之前的Plant列舉型別的改良,新的列舉型別有一個整型的原始值來標識他們在距離太陽的順序:
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
自增長的意思就是Planet.Venus的原始值會被設定成2,以此類推。
可以通過列舉成員的rawValue方法來獲取他的原始值:
let earthOrder = Planet.Earth.rawValue
print(earthOrder)
//結果是3
使用列舉成員的(rawValue:)方法來嘗試通過一個原始值來尋找他所對應的列舉成員。下面這個例子介紹了怎麼通過Uranus的原始值7找到Uranus的:
let possiblePlanet = Planet(rawValue: 7)
print(possiblePlanet)
//Optional(swiftDemo.Planet.Uranus)說明是可選擇型別,內容可以為nil
let possiblePlanet = Planet(rawValue: 100)
//輸出結果為 nil
示例如下:
enum ArithmeticExpression {
case Number(Int)
indirect case Addition(ArithmeticExpression,ArithmeticExpression)
indirect case Multiplication(ArithmeticExpression,ArithmeticExpression)
}
當然你也可以這樣寫:
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression,ArithmeticExpression)
case Multiplication(ArithmeticExpression,ArithmeticExpression)
}
下面我們進行以下測試:
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