重读 Swift 之二:Operator Declaration(运算符重载)

为什么要重载运算符

我们都知道所谓的运算符平常的也就是 + - * / 之类的,比如我们随便写个 1 + 2 打印肯定输出的是 3 ,那么我们为什么还要重载运算符呢?下面我们就举个例子,如下我定义个结构体

1
2
3
4
5
6
struct Vector {
    
    var x: Int = 0
    var y: Int = 0
    var z: Int = 0
}

然后我们定义两个变量 V1,V2

1
2
var V1 = Vector(x: 1, y: 2, z: 3)
var V2 = Vector(x: 4, y: 5, z: 6)

然后我们来写V1 + V2,报错

 error
报错 + 不能用于 Vector,这是因为 Vector 是我们自定义的一个结构体,所以编译器不知道我们要用 + 对这个结构体做什么操作。因此,在这里我们就需要用到运算符的重载。

如何重载运算符

含有两个参数的运算符的重载

因为运算符是一个函数,比如对于数组的 reduce

1
2
let arr = [1, 2, 3, 4]
arr.reduce(0, +)

结果是 10 ,这里的 + 就代表了一个函数,所以我们重新写的时候可以如下

1
2
3
4
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 类型的值

V1 + V2

这里我们就已经完成了 + 这个运算符的重载。当然有兴趣的童鞋还可以试着自己实现 - 或者 * 的重载,这里就不一一举例了。
关于双目运算符的重载,和单目运算符类似,如下

1
2
3
4
func  += (left: inout Vector, right: Vector) {
    
   left = left + right
}

运行结果
V1

含有一个参数的运算符的重载

照着上面单目运算符的方式我们自己来写个 - 重载例子,如下

1
2
3
4
func - (vector: Vector) -> Vector {
    
    return Vector(x: -vector.x, y: -vector.y, z: -vector.z)
}

按照我们的逻辑这里取反逻辑上应该是正确的,可是编译会发现报错

error
这里我们就要注意了,和有两个参数的运算符不同的是,只有一个参数的运算符位置是不固定的,这里的 - 可以在前可以在后,所以我们在这里还需要注意运算符的位置

1
2
3
4
prefix func - (vector: Vector) -> Vector {
    
    return Vector(x: -vector.x, y: -vector.y, z: -vector.z)
}

这里加上一个 prefix 表示前置(后置是 postfix)。这样就可以明确运算符的位置

-V1

比较运算符的重载

关于比较运算符的重载,顾名思义也是有两个参数的,返回值肯定是个 Bool 类型的,如下重载 == 运算符

1
2
3
4
func == (left: Vector, right: Vector) -> Bool {
    
    return left.x == right.x && left.y == right.y && left.z == right.z
}

 V1 与 V2 的比较
再来看看 > 的重载,逻辑稍微多一点

1
2
3
4
5
6
7
8
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 就会出现你逻辑中的效果。常规的运算符就说到这里,下面我们来看一下自定义运算符的重载。

自定义运算符的重载

上面我们所说的都是 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 字符。

自定义单目运算符

所以我们在自定义运算符的时候要注意一下。下面我们就来简单的自定义一个单目运算符 +++,这个运算符的作用呢就是让 Vector 中的每个变量都加 1 ,如下

1
2
3
4
prefix func +++ (vector: Vector) -> Vector {
    
    return Vector(x: vector.x + 1, y: vector.y + 1, z: vector.z + 1)
}

但是编译的时候会报错,如下
error
这是因为我们没有明确的定义这个 +++,所以编译器不识别。所以我们应该申明一下这个运算符,正确的代码如下

1
2
3
4
5
prefix operator +++
prefix func +++ (vector: Vector) -> Vector {
    
    return Vector(x: vector.x + 1, y: vector.y + 1, z: vector.z + 1)
}

在前面我们用 prefix operator +++ 声明前置运算符 +++ ,这样后面就可以用了

1
2
3
4
5
6
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),到此,单目运算符的自定义就完成了。

自定义双目运算符

双目运算符的定义和单目运算符的定义类似,但是双目运算符自定义的时候的关键字是 infix,如下

1
2
3
4
infix operator ** 
func ** (x: Double, y: Double) -> Double {
    return pow(x, y)
}

上面我们就自定义了一个求平方的双目运算符 **,然后我们试试 2 ** 2就可以看到结果是 4.0
上面好像没有什么问题了,下面我想算一个平方的平方,拨入 2 的 2次方的 3 次方,照着逻辑应该这样写

1
2 ** 2 ** 3

但是编译我们会发现报错,如下
error
错误是说我们上面的运算是个非结合性的运算,所谓的结合性(associativity)就是运算的先后顺序,在 Swift 2 中我们都知道还有个优先级(precedence),默认的是 100 ,它的范围是 0~200 ,这个是用来设置运算符优先级的,比如在swift 2.2 中我们完全定义一个求平方运算符就是

1
2
3
4
5
6
infix operator ** { associativity left precedence 120 }
func ** (x: Double, y: Double) -> Double {
    
    return pow(x, y)
}
2 ** 2 ** 3 //结果是64.0

在 Swift 3 中有些变化,如下

1
2
3
4
5
6
7
8
9
precedencegroup ComparativePrecedence {
    associativity: right
    higherThan: LogicalConjunctionPrecedence
}
infix operator ** : ComparativePrecedence
func ** (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 之类的可以在下方参考连接中参考,这里就不一一说明了。
差不多运算符重载就到这里了,如果还有什么遗漏,欢迎大家指正!

若您觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏!
------------- 本文结束 感谢您的阅读 -------------