6.Swift初探-枚舉、類和結構體

swift中的枚舉可以設置關聯值和原始值,關聯值用於實例化的時候賦值,原始值用於預設枚舉成員

關聯值(Associated Values)

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

你可以定義 Swift 枚舉來存儲任意類型的關聯值,如果需要的話,每個枚舉成員的關聯值類型可以各不相同。枚舉的這種特性跟其他語言中的可識別聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)相似。

例如,假設一個庫存跟蹤系統需要利用兩種不同類型的條形碼來跟蹤商品。有些商品上標有使用09的數字的 UPC-A 格式的一維條形碼。每一個條形碼都有一個代表“數字系統”的數字,該數字後接五位代表“廠商代碼”的數字,接下來是五位代表“產品代碼”的數字。最後一個數字是“檢查”位,用來驗證代碼是否被正確掃描:

其他商品上標有 QR 碼格式的二維碼,它可以使用任何 ISO 8859-1 字符,並且可以編碼一個最多擁有 2,953 個字符的字符串:

這便於庫存跟蹤系統用包含四個整型值的元組存儲 UPC-A 碼,以及用任意長度的字符串儲存 QR 碼。

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

enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
}

原始值(Raw Values)

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

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

enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

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

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

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


遞歸枚舉(Recursive Enumerations)

當各種可能的情況可以被窮舉時,非常適合使用枚舉進行數據建模,例如可以用枚舉來表示用於簡單整數運算的操作符。這些操作符讓你可以將簡單的算術表達式,例如整數5,結合爲更爲複雜的表達式,例如5 + 4

算術表達式的一個重要特性是,表達式可以嵌套使用。例如,表達式(5 + 4) * 2,乘號右邊是一個數字,左邊則是另一個表達式。因爲數據是嵌套的,因而用來存儲數據的枚舉類型也需要支持這種嵌套——這意味着枚舉類型需要支持遞歸。

遞歸枚舉(recursive enumeration)是一種枚舉類型,它有一個或多個枚舉成員使用該枚舉類型的實例作爲關聯值。使用遞歸枚舉時,編譯器會插入一個間接層。你可以在枚舉成員前加上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的關聯值也是算術表達式——這些關聯值使得嵌套表達式成爲可能。

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

func evaluate(expression: ArithmeticExpression) -> Int {
    switch expression {
    case .Number(let value):
        return value
    case .Addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .Multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    }
}

// 計算 (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))
print(evaluate(product))
// 輸出 "18"

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


結構體和枚舉是值類型

值類型被賦予給一個變量、常量或者本身被傳遞給一個函數的時候,實際上操作的是其的拷貝

在之前的章節中,我們已經大量使用了值類型。實際上,在 Swift 中,所有的基本類型:整數(Integer)、浮點數(floating-point)、布爾值(Boolean)、字符串(string)、數組(array)和字典(dictionary),都是值類型,並且都是以結構體的形式在後臺所實現。

在 Swift 中,所有的結構體和枚舉類型都是值類型。這意味着它們的實例,以及實例中所包含的任何值類型屬性,在代碼中傳遞的時候都會被複制。

請看下面這個示例,其使用了前一個示例中Resolution結構體:

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

在以上示例中,聲明瞭一個名爲hd的常量,其值爲一個初始化爲全高清視頻分辨率(1920 像素寬,1080 像素高)的Resolution實例。

然後示例中又聲明瞭一個名爲cinema的變量,其值爲之前聲明的hd。因爲Resolution是一個結構體,所以cinema的值其實是hd的一個拷貝副本,而不是hd本身。儘管hdcinema有着相同的寬(width)和高(height)屬性,但是在後臺中,它們是兩個完全不同的實例。

類是引用類型

與值類型不同,引用類型在被賦予到一個變量、常量或者被傳遞到一個函數時,操作的是引用,其並不是拷貝。因此,引用的是已存在的實例本身而不是其拷貝。

請看下面這個示例,其使用了之前定義的VideoMode類:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

以上示例中,聲明瞭一個名爲tenEighty的常量,其引用了一個VideoMode類的新實例。在之前的示例中,這個視頻模式(video mode)被賦予了HD分辨率(1920*1080)的一個拷貝(hd)。同時設置爲交錯(interlaced),命名爲“1080i”。最後,其幀率是25.0幀每秒。

然後,tenEighty 被賦予名爲alsoTenEighty的新常量,同時對alsoTenEighty的幀率進行修改:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

因爲類是引用類型,所以tenEightalsoTenEight實際上引用的是相同的VideoMode實例。換句話說,它們是同一個實例的兩種叫法。

下面,通過查看tenEightyframeRate屬性,我們會發現它正確的顯示了基本VideoMode實例的新幀率,其值爲30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 輸出 "The frameRate property of theEighty is now 30.0"

需要注意的是tenEightyalsoTenEighty被聲明爲常量((constants)而不是變量。然而你依然可以改變tenEighty.frameRatealsoTenEighty.frameRate,因爲這兩個常量本身不會改變。它們並不存儲這個VideoMode實例,在後臺僅僅是對VideoMode實例的引用。所以,改變的是被引用的基礎VideoModeframeRate參數,而不改變常量的值。

恆等運算符

因爲類是引用類型,有可能有多個常量和變量在後臺同時引用某一個類實例。(對於結構體和枚舉來說,這並不成立。因爲它們作爲值類型,在被賦予到常量、變量或者傳遞到函數時,其值總是會被拷貝。)

如果能夠判定兩個常量或者變量是否引用同一個類實例將會很有幫助。爲了達到這個目的,Swift 內建了兩個恆等運算符:

  • 等價於 ( === )
  • 不等價於 ( !== )

以下是運用這兩個運算符檢測兩個常量或者變量是否引用同一個實例:

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//輸出 "tenEighty and alsoTenEighty refer to the same Resolution instance."

請注意“等價於"(用三個等號表示,===) 與“等於"(用兩個等號表示,==)的不同:

  • “等價於”表示兩個類類型(class type)的常量或者變量引用同一個類實例。
  • “等於”表示兩個實例的值“相等”或“相同”,判定時要遵照類設計者定義定義的評判標準,因此相比於“相等”,這是一種更加合適的叫法。

當你在定義你的自定義類和結構體的時候,你有義務來決定判定兩個實例“相等”的標準。在章節等價操作符中將會詳細介紹實現自定義“等於”和“不等於”運算符的流程。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章