1. 程式人生 > 實用技巧 >eclipse匯入web專案出現紅叉

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
}

列舉中定義的值(如 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”。”

……以此類推。

正如在 控制流 中介紹的那樣,在判斷一個列舉型別的值時,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)相似。

例如,假設一個庫存跟蹤系統需要利用兩種不同型別的條形碼來跟蹤商品。有些商品上標有使用 09 的數字的 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。”

這個定義不提供任何 IntString 型別的關聯值,它只是定義了,當 Barcode 常量和變數等於 Barcode.upcBarcode.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 的顯式原始值為 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"

使用原始值初始化列舉例項

如果在定義列舉型別的時候使用了原始值,那麼將會自動獲得一個初始化方法,這個方法接收一個叫做 rawValue 的引數,引數型別即為原始值型別,返回值則是列舉成員或 nil。你可以使用這個初始化方法來建立一個新的列舉例項。

這個例子利用原始值 7 建立了列舉成員 Uranus

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 型別為 Planet? 值為 Planet.uranus

然而,並非所有 Int 值都可以找到一個匹配的行星。因此,原始值構造器總是返回一個可選的列舉成員。在上面的例子中,possiblePlanetPlanet? 型別,或者說“可選的 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)
}

上面定義的列舉型別可以儲存三種算術表示式:純數字、兩個表示式相加、兩個表示式相乘。列舉成員 additionmultiplication 的關聯值也是算術表示式——這些關聯值使得巢狀表示式成為可能。例如,表示式 (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”

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