從零學(xué)習(xí)Swift 12:可選項(xiàng)本質(zhì),高級(jí)運(yùn)算符, Extension 擴(kuò)展

總結(jié)

可選項(xiàng)是Swift的一大特色,那么可選項(xiàng)的本質(zhì)是什么呢?

比如下面代碼:

可選項(xiàng)

我們從打印結(jié)果可以看到,ageOptional類型,其實(shí)可選項(xiàng)本質(zhì)就是Optional類型.?只是語(yǔ)法糖的一種寫(xiě)法.我們看看Optional類型的本質(zhì)是什么:

Optional 本質(zhì)

可以看到Optional的本質(zhì)就是一個(gè)枚舉類型,有兩個(gè)成員值和一個(gè)初始化方法.既然知道可選項(xiàng)的本質(zhì)是枚舉類型,那上面的代碼可以這樣寫(xiě):

還可以簡(jiǎn)化:

簡(jiǎn)化寫(xiě)法
可選項(xiàng)類型綁定

我么知道可選項(xiàng)和if組合使用可以使用可選項(xiàng)綁定來(lái)判斷可選項(xiàng)中是否有值:

可選項(xiàng)綁定

可選項(xiàng)和if組合時(shí),如果可選項(xiàng)包裝有值,那么就進(jìn)行解包,并把值賦值給a.

其實(shí)可選項(xiàng)還可以和switch語(yǔ)句組合,也可以使用可選項(xiàng)綁定:

可選項(xiàng)和 switch 組合

可選項(xiàng)和switch組合時(shí)有兩個(gè)警告,我們先解決第二個(gè)警告:Case is already handled by previous patterns; consider removing it.這個(gè)警告的意思是說(shuō),上一個(gè)case已經(jīng)包含了所有情況,當(dāng)前的case永遠(yuǎn)也不會(huì)執(zhí)行.其實(shí)上面代碼的可選項(xiàng)綁定時(shí)無(wú)條件綁定.不管可選項(xiàng)有沒(méi)有值都會(huì)賦值給a,所以下面代碼就永遠(yuǎn)也不會(huì)執(zhí)行.那么我們要想實(shí)現(xiàn)和if那種效果,有值的時(shí)候解包再賦值,沒(méi)有值的時(shí)候不解包呢?只需要在a后面添加一個(gè)問(wèn)號(hào) ?就可以了:

這樣一來(lái),第一個(gè)警告也消除了.第一個(gè)警告就是直接打印可選項(xiàng)的時(shí)候的警告.現(xiàn)在使用?如果可選項(xiàng)有值就會(huì)解包,所以警告消除.

雙重可選項(xiàng)

既然知道了可選項(xiàng)的本質(zhì)就是枚舉類型:有值是some,nilnone.那么雙重可選項(xiàng)也是一樣的:

雙重可選項(xiàng)
高級(jí)運(yùn)算符
溢出運(yùn)算符 &+,&-,&*

我們知道UInt8的取值范圍是0~255,Int8的取值范圍是-128~127.那么如果經(jīng)過(guò)運(yùn)算后的結(jié)果超出了這個(gè)范圍會(huì)怎么樣呢?

溢出會(huì)發(fā)生運(yùn)行時(shí)錯(cuò)誤

可以看到,如果溢出后會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤.所以Swift提供了溢出運(yùn)算符&+,&-,&*

溢出運(yùn)算符

溢出運(yùn)算符的運(yùn)算是循環(huán)的,比如說(shuō)age的值是255, 加 1后就變成了0.

運(yùn)算符重載

如果我們想為系統(tǒng)原有的運(yùn)算符添加額外的功能,可以對(duì)原有的運(yùn)算符進(jìn)行重載.重載就是函數(shù)名相同,參數(shù)不同.功能不同.

系統(tǒng)的運(yùn)算符的本質(zhì)都是方法:

+

比如說(shuō)+只能對(duì)基本數(shù)據(jù)類型進(jìn)行加法運(yùn)算,如果我們要對(duì)結(jié)構(gòu)體類型進(jìn)行運(yùn)算加法運(yùn)算呢?

對(duì)結(jié)構(gòu)體進(jìn)行加法運(yùn)算

可以看到結(jié)構(gòu)體是不支持 + 運(yùn)算的.

所以我們只能對(duì)+進(jìn)行重載,達(dá)到我們的目的:


struct Point{
    var x: Int
    var y: Int
}

func + (lhs: Point , rhs: Point) -> Point{
    Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 21)
var p3 = p1 + p2
print(p3)

因?yàn)槲覀兪墙o Point 提供的額外的運(yùn)算功能,所以我們應(yīng)該把重載的方法放到 Point 結(jié)構(gòu)體里面:


struct Point{
    var x: Int
    var y: Int
    
