Swift系列二十一 - 高级运算符

如果对C++有了解的话,理解运算符重载(Operator Overload)就很简单。OC不支持运算符重载,但Swift支持。

一、溢出运算符(Overflow Operator)

Swift的算数运算符出现溢出时会抛出运行时错误。

示例代码一:

print(Int8.min) // 输出:-128
print(Int8.max) // 输出:127
print(UInt8.min) // 输出:0
print(UInt8.max) // 输出:255

var a = UInt8.max
a += 1
print(a)

Int8的可表示数范围是-128~127UInt8可表示数范围是0~255。当超出可表示数范围时运行时就会报错。

Swift有溢出运算符用来支持溢出运算。
常见的溢出运算符:&+&-&*

示例代码二:

var a = UInt8.max
a = a &+ 1
print(a) // 输出:0

var b = UInt8.min
b = b &- 2
print(b) // 输出:254

当数据溢出时,溢出运算符会自动循环可数范围。

官方图例

二、运算符重载

类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做运算符重载

正常加法运算:

let v1 = 10
let v2 = 20
let v3 = v1 + v2

如果换成非基本数值计算:

struct Point {
    var x = 0, y = 0
}
let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 20, y: 30)
let p3 = p1 + p2

编译器不支持这样的写法,这时候就需要用到运算符重载。

2.1. 运算符:+

struct Point {
    var x = 0, y = 0
    // +号运算符重载
    static func +(p1: Point, p2: Point) -> Point {
        Point(x: p1.x + p2.x, y: p1.y + p2.y)
    }
}

let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 20, y: 30)
let p3 = p1 + p2
print(p3) // 输出:Point(x: 30, y: 50)

+号运算符重载代码写到结构体外部也可以,只是平时建议写在内部(高内聚)。

2.2. 运算符:减号前缀(运算符放到前面)

static prefix func -(p: Point) -> Point {
    Point(x: -p.x, y: -p.y)
}
let p1 = Point(x: 10, y: 20)
let p2 = -p1
print(p2) // 输出:Point(x: -10, y: -20)

2.3. 运算符:+=

static func +=(p1: inout Point, p2: Point) {
    p1 = Point(x: p1.x + p2.x, y: p1.y + p2.y)
}
var p1 = Point(x: 10, y: 20)
let p2 = Point(x: 20, y: 30)
p1 += p2
print(p1) // 输出:Point(x: 30, y: 50)

+=运算符重载时,重载函数左边变量一定要用inout修饰,因为要修改外部变量的内存。并且外部变量使用var声明。由于只需要修改第一个参数的内存,所以函数不需要返回值。

2.4. 运算符:前置++

static prefix func ++(p: inout Point) -> Point {
    p += Point(x: 1, y: 1)
    return p
}
var p1 = Point(x: 10, y: 20)
let p2 = ++p1
print(p2) // 输出:Point(x: 11, y: 21)

++写在变量前面:先加后用

2.5. 运算符:后置++

static postfix func ++(p: inout Point) -> Point {
    let tmp = p
    p += Point(x: 1, y: 1)
    return tmp
}
var p1 = Point(x: 10, y: 20)
let p2 = p1++
print(p2) // 输出:Point(x: 10, y: 20)

++写在变量后面:先用后加

2.6. 运算符:==

static func ==(p1: Point, p2: Point) -> Bool {
    (p1.x == p2.x) && (p1.y == p2.y)
}
let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 20, y: 30)
let isTrue1 = p1 == p2
print(isTrue1) // 输出:false

let p3 = Point(x: 20, y: 30)
let isTrue2 = p2 == p3
print(isTrue2) // 输出:true

要想得知2个实例是否等价,一般做法是遵守Equatable协议,重载==运算符。

2.7. Equatable协议

public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}

示例代码:

struct Point: Equatable {
    var x = 0, y = 0
    static func == (lhs: Self, rhs: Self) -> Bool {
        // 普通写法
        if lhs.x == rhs.x && lhs.y == rhs.y {
            return true
        }
        return false
        // 简写
        // lhs.x == rhs.x && lhs.y == rhs.y
    }
}
let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 20, y: 30)
let isTrue1 = p1 == p2
print(isTrue1) // 输出:false

let p3 = Point(x: 20, y: 30)
let isTrue2 = p2 == p3
print(isTrue2) // 输出:true

无论是否遵守Equatable协议,都可以重载==运算符,为什么还要遵守协议呢?因为遵守协议就可以直接告诉其他人该类/结构体/枚举是支持==运算符比较的。还有一个很重要的区别是:遵守Equatable协议,默认重载!=运算符,但是自定义==运算符不会重载!=运算符。

Swift为以下类型提供默认的Equatable实现:

  • 没有关联类型的枚举
enum Answer {
    case wrong
    case right
}
var s1 = Answer.wrong
var s2 = Answer.right
print(s1 == s2) // 输出:false
  • 只拥有遵守Equatable协议关联类型的枚举
