Swift -- 5.Enum&Optional&運算符

一.Enum

1.Enum基本信息

Swift中通過enum關鍵字來聲明一個枚舉

enum LGEnum {
    case one
    case two
    case three
}

C或者OC中默認受整數支持,也就意味著下面的例子中:A,B,C分別默認代表0,1,2

typedef NS_ENUM(NSInteger, LGEnum) {
    A,
    B,
    C
};

Swift中的枚舉則更加靈活,并且不需要給枚舉中的每一個成員都提供值(所謂“原始”值)。這個值可以是字符串、字符、任意的整數值,或者浮點類型。

enum Color: String {
    case red = "Red"
    case green = "Green"
    case blue = "Blue"
}

enum LGEnum: Double {
    case one = 10.0
    case two = 20.0
    case three = 30.0
    case four = 40.0
}

隱式RawValue分配是建立在Swift類型推斷機制上的

enum Week: Int {
    case mon, tue, wed, thu, fri = 10, sat, sun
}

print(Week.mon.rawValue) //0
print(Week.tue.rawValue) //1
print(Week.wed.rawValue) //2
print(Week.thu.rawValue) //3
print(Week.fri.rawValue) //10
print(Week.sat.rawValue) //11
print(Week.sun.rawValue) //12
  • 原始值是從0,1,2,3開始的,和OC一致
  • 當指定原始值后,后面數據會從指定原始值做累加操作

RawValue類型Int改為String

enum Week: String {
    case mon, tue, wed, thu, fri = "10", sat, sun
}

print(Week.mon.rawValue) //mon
print(Week.tue.rawValue) //tue
print(Week.wed.rawValue) //wed
print(Week.thu.rawValue) //thu
print(Week.fri.rawValue) //10
print(Week.sat.rawValue) //sat
print(Week.sun.rawValue) //sun
  • 編譯器默認給每個枚舉成員分配了一個原始值,也就是枚舉成員字符串

通過SIL分析rawValue原始值默認為枚舉成員字符串的原因

Swift代碼

enum Week: String {
    case mon, tue, wed, thu, fri = "10", sat, sun
}

let m = Week.mon.rawValue

SIL代碼

//關于枚舉的定義
enum Week : String {
  case mon, tue, wed, thu, fri, sat, sun
  init?(rawValue: String) //可失敗的初始化器
  typealias RawValue = String //給String起了個別名RawValue
  var rawValue: String { get } //獲取raValue其實就是獲取rawValue的get方法
}

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1mSSvp                     // id: %2
  %3 = global_addr @$s4main1mSSvp : $*String      // user: %8
  %4 = metatype $@thin Week.Type

  //%5其實就是聲明了一個Week.mon的參數
  %5 = enum $Week, #Week.mon!enumelt              // user: %7
  // function_ref Week.rawValue.getter
  %6 = function_ref @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String // user: %7

  //傳入%5(Week.mon)返回rawValue
  %7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
  store %7 to %3 : $*String                       // id: %8
  %9 = integer_literal $Builtin.Int32, 0          // user: %10
  %10 = struct $Int32 (%9 : $Builtin.Int32)       // user: %11
  return %10 : $Int32                             // id: %11
} // end sil function 'main'


// rawValue的getter方法
// Week.rawValue.getter
sil hidden @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Week):
  debug_value %0 : $Week, let, name "self", argno 1 // id: %1

  //switch_enum,模式匹配。這里邏輯其實很簡單就是,匹配傳入的枚舉值來執行不同的代碼塊
  switch_enum %0 : $Week, case #Week.mon!enumelt: bb1, case #Week.tue!enumelt: bb2, case #Week.wed!enumelt: bb3, case #Week.thu!enumelt: bb4, case #Week.fri!enumelt: bb5, case #Week.sat!enumelt: bb6, case #Week.sun!enumelt: bb7 // id: %2

//bb1代碼塊的邏輯非常簡單,就是創建了一個字符串常量"mon",返回回去。
//其它的代碼塊邏輯與bb1一致
bb1:                                              // Preds: bb0
  %3 = string_literal utf8 "mon"                  // user: %8
  %4 = integer_literal $Builtin.Word, 3           // user: %8
  %5 = integer_literal $Builtin.Int1, -1          // user: %8
  %6 = metatype $@thin String.Type                // user: %8
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %7 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
  %8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
  br bb8(%8 : $String)                            // id: %9

