Swift-范型

【返回目錄】



范型(Generics)

  • 范型可以將類型參數(shù)化,提高代碼復(fù)用率,減少代碼量
func swapValues<T>(_ a: inout T, _ b: inout T)  {
    return (a, b) = (a, b)
}
var n1 = 10
var n2 = 20
swapValues(&n1, &n2)
func swapValues<T1, T2, T3>(_ a: T1, _ b: T2, _ c: T3)  {
    print("");
}
struct Date { var year = 0, month = 0, day = 0 }
var d1 = Date(year: 2023, month: 4, day: 24)
var d2 = Date(year: 1949, month: 10, day: 1)
swapValues(&d1, &d2)
  • 范型函數(shù)賦值給變量
func test<T1, T2>(_ v1: T1, _ v2: T2) { }
var fn2:(Int, Double) -> () = test

var fn:(inout Int, inout Int) -> () = swapValues




范型類型

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>()
var stringStack = Stack<String>()
var anyStack = Stack<Any>()

創(chuàng)建范型類型必須確定是什么類型Stack<Int>(),不像范型函數(shù)那樣傳參指定類型。

  • init

帶有初始化器的范型類型中可以去掉<Type>,因為初始化傳參的時候已經(jīng)匹配到類型。

class Stack<E> {
    var elements = [E]()
    init(firstElement: E) {
        elements.append(firstElement)
    }
}
var stack = Stack(firstElement: 10)


如果定義為<Double>,與初始化傳參類型不一樣,stack對象也是Double類型,因為Int是可以轉(zhuǎn)換為Int的。

var stack = Stack<Double>(firstElement: 10)//也是<Double>




  • 繼承關(guān)系
class SubStack<E> : Stack<E> {
    
}


  • 結(jié)構(gòu)體
struct StructStack<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
    }
}

在結(jié)構(gòu)體實例方法里,修改結(jié)構(gòu)體的內(nèi)存,就需要mutating修飾

如果不加會報錯:Cannot use mutating member on immutable value: 'self' is immutable

  • 枚舉
enum Score<T> {
    case point(T)
    case grade(String)
}
let score1 = Score<Int>.point(100)
let score2 = Score.point(89)
let score3 = Score.point(99.9)
let score4 = Score<Int>.grade("A")

枚舉的范型類型要明確<Type>,否則報錯:Generic parameter 'T' could not be inferred。因為<Type>不明確,無法分配內(nèi)存。即使case用不上也要寫上Score<Int>



關(guān)聯(lián)類型(Associated Type)

  • 關(guān)聯(lián)類型的作用:給協(xié)議中用到的類型定義一個占位名稱。
  • 協(xié)議中可以擁有多個關(guān)聯(lián)類型
protocol Stackable {
    associatedtype Element //關(guān)聯(lián)類型
    mutating func push(_ element: Element)
    mutating func pop(_ element: Element)
    func top() -> Element
    func size() -> Int
}

協(xié)議中想實現(xiàn)范型就用關(guān)聯(lián)類型(associatedtype),代表協(xié)議里面有一個范型類型。

  • 使用指定類型實現(xiàn)
class StringStack : Stackable {
    //給關(guān)聯(lián)類型設(shè)定真是類型
    //typealias Element = String //這句代碼可以省略
    var elements = [String]()
    //element: String 編譯器自動識別
    func push(_ element: String) { elements.append(element) }
    func pop(_ element: String) { elements.removeLast() }
    func top() -> String { elements.last! }
    func size() -> Int { elements.count }
}
var ss = StringStack()
ss.push("jack")
ss.push("Rose")

typealias Element = String這句代碼可以省略不寫,在實現(xiàn)方法中設(shè)定(_ element: String) 編譯器自動識別。

  • 使用范型類型實現(xiàn)
class Stack<E> : Stackable {
    //typealias Element = E
    var elements = [E]()
    func push(_ element: E) { elements.append(element) }
    func pop(_ element: E) { elements.removeLast() }
    func top() -> E { elements.last! }
    func size() -> Int { elements.count }
}




類型約束

  • 類和協(xié)議的約束
    • 要求對范型類型進(jìn)行約束,必須遵守Runnable協(xié)議,
    • 并且是Person類型或者Person子類的類型
protocol Runnable { }
class Person { }
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
    return (a, b) = (b, a)
}


  • 協(xié)議約束
    • 要求關(guān)聯(lián)類型必須遵守Equatable協(xié)議
    • 在實現(xiàn)的范型類型也必須遵守
protocol Stackable {
    associatedtype Element : Equatable //可比較、等價
}