    static func + (lhs: Point , rhs: Point) -> Point{
        Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
}


重載+ , - ,+= , -= , 取反, ++ , --


 // +
    static func + (lhs: Point , rhs: Point) -> Point{
        Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
    
    // -
    static func - (lhs: Point , rhs: Point) -> Point{
        Point(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
    }
    
    //取反
    static prefix func - (lhs: inout Point){
        lhs = Point(x: -lhs.x, y: -lhs.y)
    }
    
    // +=
    static func += (lhs: inout Point , rhs: Point){
        lhs = lhs + rhs
    }
    
    
    // -=
    static func -= (lhs: inout Point , rhs: Point){
        lhs = lhs - rhs
    }
    
    
    // ++ 在前
    static prefix func ++ (lhs: inout Point){
        //先運(yùn)算
        lhs += Point(x: 1, y: 1)
    }
    
    // ++ 在后
    static postfix func ++ (lhs: inout Point) -> Point{
        //先保存原來(lái)的
        let p = lhs
        lhs += Point(x: 1, y: 1)
        return p
    }

==運(yùn)算符

如果想要比較兩個(gè)Point對(duì)象是否相等可以使用==運(yùn)算符,但是Swift==運(yùn)算符默認(rèn)不支持結(jié)構(gòu)體類型,所以需要我們重載==運(yùn)算符.

== 運(yùn)算符默認(rèn)不支持結(jié)構(gòu)體

重載==運(yùn)算符有兩步:

  1. 實(shí)現(xiàn) Equatable 協(xié)議
  2. 重載 == 運(yùn)算符
重載 == 運(yùn)算符

上圖可以看到,我們把==運(yùn)算符的重載方法注釋了,也就是說(shuō)并沒(méi)有重載==運(yùn)算符.為什么也可以使用==運(yùn)算符呢?

Swift會(huì)為一下三種情況提供默認(rèn)的Equatable實(shí)現(xiàn):
1. 只擁有遵守了 Equatable 協(xié)議的存儲(chǔ)屬性的結(jié)構(gòu)體
2. 只關(guān)聯(lián)了遵守 Equatable 協(xié)議類型的枚舉
3. 沒(méi)有關(guān)聯(lián)值的枚舉

以上三種情況,Swift會(huì)默認(rèn)提供Equatable協(xié)議的實(shí)現(xiàn).Point的兩個(gè)存儲(chǔ)屬性都是Int類型,而Int類型顯然是遵守了Equatable協(xié)議的,所以不用重載==.

沒(méi)有關(guān)聯(lián)類型的枚舉:


enum Season{
    case spring
    case summer
    case autumn
    case winter
}

var season1 = Season.spring
var season2 = Season.winter

print(season1 == season2)

關(guān)聯(lián)類型實(shí)現(xiàn)了Equatable協(xié)議:

enum Season: Equatable{
    case spring(Int,Int)
    case summer
    case autumn
    case winter
}

var season1 = Season.spring(10, 20)
var season2 = Season.winter

print(season1 == season2)

如果是class類型,必須實(shí)現(xiàn)Equatable協(xié)議,并且重載==方法:


class Person: Equatable{
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    static func == (p1: Person, p2: Person) -> Bool{
        p1.age == p2.age
    }
}

var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")

print(person1 == person2)


上面Person類可以不實(shí)現(xiàn)Equatable協(xié)議,==方法同樣可以正常使用,但是還是建議實(shí)現(xiàn)Equatable協(xié)議,因?yàn)檫@樣做有兩個(gè)好處:

1. 一眼就可以看出 Person 類是可比較的
2. 適用于泛型限定

比如說(shuō)我們寫(xiě)一個(gè)比較傳進(jìn)來(lái)的泛型參數(shù)是否相等的方法:

可以看到編譯報(bào)錯(cuò),因?yàn)閰?shù)是泛型,傳入的參數(shù)不一定是可比較的.所以必須對(duì)泛型添加限定條件,要求必須是實(shí)現(xiàn)了Equatale協(xié)議的類型.如果我們想讓Person實(shí)例能適用這個(gè)方法,就必須讓Person實(shí)現(xiàn)Equatale協(xié)議:

如果我們實(shí)現(xiàn)了Equatale協(xié)議,重載了==方法,等價(jià)于重載了!=運(yùn)算符,我們可以直接使用!=運(yùn)算符.

如果要比較兩個(gè)引用類型引用的是不是同一個(gè)對(duì)象,要使用===運(yùn)算符:


var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")

print(person1 === person2) // false

==是判斷兩個(gè)對(duì)象是否相等,如果我們相比較大小呢?

如果要比較兩個(gè)對(duì)象大小,要遵守Comparable協(xié)議,然后重載> , >= , < , <=運(yùn)算符.

Comparable里面就聲明了這四個(gè)方法:

自定義運(yùn)算符

如果現(xiàn)有的運(yùn)算符還是不能滿足我們的需求,我們可以根據(jù)自己的需求,自定義運(yùn)算符.

自定義運(yùn)算符時(shí)需要注意兩點(diǎn):
1. 要指明是前綴運(yùn)算符,后綴運(yùn)算符,還是中綴運(yùn)算符
2. 如果是中綴運(yùn)算符,要設(shè)定優(yōu)先級(jí)

下面我們分別自定義前綴運(yùn)算符,后綴運(yùn)算符,后綴運(yùn)算符:


//優(yōu)先級(jí)組
precedencegroup plus3Prece{
    //結(jié)合性
    associativity: none //left / right / none
    //比誰(shuí)的優(yōu)先級(jí)高
    higherThan: AdditionPrecedence
    //比誰(shuí)的優(yōu)先級(jí)低
    lowerThan: MultiplicationPrecedence
    //在可選鏈操作中,和賦值運(yùn)算符一樣的優(yōu)先級(jí)
    assignment: true
}
//聲明
prefix operator +++
postfix operator +++
infix operator +++ : plus3Prece

//前綴運(yùn)算符
struct Point{
    var x: Int
    var y: Int
    
    //前綴+++
    static prefix func +++ (p: inout Point) -> Point{
        p = Point(x: p.x + 2, y: p.y + 2)
        return p
    }
    
    //后綴+++
    static postfix func +++ (p: inout Point) -> Point{
        let tempPoint = p
        p = Point(x: p.x + 2, y: p.y + 2)
        return tempPoint
    }
    
    //中綴
    static func +++ (p1: Point, p2: Point) -> Point{
        let p = Point(x: (p1.x + p2.x) * 2, y: (p1.y + p2.y) * 2)
        return p
    }
    
}

?
優(yōu)先級(jí)組precedencegroup需要介紹一下:
associativity:組合型,left表示從左到右計(jì)算.right表示從右到左計(jì)算.none表示沒(méi)有組合型,也就是說(shuō)不能1 + 2 + 3這樣運(yùn)算.

higherThan:表示比哪個(gè)優(yōu)先級(jí)高.

lowerThan:表示比哪個(gè)優(yōu)先級(jí)低.

assignment:表示在可選鏈操作中和賦值運(yùn)算符有著同樣的優(yōu)先級(jí).

assignment:可能有些抽象,我們回憶一下可選鏈中的賦值運(yùn)算符.

可選鏈中的賦值運(yùn)算符

assignment和可選鏈中的賦值運(yùn)算符一樣:

assignment 作用

另外higherThan , lowerThan后面跟著運(yùn)算符的名稱,不是瞎寫(xiě)的.運(yùn)算符的名稱可以去先面這個(gè)網(wǎng)址去找:
https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations?changes=latest_minor

擴(kuò)展 Extension

擴(kuò)展類似于 OC 中的分類.擴(kuò)展可以為類,結(jié)構(gòu)體,枚舉,協(xié)議添加新的功能.

使用擴(kuò)展的注意點(diǎn):
1. 不能覆蓋原有的功能,OC的分類可以,swift擴(kuò)展不可以
2. 不能添加存儲(chǔ)屬性,因?yàn)榇鎯?chǔ)屬性保存在實(shí)例內(nèi)存中.擴(kuò)展不能夠改變能存結(jié)構(gòu)
3. 不能通過(guò)擴(kuò)展來(lái)添加父類,因?yàn)樘砑痈割愐灿锌赡芨淖儍?nèi)存結(jié)構(gòu)
4. 不能添加指定初始化器,重要的初始化器不能通過(guò)擴(kuò)展添加.

Swift 的擴(kuò)展功能很強(qiáng)大,它可以添加方法,計(jì)算屬性,下標(biāo),便捷初始化器,嵌套類型,協(xié)議等等.

擴(kuò)展計(jì)算屬性:


//擴(kuò)展計(jì)算屬性
extension Double{
    var km: Double{ self / 1_000.0 }
    var m: Double{ self }
}

print(128.6.km)

用擴(kuò)展下標(biāo):


//使用擴(kuò)展添加下標(biāo)

extension Array{
    subscript (nulable index: Int) -> Element?{
        if (startIndex ..< endIndex).contains(index){
            return self[index]
        }
        return nil
    }
}


擴(kuò)展嵌套類型:


//擴(kuò)展嵌套類型
extension Int{
    //循環(huán)n次
    func circles(toDo: () -> Void){
        for _ in 0..<self{
            toDo()
        }
    }
    
    //嵌套類型
    enum Kind{
        case positive,zero,negative
    }
    