bb2:                                              // Preds: bb0
  %10 = string_literal utf8 "tue"                 // user: %15
  %11 = integer_literal $Builtin.Word, 3          // user: %15
  %12 = integer_literal $Builtin.Int1, -1         // user: %15
  %13 = metatype $@thin String.Type               // user: %15
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %14 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
  %15 = apply %14(%10, %11, %12, %13) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %16
  br bb8(%15 : $String)                           // id: %16

}

關于SIL中的常量mon,也就是case值對應的常量存放在Mach-o的哪個位置

  • 其實這個問題很簡單,常量字符串肯定存放在__TEXT.__cstring里,也就是硬編碼中
__TEXT__cstring

Arm64匯編驗證mon的存放位置

基地址為0x0000000100498000

mon在內存中的地址為0x0000000100498000 + 0x7D48 = 0x10049FD48

進入匯編調試

  • 此時生成字符串傳入的參數就是x0,也就是mon在內存中的地址0x000000010049fd48

2.Enum原始值&枚舉值

//枚舉值(Week類型)
print(Week.mon)
//原始值(String類型)
print(Week.mon.rawValue)
//通過原始值創建枚舉值
print(Week(rawValue: "mon")!)

SIL分析Week.init


  // function_ref _allocateUninitializedArray<A>(_:)
  // _allocateUninitializedArray,分配一個連續的內存空間
  %5 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6

  //StaticString,創建連續的內存空間來存儲case與之匹配的字符串
  %6 = apply %5<StaticString>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7

  //匹配傳進來的rawValue,返回枚舉值
  • 原理就是從一個連續的內存空間存儲case與之匹配的字符串

3.Enum關聯值

enum Shape {
    case circle(radious: Double)
    case rectangle(width: Double, height: Double)
}

let shape = Shape.circle(radious: 10)

switch shape {
    case .circle(let radious):
        print("Circle radious: \(radious)") // Circle radious: 10.0
    case .rectangle(let width, var height): //這里也使用使用var
        height += 10.0
        print("Rectangle width: \(width) height:\(height)")
    default:
        print("other shape")
}

4.Enum的其它用法

//3.可以遵循協議
protocol ShapeProtocol {
    func getPerimeter() -> Double
}

extension Shape: ShapeProtocol {
    func getPerimeter() -> Double {
        switch cirCleshape {
            case .circle(let radious):
                return 2 * Double.pi * radious
            case let .rectangle(width, height):
                return 2 * (width + height)
        }
    }
}

//4.可以使用extension擴展方法
extension Shape {
    func printInfo() {
        switch cirCleshape {
            case .circle(let radious):
                print("Circle radious: \(radious)") // Circle radious: 10.0
            case .rectangle(let width, var height): //這里也使用使用var
                height += 10.0
                print("Rectangle width: \(width) height:\(height)")
        }
    }
}

enum Shape {
    
    case circle(radious: Double)
    case rectangle(width: Double, height: Double)
    
    //2.計算屬性,但是枚舉不能添加屬性
    var area: Double {
        get {
            switch self {
            case .circle(let radious):
                return Double.pi * radious * radious
            case .rectangle(let width, let height):
                return width * height
            }
        }
        set {
            switch self {
            case .circle:
                self = Shape.circle(radious: sqrt(newValue/Double.pi))
            case .rectangle:
                self = Shape.circle(radious: sqrt(newValue/Double.pi))
            }
        }
    }
    
    //1.異變方法,修改自身
    mutating func changeShape(shape: Shape) {
        self = shape
    }
}

5.枚舉的大小

1.No-Payload enums也就是沒有關聯值的枚舉

enum Week {
    case Mon
    case Tue
    case Wed
    case Thu
    case Fri
    case Sat
    case Sun
}

print(MemoryLayout<Week>.size) //1
print(MemoryLayout<Week>.stride) //1
print(MemoryLayout<Week>.alignment) //1
  • 默認以UInt8去存枚舉值,因此枚舉值大小/步長/對齊方式都是1字節
  • 最多能存256個case,如果枚舉值超了,此時的UInt8會升為UInt16,當然在實際開發中也不可能會有那么多枚舉值

通過LLDB讀取枚舉在內存中的值