// 必須遵守<E: Equatable>協(xié)議
class Stack<E: Equatable> : Stackable {
    typealias Element = E
}

E是范型,也要對E做約束,遵守Equatable協(xié)議,否則編譯器報錯:Type 'Stack<E>' does not conform to protocol 'Stackable'



  • where條件約束
    • 要求遵守Stackable協(xié)議
    • S1和S2是同一種類型
    • S1是默認(rèn)遵守Hashable協(xié)議的
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>()

Int默認(rèn)遵守Equatable、Hashable協(xié)議,條件符合。

equal(s1, s2)


s1關(guān)聯(lián)值是Int,s3關(guān)聯(lián)值是String,不是同種類型,條件不符合.

equal(s1, s3)

編譯器報錯:Global function 'equal' requires the types 'Stack<Int>.Element' (aka 'Int') and 'Stack<String>.Element' (aka 'String') be equivalent



不透明類型(Opaque Type)

具有不透明返回類型的函數(shù)或方法會隱藏返回值的類型信息。函數(shù)不再提供具體的類型作為返回類型,而是根據(jù)它支持的協(xié)議來描述返回值。

some

  • 使用some關(guān)鍵字聲明一個不透明類型
  • some要求只能返回一個類型
  • some:除了用在返回值類型上,一般還可以用在屬性上
protocol Runnable {
    associatedtype Speed
}
class Dog : Runnable {
    typealias Speed = Double
    func eat() { }
}

class Person {
    var pet: some Runnable {
        return Dog()
    }
}

var p = Person()
p.pet




1.注意點:下面代碼中r1、r2都是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,在編譯時期編譯器認(rèn)為r1、r2是Runnable類型,只有在運行時根據(jù)傳參才決定返回的類型

2.如果協(xié)議中有associatedtype

protocol Runnable {
    associatedtype Speed
    var speed : Speed { get }
}
class Person : Runnable {
    var speed: Double { 0.0 }
}
class Car : Runnable {
    var speed: Double { 0 }
    var run: Int { 0 }
}


編譯的時候發(fā)現(xiàn)Runnable的關(guān)聯(lián)類型associatedtype Speed是不確定的,所以就產(chǎn)生問題。

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

編譯器報錯:Protocol 'RunnableTow' can only be used as a generic constraint because it has Self or associated type requirements

原因:編譯階段只能確定r1是Runnable類型(遵守Runnable),但是它里面關(guān)聯(lián)的Speed具體類型無法確定,所以編譯器就報錯了。


  • 解決方案1:通過范型
func get<T: Runnable>(_ type: Int) -> T {
    if type == 0 {
        return Person() as! T  // 強制轉(zhuǎn)換“as! T”有一定的風(fēng)險
    }
    return Car()  as! T
}
var person: Person = get(0)
var car: Car = get(1)


  • 解決方案2:通過不透明類型

如果只想返回一個遵守某種協(xié)議的對象,但是這個對象的具體類型不想讓別知道,在別人使用的時候只能看到某種協(xié)議暴露的接口。

func get(_ type: Int) -> some Runnable {
    return Car()
}
var p1 = get(0) 
p1.speed 
var p2 = get(1)

代碼中p1也遵守Runnable協(xié)議,但只能訪問到Runnable協(xié)議的Speed()方法,不能調(diào)用自己的run()方法。



可選項的本質(zhì)

  • 可選項的本質(zhì)就是范型枚舉類型
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
    public init(_ some: Wrapped)
    // ......
}


下面定義是等價的

  • .some()
var age: Int? = 10
var age0: Optional<Int> = Optional<Int>.some(10)
var age1: Optional = .some(10)
var age2 = Optional.some(10)
var age3 = Optional(10)
age = nil
age3 = .none
  • .none
var age: Int? = nil // nil
var age0 = Optional<Int>.none
var age1: Optional<Int> = .none


  • 多重可選項??
var a1 : Int? = 10 //Optional(10)
var a2: Int?? = a1 // Optional(Optional(10))
a2 = nil
var age1 = Optional.some(Optional.some(10)) // Optional(Optional(10))
age1 = .none // nil
var age2: Optional<Optional> = .some(.some(10))
age2 = .none
var age: Int?? = 10 // Optional(Optional(10))
var a : Optional<Optional> = 10 // Optional(Optional(10))



  • 可選項的用法:switch
var age: Int? = .some(20)

switch age {
case let value?:
    print("some:\(value)")
case nil:
    print("none")
}

switch age {
case let .some(value):
    print("some:\(value)")
case nil:
    print("none")
}






【上一篇】:錯誤處理
【下一篇】:高級運算符




最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容