    var kind: Kind{
        switch self{
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

3.circles {
    print(2)
}

print((-2).kind)

擴(kuò)展協(xié)議
如果一個(gè)類之前沒(méi)有遵守某個(gè)協(xié)議,我們可以通過(guò)擴(kuò)展,讓他遵守協(xié)議:


class Person{}

protocol Runable {
    func run()
}

extension Person: Runable{
    func run() {
        print("run")
    }
}

var person = Person()
person.run()


如果協(xié)議中有初始化器方法,那么遵守了此協(xié)議的類必須把協(xié)議中的初始化器聲明為required必要初始化器,這樣做的目的是為了讓子類都有這個(gè)初始化器:


protocol Runable {
    init(speed: Int)
}

class Person: Runable{
    required init(speed: Int) {
        print("run")
    }
}

注意: 協(xié)議中不能聲明 required 初始化器

我們知道,如果我們自定義了初始化器,那么系統(tǒng)就不會(huì)再幫我們自動(dòng)生成初始化器.有一種辦法可以既保留系統(tǒng)生成的初始化器,也可以自定義初始化器.

擴(kuò)展可以保留系統(tǒng)生成的初始化器:


struct Point {
    var x: Int = 0
    var y: Int = 0
}

extension Point{
    init(p: Point) {
        self.x = p.x
        self.y = p.y
    }
}

//系統(tǒng)生成的初始化器
var p1 = Point()
var p2 = Point(x: 10)
var p3 = Point(y: 10)
var p4 = Point(x: 10, y: 10)

//通過(guò)擴(kuò)展自定義的初始化器
var p5 = Point(p: p4)

比如說(shuō)現(xiàn)在有這樣一個(gè)需求,給整數(shù)擴(kuò)展一個(gè)方法,判斷整數(shù)是不是偶數(shù).

我們很快會(huì)想到可以這樣做:


extension Int{
    func isEven () -> Bool{
        self % 2 == 0
    }
}

print(4.isEven())

Int類型擴(kuò)展一個(gè)方法.但是這樣做不完善,因?yàn)檎麛?shù)包括有符號(hào)整數(shù)和無(wú)符號(hào)整數(shù).那我們?cè)鯓幽依ㄋ姓麛?shù)呢?我們只要找到整數(shù)的共同點(diǎn)就好了.

swift中所有整數(shù)都遵守了BinaryInteger協(xié)議,所以我么可以直接給BinaryInteger協(xié)議擴(kuò)展一個(gè)方法就好了.

給一個(gè)協(xié)議擴(kuò)展方法,凡是遵守了這個(gè)協(xié)議的類型都有這個(gè)方法:


extension BinaryInteger{
    func isEven () -> Bool{
        self % 2 == 0
    }
}

print(4.isEven())

var uNum: UInt = 10
uNum.isEven()

我們知道協(xié)議中所有的東西都必須實(shí)現(xiàn),如果實(shí)現(xiàn)的不完整就會(huì)編譯不通過(guò):

協(xié)議中的聲明必須全部實(shí)現(xiàn)

有時(shí)候我們可能只想實(shí)現(xiàn)協(xié)議中的某些方法,可不可以做到呢?

可以使用擴(kuò)展給協(xié)議中的方法提供默認(rèn)實(shí)現(xiàn)來(lái)間接實(shí)現(xiàn)協(xié)議的可選效果:

在擴(kuò)展中添加協(xié)議的默認(rèn)實(shí)現(xiàn),達(dá)到間接可選效果

我們還可以通過(guò)擴(kuò)展給協(xié)議添加協(xié)議中沒(méi)有的方法:

通過(guò)擴(kuò)展給協(xié)議添加方法

注意一個(gè)小細(xì)節(jié),如果我們創(chuàng)建Person實(shí)例的時(shí)候,指明是遵守了Runable協(xié)議的類型,會(huì)怎么樣呢?

為什么會(huì)這樣呢?

因?yàn)槲覀冎该髁祟愋褪?code>遵守了 Runable 協(xié)議.而Runable協(xié)議中并沒(méi)有test2方法的聲明.所以Xcode會(huì)認(rèn)為遵守了此協(xié)議的類中可能沒(méi)有test2方法.所以他就會(huì)優(yōu)先從協(xié)議中找這個(gè)方法.

擴(kuò)展中的泛型:

擴(kuò)展中依然可以使用原類型中的泛型:

Array 中的泛型
擴(kuò)展中可以使用原類型中的泛型

符合條件后才擴(kuò)展:


protocol Runable {
    func test1()
}

class Person<C>{
    var pet: C
    init(p: C) {
        self.pet = p
    }
}

//只有當(dāng) Person 中的泛型 C 遵守了 Runable 協(xié)議后
//才會(huì)給 Person 擴(kuò)展協(xié)議
extension Person: Runable where C: Runable{
    func test1() {
        
    }
}

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

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