(lldb) frame variable -L a
0x00000001000080d9: (swiftTest.Week) a = Mon
(lldb) x/b 0x00000001000080d9
0x1000080d9: 0x00
(lldb) frame variable -L b
0x00000001000080da: (swiftTest.Week) b = Tue
(lldb) x/b 0x00000001000080da
0x1000080da: 0x01
(lldb) frame variable -L c
0x00000001000080db: (swiftTest.Week) c = Wed
(lldb) x/b 0x00000001000080db
0x1000080db: 0x02
(lldb) 

2.Single-Playload enums只有一個成員負載

enum LGEnum {
    case one(Bool)
    case two
    case three
    case four
}

/*
 分析為什么掛載了一個負載Bool,還是一字節,和未掛載一樣
 我們知道1字節為8位0b00000000,最大可存256個值(Uint8),也就是0~255
 掛載值Bool在內存中只會占取1位,因此還有剩下的7位去存放我們的枚舉值。(128個)
 因此當枚舉的case小于128時,會用Int8去存。當然如果大于等于128,此時的Int8就會升級成Int16
 */
print(MemoryLayout<LGEnum>.size) //1
print(MemoryLayout<LGEnum>.stride) //1
print(MemoryLayout<LGEnum>.alignment) //1

enum LGEnum_Int {
    case one(Int)
    case two
    case three
    case four
}

/*
 分析為什么掛載了Int,占用了9字節
 因為Int占用8字節,不能與case值占用的1字節共用。因此需要額外的8字節來存儲Int
 所以就是8+1=9字節
 stride步長,也就是對齊后的大小,這里是8字節對齊,因此內存對齊后就是16
 aligment,對齊大小,當前這里就是Int的大小8字節
 */
print(MemoryLayout<LGEnum_Int>.size) //9
print(MemoryLayout<LGEnum_Int>.stride) //16
print(MemoryLayout<LGEnum_Int>.alignment) //8

3.關于Single-Playload enums多個關聯值內存對齊問題

enum Enum_Aligment {
    case one(Int,Int,Bool,Int)
}

print(MemoryLayout<Enum_Aligment>.size) //32
print(MemoryLayout<Enum_Aligment>.stride) //32

enum Enum_AligmentTwo {
    case one(Int,Int,Int,Bool)
}

print(MemoryLayout<Enum_AligmentTwo>.size) //25
print(MemoryLayout<Enum_AligmentTwo>.stride) //32

/*
 這里的步長其實很好理解,也就是基于8字節對齊后的大小
 
 這里的Bool位置不同,影響這size的大小,那么這里其實也好理解,原理和結構體內存對齊是一回事。
 
 對于Enum_Aligment來說,前2個Int開辟了16字節內存大小,來到第三個Bool值,開辟了1字節的內存大小存放Bool。
 當來到第4個Int時,此時的index不能滿足被8整除,因此會向后偏移到能被8整除的下標才能存放Int。
 所以Bool和最后一個Int中間還有7字節的剩余空間來存放case值。所以size為32
 
 對于Enum_AligmentTwo來說,前3個Int都好理解,開辟24字節內存空間存放。然后開辟1字節存放Bool也是沒問題,所以size為25
 */

4.Mutil-Playload enums

enum LGEnum_Multi {
    case one(Bool)
    case two(Bool)
    case three
    case four
    case five
    case six
}

//探究關于內存
//tag Index(低4位)
//tag Value(高4位)

//暫時還沒有在源碼找到關于枚舉內存的規律
let x1 = LGEnum_Multi.one(false) //00
let x2 = LGEnum_Multi.one(true) //01
let x3 = LGEnum_Multi.two(false) //40
let x4 = LGEnum_Multi.two(true) //41
let x5 = LGEnum_Multi.three //80
let x6 = LGEnum_Multi.four //81
let x7 = LGEnum_Multi.five //c0
let x8 = LGEnum_Multi.six //c1

/*
 其實看到x1~x8,大概可以總結一個規律。當然沒有在源碼中找到驗證
 如果掛載了Bool值來說
 那么對于同一個tagValue來說,低4位的0/1來判斷是否有關聯值
 如果沒有掛載的話,就是80到81,也就是上面的x5和x6
 有掛載值的tagValue,相鄰類型是差距了4倍。也就是x1(00)-x3(40)
 沒有掛載值的tagValue,如果前一個內存低4位是0,將低4位加1存入下一個枚舉值。也就是x5(80)-x6(81)
 如果前一個類型內存低4位是1,相鄰類型是差了2倍。也就是x6(81)-x7(c0)
 */

