swift小技巧之运算符重载
如果对C++
有了解的话,理解运算符重载(Operator Overload)就很简单。OC
不支持运算符重载,但Swift
支持。
一、运算符重载
类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做运算符重载。
正常加法运算:
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
编译器不支持这样的写法,这时候就需要用到运算符重载。
1.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)
+
号运算符重载代码写到结构体外部也可以,只是平时建议写在内部(高内聚)。
1.2. 运算符:减号前缀(运算符放到前面,多了prefix关键词)
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)
1.3. 运算符:+= (多了 inout 关键词)
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
声明。由于只需要修改第一个参数的内存,所以函数不需要返回值。
1.4. 运算符:== (结构体或者类需继承于Equatable)
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
协议,重载==
运算符。
1.5. 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
1.6. 恒等运算符===
、!==
引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符===
,!==
(仅限引用类型)。
class Person { } var p1 = Person() var p2 = Person() print(p1 === p2) // 输出:false print(p1 !== p2) // 输出:true
1.7. 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
,并且使用了两个及以上运算符就会报错:

解决报错:
associativity
取值left
或right
- 使用一个运算符
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)
如果变量p
为nil
,不会继续往右执行(+-
运算符不会执行);如果不为nil
,则正常顺序执行代码(+-
运算符正常执行)。
优先级组参数说明:
associativity
结合性有三个取值:left
:从左往右开始结合计算right
:从右往左开始结合计算none
:仅限一个运算符,多个运算符会报错(例:a1 + a2 + a3
,有2个运算符编译报错)
higherThan
:哪个运算符优先级比当前定义的运算符优先级高lowerThan
:哪个运算符优先级比当前定义的运算符优先级低assignment
:true
代表在可选链操作中拥有跟赋值运算符一样的优先级
运算符优先级组描述: