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

一、为什么要重载运算符


我们都知道所谓的运算符平常的也就是 + - * / 之类的,比如我们随便写个 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,报错

error

报错 + 不能用于 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 类型的值

V1 + V2

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

func  += (left: inout Vector, right: Vector) {
    
   left = left + right
}

运行结果


V1
  • 2、含有一个参数的运算符的重载
    照着上面单目运算符的方式我们自己来写个 - 重载例子,如下
func - (vector: Vector) -> Vector {
    
    return Vector(x: -vector.x, y: -vector.y, z: -vector.z)
}

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

error

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

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

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

-V1
  • 3 、比较运算符的重载
    关于比较运算符的重载,顾名思义也是有两个参数的,返回值肯定是个 Bool 类型的,如下重载 == 运算符
func == (left: Vector, right: Vector) -> Bool {
    
    return left.x == right.x && left.y == right.y && left.z == right.z
}

V1 与 V2 的比较

再来看看 > 的重载,逻辑稍微多一点

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)
}

但是编译的时候会报错,如下

error

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

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

但是编译我们会发现报错,如下
![error](http://upload-images.jianshu.io/upload_images/571495-b2e1e315590d0634.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
错误是说我们上面的运算是个非结合性的运算,所谓的结合性(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 ** : 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 之类的可以在下方参考连接中参考,这里就不一一说明了。
差不多运算符重载就到这里了,如果还有什么遗漏,欢迎大家指正!

> 参考:
1、[swift-evolution](https://github.com/apple/swift-evolution/blob/master/proposals/0077-operator-precedence.md)
2、[Operator Declaration](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/operator-declaration)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容