enum LGEnum_Multi_Int {
    case one(Int)
    case two(Int)
    case three
    case four
}


let i1 = LGEnum_Multi_Int.one(10) // 0x10000c118: 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
let i2 = LGEnum_Multi_Int.one(30) // 0x10000c128: 1e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
let i3 = LGEnum_Multi_Int.three // 0x10000c138: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00

/*
 這里的前8字節都是存放的Int,也就是掛載值
 偏移8字節開始存放的是case值,也就是枚舉值。這里可以看到i1和i2都是0,而i3的枚舉值卻是0x2
 在源碼中也沒有找到關于枚舉在內存中是怎么存儲中
 */

5.關于Single-Playload enumsMulti-Playload enums的源碼

Enum.cpp

void
swift::swift_initEnumMetadataSinglePayload(EnumMetadata *self,
                                           EnumLayoutFlags layoutFlags,
                                           const TypeLayout *payloadLayout,
                                           unsigned emptyCases) {
  size_t payloadSize = payloadLayout->size;
  //獲取掛載額外的bits空間
  unsigned payloadNumExtraInhabitants
    = payloadLayout->getNumExtraInhabitants();
  
  //沒有使用的額外存儲空間
  unsigned unusedExtraInhabitants = 0;
  
  // If there are enough extra inhabitants for all of the cases, then the size
  // of the enum is the same as its payload.
  size_t size;
  //如果額外的存儲空間>=case占用的空間
  if (payloadNumExtraInhabitants >= emptyCases) {
    size = payloadSize;
    //沒有使用的額外存儲空間 = 額外的剩余空間 - case占用的空間
    unusedExtraInhabitants = payloadNumExtraInhabitants - emptyCases;
  } else {
    //這里相當于是掛載了一個Int, 8 + 1 = 9
    size = payloadSize + getEnumTagCounts(payloadSize,
                                      emptyCases - payloadNumExtraInhabitants,
                                        1 /*payload case*/).numTagBytes; ;
  }

  auto vwtable = getMutableVWTableForInit(self, layoutFlags);
  
  //關于內存對齊
  size_t align = payloadLayout->flags.getAlignment();
  ...

}

void
swift::swift_initEnumMetadataMultiPayload(EnumMetadata *enumType,
                                     EnumLayoutFlags layoutFlags,
                                     unsigned numPayloads,
                                     const TypeLayout * const *payloadLayouts) {
  // Accumulate the layout requirements of the payloads.
  size_t payloadSize = 0, alignMask = 0;
  bool isPOD = true, isBT = true;
  for (unsigned i = 0; i < numPayloads; ++i) {
    const TypeLayout *payloadLayout = payloadLayouts[i];
    payloadSize
      = std::max(payloadSize, (size_t)payloadLayout->size);
    alignMask |= payloadLayout->flags.getAlignmentMask();
    isPOD &= payloadLayout->flags.isPOD();
    isBT &= payloadLayout->flags.isBitwiseTakable();
  }

6.關于一些常用的LLDB指令

 po                 打印信息
 p                  打印詳細的信息
 bt                 打出堆
 register read      讀取寄存器
 x或memory read     讀取內存段
 x/4g               讀取4段8字節內存段
 x/4w               讀取4段4字節內存段
 p/x                以16進制打印
 p/t                以二進制打印
 p *$0              打印變量($0)的值
 
 字節大小
 b
 byte 1字節
 h
 half word 2字節
 w
 word 4字節
 g
 giant word 8字節 

6.Indirect關鍵字

1.關于indirect

/*
 枚舉是值類型,在編譯時期大小就能確定
 但是下列寫法確定不了枚舉的大小
 
 indirect表達遞歸的枚舉類型,編譯器會在堆上分配內存空間
 */

indirect enum BanaryTree<T> {
    case empty
    case node(left: BanaryTree, right: BanaryTree, value: T)
}


var code = BanaryTree<Int>.node(left: BanaryTree<Int>.empty, right: BanaryTree<Int>.empty, value: 10)

LLDB查看

(lldb) frame variable -L code
0x000000010000c178: (swiftTest.BanaryTree<Int>) code = node {
0x00000001012111a0:   node = {
0x00000001012111a0:     left = empty
0x00000001012111a8:     right = empty
0x00000001012111b0:     value = 10
  }
}
(lldb) x/8g 0x000000010000c178
0x10000c178: 0x0000000101211190 0x0000000100008150
0x10000c188: 0x0000000000000000 0x0000000000000000
0x10000c198: 0x0000000000000000 0x0000000000000000
0x10000c1a8: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000101211190
0x101211190: 0x00000001000080a0 0x0000000000000003
0x1012111a0: 0x0000000000000000 0x0000000000000000
0x1012111b0: 0x000000000000000a 0x00037ff843559c20
0x1012111c0: 0x0000000000000007 0x00007ff841d98240
(lldb) 
  • 當我們讀取0x0000000101211190時,可以看到讀取的值就是一個HeapObject
  • 也就意味著,我們在枚舉上使用遞歸時,加上indirect關鍵字后,編譯器會在堆區申請內存地址來進行存放

SIL分析

SIL中有一個可以看出,調用了alloc_box,也就是執行swift_allocObject

匯編查看是否執行了swift_allocObject

2.關于indirect放到case前面

enum BanaryTree<T> {
    case empty
    indirect case node(left: BanaryTree, right: BanaryTree, value: T)
}
  • 當我們把indirect放到case前面時,只有這個case值使用引用類型,也就是會放到堆空間上存儲

  • 當枚舉使用了indirect關鍵字后,整個枚舉會變為引用類型,也就是在堆空間存儲的

二.Optional

1.認識可選值

class LGTeacher {
    var age: Int?
}

當前的age就稱為可選值

var age: Int? 等同于 var age: Optional<Int>

2.Optional的本質

Optional.swift

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
  case none
  case some(Wrapped)
}
  • 實際上就是一個枚舉中,關聯一個值

根據源碼使用自己的MyOptional

enum MyOptional<Value> {
    case none
    case some(Value)
}

func getOddValue(value:Int) -> MyOptional<Int> {
    if value % 2 == 0 {
        return .some(value)
    }else {
        return .none
    }
}

var arr = [0, 1, 2, 3, 4, 5]

for element in arr {
    let value = getOddValue(value: element)
    switch value {
    case .some(let value):
        print("Odd: \(value)")
    case .none:
        break
    }
}
  • 通過模式匹配拿出當前的值

  • 如果將MyOptional<Int>改為Int?,其它代碼完全不用改變,也能正常使用。通過代碼也印證了Optional的本質

3.可選值的綁定(解包)

如果每一個可選值都用模式匹配的方式來獲取值在書寫代碼上就比較繁瑣,我們還可以使用if let的方式來進行可選值的綁定

if let value = value {
        
 }
  • 這里value類型只有是Int?時才能使用if let解包,如果是MyOptional<Int>,編譯器是不允許這樣做的

當然這里還可以使用guard let來解包。注意:只能在func中使用

guard let value = value else {
  return
}

4.可選鏈

可選鏈其實就是類似于OC向一個nil對象發送消息什么都不會發生。在Swift中借助可選鏈可以達到類似的效果

let str: String? = "abc"
let upperStr = str?.uppercased()
print(upperStr) //Optional("ABC")

var str1: String?
let upperStr1 = str1?.uppercased()
print(upperStr1) //nil

可選鏈的調用

let str: String? = "abc"
let upperStr = str?.uppercased().lowercased()
print(upperStr) //Optional("abc")

對于閉包也適用

var closure: (() -> Void)?
closure?() //closure為nil不執行

5.??運算符(空合并運算符)

a ?? b對可選類型a進行空判斷,如果a包含一個值就進行解包,否則就返回一個默認值b

  • 表達式a必須是Optional類型
  • 默認值b的類型必須與a存儲值的類型保持一致
var age: Int?

print(age ?? 0) //0

age = 10

print(age ?? 0) //10

關于optional源碼中的運算符??

