Swift5.1學習隨筆之泛型Generics

泛型函數
  • 泛型可以將類型參數化,提高代碼復用率,減少代碼量

看個例子??,交換兩個變量的值:定義一個函數,參數為inout參數,內部元組實現交換兩個外部傳入的變量

func swapValue(_ a: inout Int, _ b: inout Int) {
    (a, b) = (b, a)
}
var n1 = 10
var n2 = 20
swapValue(&n1, &n2)
print(n1, n2) // 20 10

上面的swapValue函數的參數只支持Int類型,用局限性,如果要支持其他類型的參數,就必須寫多個函數,很麻煩,嚴重增加代碼量,這時候就需要參數支持泛型,能傳入任意類型的參數。

這里的T代表一種不確定的類型:(這里的T隨便自定義,通常用T,代表type)

func swapValue<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}
var n1 = "abc"
var n2 = "123"
swapValue(&n1, &n2)
print(n1, n2) //123 abc
var n1 = true
var n2 = false
swapValue(&n1, &n2)
print(n1, n2) //false true
var n1 = 10.1
var n2 = 90.23
swapValue(&n1, &n2)
print(n1, n2) //90.23 10.1

其實swift內部就已經提供了一個swap交換的方法,我們點進去看下就可以知道我們自定義的swapValue跟它的定義方法是一樣的:

/// Exchanges the values of the two arguments.
///
/// The two arguments must not alias each other. To swap two elements of a
/// mutable collection, use the `swapAt(_:_:)` method of that collection
/// instead of this function.
///
/// - Parameters:
///   - a: The first value to swap.
///   - b: The second value to swap.
@inlinable public func swap<T>(_ a: inout T, _ b: inout T)
  • 泛型函數賦值給變量
var n1 = 10
var n2 = 20
var fn: (inout Int, inout Int) -> () = swapValue
fn(&n1, &n2)
func test<T1, T2>(_ t1: T1, _ t2: T2) { }
var fn: (Int, Double) -> () = test

泛型類型

結構體、類、枚舉也可以增加泛型定義

舉個例子,利用數組實現一個棧,類Stack后面加個<E>,代表支持任意類型的元素操作

class Stack<E> {
    var elements = [E]()
    func push(_ element: E) { elements.append(element) }
    func pop() -> E { elements.removeLast() }
    func top() -> E { elements.last! }
    func size() -> Int { elements.count }
}
var intStack = Stack<Int>() //支持Int的棧
var stringStack = Stack<String>() //支持String的棧
var anyStack = Stack<Any>() //支持Any的棧

存在繼承的寫法:

class SubStack<E>: Stack<E> {
    
}

struct中自定義需要加上mutating

struct Stack<E> {
    var elements = [E]()
    mutating func push(_ element: E) { elements.append(element) }
    mutating func pop() -> E { elements.removeLast() }
    func top() -> E { elements.last! }
    func size() -> Int { elements.count }
}

枚舉中定義泛型

enum Score<T> {
    case point(T)
    case grade(String)
}
let score0 = Score.point(10)
let score1 = Score<Int>.point(10)
let score2 = Score<Double>.point(10.1)

let score3 = Score<Int>.grade("C")

關聯類型 Associated Type
  • 給協議中用到的類型定義一個占位名稱

定義一個協議Stackable,聲明幾個棧的常規操作,如果要實現一個棧,遵循Stackable協議,內部自己去實現

protocol Stackable {
    associatedtype Element //關聯類型(可以理解泛型)
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

協議中實現泛型無法像類、結構體、枚舉那樣,只能用associatedtype

protocol Stackable<Element> { }
//報錯:Protocols do not allow generic parameters; use associated types instead
  • 協議中可以擁有多個關聯類型
protocol Stackable {
    associatedtype Element //關聯類型
    associatedtype Element2 //關聯類型
    associatedtype Element3 //關聯類型
}

具體應用中的實現:
聲明一個類StringStack,遵循Stackable協議,設置真實關聯類型為String類型。

class StringStack: Stackable {
    //給關聯類型設置真實類型
    typealias Element = String
    //...
}

因為編譯器會自動處理,其實可以不用寫明typealias Element = String,只要實現協議方法的時候寫明是String就行,編譯器會自動關聯到需要的類型

class StringStack: Stackable {
    //給關聯類型設置真實類型
//    typealias Element = String
    var elements = [String]()
    func push(_ element: String) { elements.append(element) }
    func pop() -> String { elements.removeLast() }
    func top() -> String { elements.last! }
    func size() -> Int { elements.count }
}

想要更靈活的實現一個泛型的類,可以在定義class的時候設置泛型:

class Stack<E>: Stackable {
//    typealias Element = E
    var elements = [E]()
    func push(_ element: E) { elements.append(element) }
    func pop() -> E { elements.removeLast() }
    func top() -> E { elements.last! }
    func size() -> Int { elements.count }
}

類型約束

下面的代碼中,對于泛型T設置了一些約束,參數泛型T必須是Person類或者子類且遵循了Runnable協議的。

protocol Runnable { }
class Person { }
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

下面的代碼中,協議里面的關聯類型遵守Equatable協議,那么遵守Stackable協議的類的泛型也必須遵守Equatable協議。

protocol Stackable {
    associatedtype Element: Equatable
}
class Stack<E: Equatable>: Stackable {
    typealias Element = E
}

下面的代碼中,S1S2必須遵守Stackable協議,且S1.Element == S2.Element,同時S1.Element要遵守Hashable協議。

func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool
    where S1.Element == S2.Element, S1.Element: Hashable {
    return false
}
var s1 = Stack<Int>()
var s2 = Stack<Int>()
var s3 = Stack<String>()
equal(s1, s2) //編譯正確
equal(s1, s3) //編譯錯誤:Global function 'equal' requires the types 'Stack<Int>.Element' (aka 'Int') and 'Stack<String>.Element' (aka 'String') be equivalent

協議類型的注意點

定義Runnable協議,定義類PersonCar遵守Runnable協議。
可以看到變量r1Person對象,r2Car對象。
但是編譯器并不知道r1r2具體哪個是Person,哪個是Car,因為get返回值統一是Runnable類型

protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }

func get(_ type: Int) -> Runnable {
    if type == 0 {
        return Person()
    }
    return Car()
}

var r1 = get(0)
var r2 = get(1)

接下來給協議Runnable引入關聯類型associatedtype:聲明關聯類型Speed,有一個speed只讀的計算屬性

protocol Runnable {
    associatedtype Speed
    var speed: Speed { get }
}
class Person: Runnable {
    var speed: Double { 0.0 }
}
class Car: Runnable {
    var speed: Int { 0 }
}
  • 用泛型解決問題
func get<T: Runnable>(_ type: Int) -> T {
    if type == 0 {
        return Person() as! T
    }
    return Car() as! T
}
var r1: Person = get(0)
var r2: Car = get(1)
  • 用不透明類型some解決問題
func get(_ type: Int) -> some Runnable {
    return Car()
}

some限制只能返回一種類型,因此編譯器能確定最后返回的是什么類型,編譯就能通過。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。