天天看點

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

swift中的枚舉可以設定關聯值和原始值,關聯值用于執行個體化的時候指派,原始值用于預設枚舉成員

關聯值(Associated Values)

上一小節的例子示範了如何定義和分類枚舉的成員。你可以為

Planet.Earth

設定一個常量或者變量,并在指派之後檢視這個值。然而,有時候能夠把其他類型的關聯值和成員值一起存儲起來會很有用。這能讓你連同成員值一起存儲額外的自定義資訊,并且你每次在代碼中使用該枚舉成員時,還可以修改這個關聯值。

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

例如,假設一個庫存跟蹤系統需要利用兩種不同類型的條形碼來跟蹤商品。有些商品上标有使用

9

的數字的 UPC-A 格式的一維條形碼。每一個條形碼都有一個代表“數字系統”的數字,該數字後接五位代表“廠商代碼”的數字,接下來是五位代表“産品代碼”的數字。最後一個數字是“檢查”位,用來驗證代碼是否被正确掃描:

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

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

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

這便于庫存跟蹤系統用包含四個整型值的元組存儲 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)
}
           

上面定義的枚舉類型可以存儲三種算術表達式:純數字、兩個表達式相加、兩個表達式相乘。枚舉成員

Addition

Multiplication

的關聯值也是算術表達式——這些關聯值使得嵌套表達式成為可能。

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

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()
let four = ArithmeticExpression.Number()
let sum = ArithmeticExpression.Addition(five, four)
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number())
print(evaluate(product))
// 輸出 "18"
           

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

結構體和枚舉是值類型

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

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

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

請看下面這個示例,其使用了前一個示例中

Resolution

結構體:

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

在以上示例中,聲明了一個名為

hd

的常量,其值為一個初始化為全高清視訊分辨率(1920 像素寬,1080 像素高)的

Resolution

執行個體。

然後示例中又聲明了一個名為

cinema

的變量,其值為之前聲明的

hd

。因為

Resolution

是一個結構體,是以

cinema

的值其實是

hd

的一個拷貝副本,而不是

hd

本身。盡管

hd

cinema

有着相同的寬(width)和高(height)屬性,但是在背景中,它們是兩個完全不同的執行個體。

類是引用類型

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

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

VideoMode

類:

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

以上示例中,聲明了一個名為

tenEighty

的常量,其引用了一個

VideoMode

類的新執行個體。在之前的示例中,這個視訊模式(video mode)被賦予了HD分辨率(1920*1080)的一個拷貝(

hd

)。同時設定為交錯(interlaced),命名為

“1080i”

。最後,其幀率是

25.0

幀每秒。

然後,

tenEighty

 被賦予名為

alsoTenEighty

的新常量,同時對

alsoTenEighty

的幀率進行修改:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 
           

因為類是引用類型,是以

tenEight

alsoTenEight

實際上引用的是相同的

VideoMode

執行個體。換句話說,它們是同一個執行個體的兩種叫法。

下面,通過檢視

tenEighty

frameRate

屬性,我們會發現它正确的顯示了基本

VideoMode

執行個體的新幀率,其值為

30.0

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

需要注意的是

tenEighty

alsoTenEighty

被聲明為常量((constants)而不是變量。然而你依然可以改變

tenEighty.frameRate

alsoTenEighty.frameRate

,因為這兩個常量本身不會改變。它們并不

存儲

這個

VideoMode

執行個體,在背景僅僅是對

VideoMode

執行個體的引用。是以,改變的是被引用的基礎

VideoMode

frameRate

參數,而不改變常量的值。

恒等運算符

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

如果能夠判定兩個常量或者變量是否引用同一個類執行個體将會很有幫助。為了達到這個目的,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)的常量或者變量引用同一個類執行個體。
  • “等于”表示兩個執行個體的值“相等”或“相同”,判定時要遵照類設計者定義定義的評判标準,是以相比于“相等”,這是一種更加合适的叫法。

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

繼續閱讀