Latest web development tutorials

Swift 泛型

Swift 提供了泛型讓你寫出靈活且可重用的函數和類型。

Swift 標準庫是通過泛型代碼構建出來的。

Swift 的數組和字典類型都是泛型集。

你可以創建一個Int數組,也可創建一個String數組,或者甚至於可以是任何其他Swift 的類型數據數組。

以下實例是一個非泛型函數exchange 用來交換兩個Int 值:

// 定义一个交换两个变量的函数
func exchange(inout a: Int, inout b: Int) {
    let temp = a
    a = b
    b = temp
}

var numb1 = 100
var numb2 = 200

print("交换前数据: \(numb1) 和 \(numb2)")
exchange(&numb1, b: &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")

以上程序執行輸出結果為:

交换前数据: 100 和 200
交换后数据: 200 和 100

泛型函數可以訪問任何類型,如Int 或String。

以下實例是一個泛型函數exchange 用來交換兩個Int 和String 值:

func exchange<T>(inout a: T, inout b: T) {
    let temp = a
    a = b
    b = temp
}

var numb1 = 100
var numb2 = 200

print("交换前数据:  \(numb1) 和 \(numb2)")
exchange(&numb1, b: &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")

var str1 = "A"
var str2 = "B"

print("交换前数据:  \(str1) 和 \(str2)")
exchange(&str1, b: &str2)
print("交换后数据: \(str1) 和 \(str2)")

以上程序執行輸出結果為:

交换前数据:  100 和 200
交换后数据: 200 和 100
交换前数据:  A 和 B
交换后数据: B 和 A

這個函數的泛型版本使用了佔位類型名字(通常此情況下用字母T來表示)來代替實際類型名(如Int、String或Double)。 佔位類型名沒有提示T必須是什麼類型,但是它提示了a和b必須是同一類型T,而不管T表示什麼類型。 只有exchange(_:_:)函數在每次調用時所傳入的實際類型才能決定T所代表的類型。

另外一個不同之處在於這個泛型函數名後面跟著的佔位類型名字(T)是用尖括號括起來的( )。 這個尖括號告訴Swift 那個T是exchange(_:_:)函數所定義的一個類型。 因為T是一個佔位命名類型,Swift 不會去查找命名為T的實際類型。


泛型類型

Swift 允許你定義你自己的泛型類型。

自定義類、結構體和枚舉作用於任何類型,如同Array和Dictionary的用法。

struct TOS<T> {
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}

var tos = TOS<String>()
tos.push("Swift")
print(tos.items)

tos.push("泛型")
print(tos.items)

tos.push("类型参数")
print(tos.items)

tos.push("类型参数名")
print(tos.items)


let deletetos = tos.pop()

以上程序執行輸出結果為:

["Swift"]
["Swift", "泛型"]
["Swift", "泛型", "类型参数"]
["Swift", "泛型", "类型参数", "类型参数名"]

擴展泛型類型

當你擴展一個泛型類型的時候(使用extension 關鍵字),你並不需要在擴展的定義中提供類型參數列表。 更加方便的是,原始類型定義中聲明的類型參數列表在擴展裡是可以使用的,並且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用。

實例

下面的例子擴展了泛型TOS 類型,為其添加了一個名為first 的只讀計算屬性,它將會返回當前棧頂端的元素而不會將其從棧中移除。

struct TOS<T> {
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}

var tos = TOS<String>()
tos.push("Swift")
print(tos.items)

tos.push("泛型")
    print(tos.items)
    
    tos.push("类型参数")
    print(tos.items)
    
    tos.push("类型参数名")
    print(tos.items)

// 扩展泛型 TOS 类型
extension TOS {
    var first: T? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

if let first = tos.first {
    print("栈顶部项:\(first)")
}

以上程序執行輸出結果為:

["Swift"]
["Swift", "泛型"]
["Swift", "泛型", "类型参数"]
["Swift", "泛型", "类型参数", "类型参数名"]
栈顶部项:类型参数名

類型約束

類型約束指定了一個必須繼承自指定類的類型參數,或者遵循一個特定的協議或協議構成。

類型約束語法

你可以寫一個在一個類型參數名後面的類型約束,通過冒號分割,來作為類型參數鏈的一部分。 這種作用於泛型函數的類型約束的基礎語法如下所示(和泛型類型的語法相同):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是函数主体
}

實例

// 函数可以作用于查找一字符串数组中的某个字符串
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findStringIndex(strings, "llama") {
    print("llama 的下标索引值为 \(foundIndex)")
}

以上程序執行輸出結果為:

llama 的下标索引值为 2

關聯類型實例

Swift 中使用typealias 關鍵字來設置關聯類型。

定義一個協議時,有的時候聲明一個或多個關聯類型作為協議定義的一部分是非常有用的。

protocol Container {
    // 定义了一个ItemType关联类型
    typealias ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

// 遵循Container协议的泛型TOS类型
struct TOS<T>: Container {
    // original Stack<T> implementation
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
    
    // conformance to the Container protocol
    mutating func append(item: T) {
        self.push(item)
    }
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> T {
        return items[i]
    }
}

var tos = TOS<String>()
tos.push("Swift")
print(tos.items)

tos.push("泛型")
print(tos.items)

tos.push("参数类型")
print(tos.items)

tos.push("类型参数名")
print(tos.items)

以上程序執行輸出結果為:

["Swift"]
["Swift", "泛型"]
["Swift", "泛型", "参数类型"]
["Swift", "泛型", "参数类型", "类型参数名"]

Where 語句

類型約束能夠確保類型符合泛型函數或類的定義約束。

你可以在參數列表中通過where語句定義參數的約束。

你可以寫一個where語句,緊跟在在類型參數列表後面,where語句後跟一個或者多個針對關聯類型的約束,以及(或)一個或多個類型和關聯類型間的等價(equality)關係。

實例

下面的例子定義了一個名為allItemsMatch的泛型函數,用來檢查兩個Container實例是否包含相同順序的相同元素。

如果所有的元素能夠匹配,那麼返回一個為true的Boolean值,反之則為false。

protocol Container {
    typealias ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

struct Stack<T>: Container {
    // original Stack<T> implementation
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
    
    // conformance to the Container protocol
    mutating func append(item: T) {
        self.push(item)
    }
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> T {
        return items[i]
    }
}

func allItemsMatch<
    C1: Container, C2: Container
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
    (someContainer: C1, anotherContainer: C2) -> Bool {
        // 检查两个Container的元素个数是否相同
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // 检查两个Container相应位置的元素彼此是否相等
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        // 匹配所有项,返回 true
        return true
}

var tos = Stack<String>()
tos.push("Swift")
print(tos.items)

tos.push("泛型")
print(tos.items)

tos.push("Where 语句")
print(tos.items)

var eos = ["Swift", "泛型", "Where 语句"]
print(eos)

以上程序執行輸出結果為:

["Swift"]
["Swift", "泛型"]
["Swift", "泛型", "Where 语句"]
["Swift", "泛型", "Where 语句"]