enum Answer: Equatable {
    case wrong(Int)
    case right
}
var s1 = Answer.wrong(10)
var s2 = Answer.wrong(10)
print(s1 == s2) // 输出:true

如果不遵守Equatable协议关联类型的枚举:

  • 只拥有遵守Equatable协议存储属性的结构体
struct Point: Equatable {
    var x = 0, y = 0
}
let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 20, y: 30)
let isTrue1 = p1 == p2
print(isTrue1) // 输出:false

let p3 = Point(x: 20, y: 30)
let isTrue2 = p2 == p3
print(isTrue2) // 输出:true

2.8. 恒等运算符===!==

引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符===,!==(仅限引用类型)。

class Person { }
var p1 = Person()
var p2 = Person()
print(p1 === p2) // 输出:false
print(p1 !== p2) // 输出:true

2.9. Comparable协议

要想比较2个实例的大小,一般做法是:

  • 遵守Comparable协议
  • 重载相应的运算符

官方定义的Comparable协议:

public protocol Comparable : Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
    static func <= (lhs: Self, rhs: Self) -> Bool
    static func >= (lhs: Self, rhs: Self) -> Bool
    static func > (lhs: Self, rhs: Self) -> Bool
}

示例代码:

// score大的比较大,若score相等,age小的比较大
struct Student : Comparable {
    var age: Int
    var score: Int
    init(age: Int, score: Int) {
        self.age = age
        self.score = score
    }
    static func < (lhs: Student, rhs: Student) -> Bool {
        (lhs.score < rhs.score) || (lhs.score == rhs.score && lhs.age > rhs.age)
    }
    static func > (lhs: Student, rhs: Student) -> Bool {
        (lhs.score > rhs.score) || (lhs.score == rhs.score && lhs.age < rhs.age)
    }
    static func <= (lhs: Student, rhs: Student) -> Bool {
        !(lhs > rhs)
    }
    static func >= (lhs: Student, rhs: Student) -> Bool {
        !(lhs < rhs)
    }
}

var stu1 = Student(age: 20, score: 100)
var stu2 = Student(age: 18, score: 98)
var stu3 = Student(age: 20, score: 100)
print(stu1 > stu2) // 输出:true
print(stu1 >= stu2) // 输出:true
print(stu1 >= stu3) // 输出:true
print(stu2 < stu1) // 输出:true
print(stu2 <= stu1) // 输出:true
print(stu1 <= stu3) // 输出:false

三、自定义运算符(Custom Operator)

上面的都是为已经存在的运算符进行重载,而自定义运算符是定义一个原本不存在的运算符。

自定义新的运算符在全局作用域使用operator进行声明。

格式:

prefix operator 前缀运算符

postfix operator 后缀运算符

infix operator 中缀运算符 : 优先级组
precedencegroup 优先级组 {
    associativity: 结合性(left/right/none)
    higherThan: 比谁的优先级高
    lowerThan: 比谁的优先级低
    assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
}

示例代码(定义前缀运算符+++):

prefix operator +++
prefix func +++ (_ i: inout Int) {
    i += 2
}
var age = 10
+++age
print(age) // 输出:12

示例代码(定义+-运算符并设置运算符优先级):

infix operator +- : PlusMinusPrecedence
precedencegroup PlusMinusPrecedence {
    associativity: none
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    assignment: true
}
struct Point {
    var x = 0, y = 0
    static func +- (p1: Point, p2: Point) -> Point {
        Point(x: p1.x + p2.x, y: p1.y - p2.y)
    }
}
var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 5, y: 15)
var p3 = p1 +- p2
print(p3) // 输出:Point(x: 15, y: 5)

如果设置associativity: none,并且使用了两个及以上运算符就会报错:

解决报错:

  1. associativity取值leftright
  2. 使用一个运算符

assignment示例:

class Person {
    var point: Point = Point()
}
var p: Person? = Person()
let result = p?.point +- Point(x: 10, y: 20)
print(result!) // 输出:Point(x: 10, y: -20)

如果变量pnil,不会继续往右执行(+-运算符不会执行);如果不为nil,则正常顺序执行代码(+-运算符正常执行)。

优先级组参数说明:

  • associativity结合性有三个取值:
    • left:从左往右开始结合计算
    • right:从右往左开始结合计算
    • none:仅限一个运算符,多个运算符会报错(例:a1 + a2 + a3,有2个运算符编译报错)
  • higherThan:哪个运算符优先级比当前定义的运算符优先级高
  • lowerThan:哪个运算符优先级比当前定义的运算符优先级低
  • assignmenttrue代表在可选链操作中拥有跟赋值运算符一样的优先级

运算符优先级组描述:

参考官方文档:
1. 运算符优先级组描述:https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations

2. 高级运算符:https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值