eclipse匯入web專案出現紅叉
列舉
列舉為一組相關的值定義了一個共同的型別,使你可以在你的程式碼中以型別安全的方式來使用這些值。
如果你熟悉 C 語言,你會知道在 C 語言中,列舉會為一組整型值分配相關聯的名稱。Swift 中的列舉更加靈活,不必給每一個列舉成員提供一個值。如果給列舉成員提供一個值(稱為原始值),則該值的型別可以是字串、字元,或是一個整型值或浮點數。
此外,列舉成員可以指定任意型別的關聯值儲存到列舉成員中,就像其他語言中的聯合體(unions)和變體(variants)。你可以在一個列舉中定義一組相關的列舉成員,每一個列舉成員都可以有適當型別的關聯值。
在 Swift 中,列舉型別是一等(first-class)型別。它們採用了很多在傳統上只被類(class)所支援的特性,例如計算屬性(computed properties),用於提供列舉值的附加資訊,例項方法(instance methods),用於提供和列舉值相關聯的功能。列舉也可以定義建構函式(initializers)來提供一個初始值;可以在原始實現的基礎上擴充套件它們的功能;還可以遵循協議(protocols)來提供標準的功能。
想了解更多相關資訊,請參見 屬性,方法,構造過程,擴充套件 和 協議。
列舉語法
使用 enum
關鍵詞來建立列舉並且把它們的整個定義放在一對大括號內:
enum SomeEnumeration {
// 列舉定義放在這裡
}
下面是用列舉表示指南針四個方向的例子:
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
,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”
。”
……以此類推。
正如在 控制流 中介紹的那樣,在判斷一個列舉型別的值時,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”
列舉成員的遍歷
在一些情況下,你會需要得到一個包含列舉所有成員的集合。可以通過如下程式碼實現:
令列舉遵循 CaseIterable
協議。Swift 會生成一個 allCases
屬性,用於表示一個包含列舉所有成員的集合。下面是一個例子:
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// 列印“3 beverages available”
在前面的例子中,通過 Beverage.allCases
可以訪問到包含 Beverage
列舉所有成員的集合。allCases
的使用方法和其它一般集合一樣——集合中的元素是列舉型別的例項,所以在上面的情況中,這些元素是 Beverage
值。在前面的例子中,統計了總共有多少個列舉成員。而在下面的例子中,則使用 for
迴圈來遍歷所有列舉成員。
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
在前面的例子中,使用的語法表明這個列舉遵循 CaseIterable 協議。想了解 protocols 相關資訊,請參見 協議。
關聯值
列舉語法那一小節的例子演示瞭如何定義和分類列舉的成員。你可以為 Planet.earth
設定一個常量或者變數,並在賦值之後檢視這個值。然而,有時候把其他型別的值和成員值一起儲存起來會很有用。這額外的資訊稱為關聯值,並且你每次在程式碼中使用該列舉成員時,還可以修改這個關聯值。
你可以定義 Swift 列舉來儲存任意型別的關聯值,如果需要的話,每個列舉成員的關聯值型別可以各不相同。列舉的這種特性跟其他語言中的可識別聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)相似。
例如,假設一個庫存跟蹤系統需要利用兩種不同型別的條形碼來跟蹤商品。有些商品上標有使用 0
到 9
的數字的 UPC 格式的一維條形碼。每一個條形碼都有一個代表數字系統的數字,該數字後接五位代表廠商程式碼的數字,接下來是五位代表“產品程式碼”的數字。最後一個數字是檢查位,用來驗證程式碼是否被正確掃描:
其他商品上標有 QR 碼格式的二維碼,它可以使用任何 ISO 8859-1 字元,並且可以編碼一個最多擁有 2,953 個字元的字串:
這便於庫存跟蹤系統用包含四個整型值的元組儲存 UPC 碼,以及用任意長度的字串儲存 QR 碼。
在 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 語句的一部分。你可以在 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).")
}
// 列印“QR code: ABCDEFGHIJKLMNOP.”
如果一個列舉成員的所有關聯值都被提取為常量,或者都被提取為變數,為了簡潔,你可以只在成員名稱前標註一個 let
或者 var
:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// 列印“QR code: ABCDEFGHIJKLMNOP.”
原始值
在 關聯值 小節的條形碼例子中,演示瞭如何宣告儲存不同型別關聯值的列舉成員。作為關聯值的替代選擇,列舉成員可以被預設值(稱為原始值)預填充,這些原始值的型別必須相同。
這是一個使用 ASCII 碼作為原始值的列舉:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
列舉型別 ASCIIControlCharacter
的原始值型別被定義為 Character
,並設定了一些比較常見的 ASCII 控制字元。Character
的描述詳見 字串和字元 部分。
原始值可以是字串、字元,或者任意整型值或浮點型值。每個原始值在列舉宣告中必須是唯一的。
注意
原始值和關聯值是不同的。原始值是在定義列舉時被預先填充的值,像上述三個 ASCII 碼。對於一個特定的列舉成員,它的原始值始終不變。關聯值是建立一個基於列舉成員的常量或變數時才設定的值,列舉成員的關聯值可以變化。
原始值的隱式賦值
在使用原始值為整數或者字串型別的列舉時,不需要顯式地為每一個列舉成員設定原始值,Swift 將會自動為你賦值。
例如,當使用整數作為原始值時,隱式賦值的值依次遞增 1
。如果第一個列舉成員沒有設定原始值,其原始值將為 0
。
下面的列舉是對之前 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"
使用原始值初始化列舉例項
如果在定義列舉型別的時候使用了原始值,那麼將會自動獲得一個初始化方法,這個方法接收一個叫做 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 \(positionToFind)")
}
// 列印“There isn't a planet at position 11”
這個例子使用了可選繫結(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”
該函式如果遇到純數字,就直接返回該數字的值。如果遇到的是加法或乘法運算,則分別計算左邊表示式和右邊表示式的值,然後相加或相乘。