一、为什么要重载运算符
我们都知道所谓的运算符平常的也就是 + - * /
之类的,比如我们随便写个 1 + 2
打印肯定输出的是 3
,那么我们为什么还要重载运算符呢?下面我们就举个例子,如下我定义个结构体
struct Vector { var x: Int = 0 var y: Int = 0 var z: Int = 0}复制代码
然后我们定义两个变量 V1,V2
var V1 = Vector(x: 1, y: 2, z: 3)var V2 = Vector(x: 4, y: 5, z: 6)复制代码
然后我们来写V1 + V2
,报错
+
不能用于 Vector
,这是因为 Vector
是我们自定义的一个结构体,所以编译器不知道我们要用 +
对这个结构体做什么操作。因此,在这里我们就需要用到运算符的重载。 二、如何重载运算符
- 1、含有两个参数的运算符的重载 因为运算符是一个函数,比如对于数组的
reduce
方法我们就可以如下
let arr = [1, 2, 3, 4]arr.reduce(0, +)复制代码
结果是 10 ,这里的 +
就代表了一个函数,所以我们重新写的时候可以如下
func + (left: Vector, right: Vector) -> Vector { return Vector(x: left.x + right.x, y: left.y + right.y, z: left.z + right.z)}复制代码
这样我们就实现了 +
的重载,上面函数中 left 和 right 两个参数都是 Vector 类型的,一个表示运算符左边的参数一个表示运算符右边的参数(这两个参数是有先后顺序的,由于加法满足加法交换律所以这里体现不出来,有兴趣的可以试一下 -
的重载,这时候就要注意顺序了),然后返回值得类型也是 Vector 类型,再来实现V1 + V2
的时候,就发现得到了一个新的 Vector 类型的值
+
这个运算符的重载。当然有兴趣的童鞋还可以试着自己实现 -
或者 *
的重载,这里就不一一举例了。 关于双目运算符的重载,和单目运算符类似,如下 func += (left: inout Vector, right: Vector) { left = left + right}复制代码
运行结果
- 2、含有一个参数的运算符的重载 照着上面单目运算符的方式我们自己来写个
-
重载例子,如下
func - (vector: Vector) -> Vector { return Vector(x: -vector.x, y: -vector.y, z: -vector.z)}复制代码
按照我们的逻辑这里取反逻辑上应该是正确的,可是编译会发现报错
这里我们就要注意了,和有两个参数的运算符不同的是,只有一个参数的运算符位置是不固定的,这里的-
可以在前可以在后,所以我们在这里还需要注意运算符的位置 prefix func - (vector: Vector) -> Vector { return Vector(x: -vector.x, y: -vector.y, z: -vector.z)}复制代码
这里加上一个 prefix 表示前置(后置是 postfix)。这样就可以明确运算符的位置
- 3 、比较运算符的重载 关于比较运算符的重载,顾名思义也是有两个参数的,返回值肯定是个
Bool
类型的,如下重载==
运算符
func == (left: Vector, right: Vector) -> Bool { return left.x == right.x && left.y == right.y && left.z == right.z}复制代码再来看看
>
的重载,逻辑稍微多一点 func > (left: Vector, right: Vector) -> Bool { if left.x != right.x { return left.x > right.x } if left.y != right.y { return left.y > right.y } if left.z != right.z { return left.z > right.z } //如果上面判断都失败了说明 left == right,所以返回值应该是 false return false}复制代码
此时再去比较 V1 和 V2 就会出现你逻辑中的效果。常规的运算符就说到这里,下面我们来看一下自定义运算符的重载。
Tips:对于运算符的重载我们是不能重载
=
的,它是被编译器固定拥有的,在底层它是与内存管理相关的,,我们不能认为的去改变它,这里需要注意一下(或者你可以别把赋值运算=
看成运算符 +_+)。 注意以下这些标记=
、->
、//
、/*
、*/
、.
、<(前缀运算符)
、&
、?
、?(中缀运算符)
、>(后缀运算符)
、!
、?
是被系统保留的。这些符号不能被重载,也不能用于自定义运算符。
三、自定义运算符的重载
上面我们所说的都是 Swift 中已经存在了的运算符,那么我们能不能自己定义运算符呢?答案是肯定的,在文档中我们可以看到这么一句话
Custom operators can begin with one of the ASCII characters /, =, -, +, !, *, %, <, >, &, |, ^, ?, or ~, or one of the Unicode characters defined in the grammar below (which include characters from the *Mathematical Operators*, *Miscellaneous Symbols*, and *Dingbats* Unicode blocks, among others). After the first character, combining Unicode characters are also allowed.复制代码
意思就是
自定义运算符可以由以下其中之一的 ASCII 字符 /、=、 -、+、!、*、%、<、>、&、|、^、? 以及~,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号(Miscellaneous Symbols)* 以及印刷符号 (Dingbats) 之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。复制代码
- 1、自定义单目运算符 所以我们在自定义运算符的时候要注意一下。下面我们就来简单的自定义一个单目运算符
+++
,这个运算符的作用呢就是让 Vector 中的每个变量都加 1 ,如下
prefix func +++ (vector: Vector) -> Vector { return Vector(x: vector.x + 1, y: vector.y + 1, z: vector.z + 1)}复制代码
但是编译的时候会报错,如下
这是因为我们没有明确的定义这个+++
,所以编译器不识别。所以我们应该申明一下这个运算符,正确的代码如下 prefix operator +++prefix func +++ (vector: Vector) -> Vector { return Vector(x: vector.x + 1, y: vector.y + 1, z: vector.z + 1)}复制代码
在前面我们用 prefix operator +++
声明前置运算符 +++ ,这样后面就可以用了
var V3 = Vector(x: 1, y: 1, z: 1)prefix operator +++prefix func +++ (vector: Vector) -> Vector { return Vector(x: vector.x + 1, y: vector.y + 1, z: vector.z + 1)}V3+++复制代码
如上输出结果就是 Vector(x: 2, y: 2, z: 2),到此,单目运算符的自定义就完成了。
- 2、自定义双目运算符 双目运算符的定义和单目运算符的定义类似,但是双目运算符自定义的时候的关键字是
infix
,如下
infix operator ** func ** (x: Double, y: Double) -> Double { return pow(x, y)}复制代码
上面我们就自定义了一个求平方的双目运算符 **
,然后我们试试 2 ** 2
就可以看到结果是 4.0
。 上面好像没有什么问题了,下面我想算一个平方的平方,拨入 2 的 2次方的 3 次方,照着逻辑应该这样写
2 ** 2 ** 3复制代码
但是编译我们会发现报错,如下
错误是说我们上面的运算是个非结合性的运算,所谓的结合性(associativity)就是运算的先后顺序,在 Swift 2 中我们都知道还有个优先级(precedence),默认的是 100 ,它的范围是 0~200 ,这个是用来设置运算符优先级的,比如在swift 2.2 中我们完全定义一个求平方运算符就是infix operator ** { associativity left precedence 120 }func ** (x: Double, y: Double) -> Double { return pow(x, y)}2 ** 2 ** 3 //结果是64.0复制代码
在 Swift 3 中有些变化,如下
precedencegroup ComparativePrecedence { associativity: right higherThan: LogicalConjunctionPrecedence}infix operator ** : ComparativePrecedencefunc ** (x: Double, y: Double) -> Double { return pow(x, y)}复制代码
如上我们输入 2 ** 2 ** 3
,就会发现结果是 256.0,这是因为我们把 associativity 设置成为了 right,所以运算从右边开始,先算 2 ** 3 = 8.0
,然后再是 2 ** 8.0 = 256.0
,如果我们把 associativity 设置成为了 left,就会发现结果是 64.0。关于更多的 associativity 和 higherThan 或者 lowerThan 之类的可以在下方参考连接中参考,这里就不一一说明了。 差不多运算符重载就到这里了,如果还有什么遗漏,欢迎大家指正!
参考:
- 1、
- 2、