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)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容