@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
    rethrows -> T? {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

6.隱式解包

一般在開發的時候,如果確定某個變量的值在使用的時候是一直存在的,可以使用隱式解包!


int age: Int!

  • 后續在使用到age時,雖然它是可選值,但是編譯器已經幫我們隱式解包了,也就是告訴編譯器它是肯定有值的。這種隱式解包操作,可以大大減少代碼中的解包操作

  • 最常見的用法就是從StoryboardXib生成的IBOutlet屬性

三.運算符

1.運算符重載

比如實現2個向量的加、減、負運算符

  • 運算符重載必須使用static關鍵字
struct Vector {
    let x: Double
    let y: Double
}

extension Vector {
    //運算符重載必須使用static
    //prefix聲明前綴運算符
    static prefix func - (vector: Vector) -> Vector {
        return Vector(x: -vector.x, y: -vector.y)
    }
    
    static func + (vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x, y: vector0.y + vector1.y)
    }
    
    static func - (vector0: Vector, vector1: Vector) -> Vector {
        return vector0 + -vector1
    }
    
}

let vector0 = Vector(x: 10, y: 10)
let vector1 = Vector(x: 5, y: 5)

print(-vector0) //Vector(x: -10.0, y: -10.0)
print(vector0+vector1) //Vector(x: 15.0, y: 15.0)
print(vector0-vector1) //Vector(x: 5.0, y: 5.0)

2.自定義運算符

官方文檔

  • prefix前綴運算符
  • infix中綴運算符
  • postfix后綴運算符

1.聲明自定義prefixpostfix運算符

struct Vector {
    let x: Double
    let y: Double
}

//1.聲明prefix或postfix自定義運算符

//pow
prefix operator *==
//sqar
postfix operator /==


//2.prefix或postfix實現運算符功能
extension Vector {
    static prefix func *==(vector: Vector) -> Vector {
        return Vector(x: pow(vector.x, 2), y: pow(vector.y, 2))
    }
    
    static postfix func /==(vector: Vector) -> Vector {
        return Vector(x: sqrt(vector.x), y: sqrt(vector.y))
    }
}

print(*==Vector(x: 10, y: 10)) //Vector(x: 100.0, y: 100.0)
print(Vector(x: 100, y: 100)/==) //Vector(x: 10.0, y: 10.0)

2.聲明infix運算符

struct Vector {
    let x: Double
    let y: Double
}

//1.聲明一個`infix`運算符

//實現2次+=
infix operator +=+=

//2.實現運算符功能
extension Vector {
    static func +=+=(vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x + vector1.x, y: vector0.y + vector1.y + vector1.y)
    }
    
}

print(Vector(x: 10, y: 10) +=+= Vector(x: 5, y: 5)) //Vector(x: 20.0, y: 20.0)

如果當前自定義運算符類型為infix,還可以指定優先級組,也就是結合原則。

//也就是將當前聲明的運算符遵循一個優先級原則,當然這個優先級組可以是系統的的也可以是自定義的
infix operator +=+=: MyCustomOperator

//也可以遵循加法優先級原則
//infix operator +=+=: AdditionPrecedence

//3.自定義優先級組
precedencegroup MyCustomOperator {
    //優先級高于
    higherThan: AdditionPrecedence
    //優先級低于
    lowerThan: MultiplicationPrecedence
    //左結合
    associativity: left
}

驗證自定義的優先級組

struct Vector {
    let x: Double
    let y: Double
}

extension Vector {
    //運算符重載必須使用static
    //prefix聲明前綴運算符
    static prefix func - (vector: Vector) -> Vector {
        return Vector(x: -vector.x, y: -vector.y)
    }
    
    static func + (vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x, y: vector0.y + vector1.y)
    }
    
    static func - (vector0: Vector, vector1: Vector) -> Vector {
        return vector0 + -vector1
    }
}

//1.聲明一個`infix`運算符

//實現2次+=
//也就是將當前聲明的運算符遵循一個優先級原則,當然這個優先級組可以是系統的的也可以是自定義的
infix operator +=+=: MyCustomOperator

//2.實現運算符功能
extension Vector {
    static func +=+=(vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x + vector1.x, y: vector0.y + vector1.y + vector1.y)
    }
    
}

//3.自定義優先級組
precedencegroup MyCustomOperator {
    //優先級高于
    higherThan: AdditionPrecedence
    //優先級低于
    lowerThan: MultiplicationPrecedence
    //左結合
    associativity: left
}

let x = Vector(x: 5, y: 5)
let y = Vector(x: 10, y: 10)
//此時的+=+=優先級比+搞,因此會先執行+=+=
let z = x + y +=+= x

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

推薦閱讀更多精彩內容