http://blog.csdn.net/mengxiangyue/article/details/43437797

原文地址:http://www.raywenderlich.com/80818/operator-overloading-in-swift-tutorial 作者:Corinne Krych  译者:孟祥月 blog:http://blog.csdn.net/mengxiangyue

这篇文章是本人第一次翻译,难免有错误,翻译的时候使用的是txt,所以格式上面有些不太好。

在早前的IOS 8盛宴系列的教程里,你已经了解到,Swift提供了许多强大的、现代的编程特性,比如泛型、函数式编程、一等类型(first class)的枚举、结构体等特性。

但是现在还有另外一个Swift的特性,你应该知道并且会爱上它,它就是运算符重载。

这是一个很好的方法,你能使用+、-、*、/等操作符作用在你喜欢的任何类型上面。如果你有一定的创造性,你甚至可以定义属于你自己的操作符。

例如:我们在Swift Sprite Kit utility library(https://github.com/raywenderlich/SKTUtils/tree/swift)代码中使用运算符重载去讲多个CGPoints对象相加,例如下面代码:
let pt1 = CGPoint(x: 10, y: 20)
let pt2 = CGPoint(x: -5, y: 0)
let pt3 = pt1 + pt2
let pt4 = pt3 * 100

方便吧?那就马上开始重载吧,增强你的Swift开发的能力吧。

注意:这个Swift的教程是假设你已经具备了基础的Swift开发能力。如果你是新接触Swift,我们建议你先去学习我们的其他的Swift教程(http://www.raywenderlich.com/tutorials#swift).

运算符:概述
注意:这一部分的内容是可选的,如果你想回顾一下运算符及其优先级,还是可以看这部分内容的。如果你已经对这些很熟悉了,可以直接创建一个空的playground,进行下一部分内容:重载(Overloading)。

首先我们创建一个新的playground来帮助你去了解运算符。
添加如下的代码在你的playground中:
var simpleSum = 1 + 3
你能看到我们希望的结果:
4
这里有两个我们熟悉的操作符:
1 首先,你定义了一个叫做simpleSum的变量,并且使用赋值操作符(=)设置了它的值。
2 然后,你使用加操作符(+)计算了两个整数的和。

在这篇教程里,你将像这样重载操作符。但是首先,你需要理解优先级的概念。
优先级
你可能还记得在学校里的数学课上学过的关于操作符的优先级的规则。这些规则使某些操作符比其他得操作符有一个更高的优先级,高优先级的操作符被优先计算。例如乘会在加或者减之前计算。
在你的playground中输入以下的代码,验证在Swift中是否也遵循这些规则。
var sumWithMultiplication = 1 + 3 - 3 * 2
你能看到如下的结果:
-2
当算数操作符有相同的优先级的时候,Swift从左到右去计算这些操作符。在这个例子中,运算符按照如下的顺序计算的:
1.3 * 2:减去(译者注:这个减去可以忽略,主要是为了对应第三步)
2.1 + 3:因为在操作符优先级一样得情况下,优先计算最左边得操作符。
3.4 - 6:这个运算完全依赖于前面高优先级的运算符的运算结果。

注意:如果你想了解Swift中优先级的列表,你能在这里(https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-XID_720)找到完成的运算符优先级的列表。

加不是仅仅能够使用在数字上(Adding Isn’t Just for Ints)
整数运算会按照我们希望的运行,但是你能将+使用到其他的类型上吗?
下面代码证明了,你可以!在你的playground里面添加如下的代码试一试:
var sumArray = [1, 2] + [1, 2]
在这种情况下,Swift将+解释成为append指令。但是如果你是想把每一个位置的元素相加怎么办呢?我们都知道这个叫做向量加法(vector addition)。
当然,你能自己定义一个方法去实现这个功能,在你的playground添加如下的代码再试一试:
func add(left: [Int], right: [Int]) -> [Int] {
    var sum = [Int]() 
    assert(left.count == right.count, "vector of same length only") 
    for (key, v) in enumerate(left) {
        sum.append(left[key] + right[key]) 
    }
    return sum
}
这样你就定义了一个全局的方法,这个方法实现了计算输入的两个数组的相加,首先检测两个输入的数组的长度是否一致,然后将两个数组每一个位置上的元素相加并且存储到一个新的数组里面。
现在添加下面的代码,验证一下你的新方法是否工作正常:
var arr1 = [1, 1]
var arr2 = [1, 1]
var arr3 = add(arr1, arr2)
你将在控制台看到如下的输出:
[2, 2]
它很棒!但是我们必须去调用一个方法去做这件事,为什么我们不可以使用+运算符代替呢?

运算符重载
运算符重载允许你改变现在的作用在特定在的结构体和类上的已经存在的操作符的工作方式(译者注:可能有点乱)。这个不正是你想要的吗--改变+操作符作用在int数组上的方式。
因为运算符重载是作用在playground的全局中的,所以新建一个playground,防止影响你原来写的例子。然后添加如下的代码到你的playground:
func +(left: [Int], right: [Int]) -> [Int] { // 1
    var sum = [Int]() // 2
    assert(left.count == right.count, "vector of same length only")  // 3
    for (key, v) in enumerate(left) {
      sum.append(left[key] + right[key]) // 4
    }
    return sum
}
你已经定义了一个全局的函数,叫做+,它将两个int数组相加然后返回一个int数组。下面分解一下它是怎么工作的:
1.注意这个方法定义没有什么特殊。它是一个普通的方法定义,除了你使用了+作为它的函数名。
2.你创建了一个空的Int数组。
3.这个例子只能工作在两个数组是相同的情况上,所以这里使用assert保证它是这样。
4.然后你枚举了左侧的数组,并且加上了右边的数组在相同位置的值。
在你的playground添加如下的代码,测试一下这个方法:
var sumArray1 = [1, 2, 3] + [1, 2, 3]
最终--你期望的向量相加操作符结果出现了!你将看到如下的结果:
[2, 4, 6]
当然,运算符重载并不都是愉快的。当一个人查看你的代码,他们希望操作符的默认行为,这时候运算符重载会使他们迷惑。虽然这样,但是还是不能阻止你重写+运算符让它去执行数字的减法,当然这样的风险是明显的。
image http://cdn4.raywenderlich.com/wp-content/uploads/2014/09/OperatorRage.png
记住运算符重载的原则:能力越大责任越大(with great power comes great responsibility)。
典型的,当你在一个新的对象上重载运算符的时候,需要保持它原始的语义,而不是定义不同(和让人费解)的行为。
在这个例子中,重载的行为还是保持了原始的语义:向量加法仍然是一种加法。但是当你覆盖了Int数组默认的加行为的时候,过了几个月你可能想要使用Int数组加得默认行为,这个将会使用感到很困惑。
幸运的是Swift让你能够定义属于你自己的自定义的运算符。
定义自定义运算符
这里有三个步骤去定义一个自定义操作符:
1.命名你的运算符
2.选择一种类型
3.设置它的优先级和结合性
定义你的运算符
现在你必须选择一个字符作为你的运算符。自定义运算符可以以/、=、-、+、!、*、%、<、>、&、|、^、~或者Unicode字符开始。这个给了你一个很大的范围去选择你的运算符。但是别太高兴,选择的时候你还必须考虑重复输入的时候更少的键盘键入次数。
在这种情况下,你可以复制粘贴Unicode字符⊕作为很好适应你例子里面加法的实现。
选择一种类型
在Swift中你能定义一元、二元和三元的操作符。他们表明了运算符操作的数字的数目。
一元操作符与一个操作数相关,比如后置++(i++)或者前置++(++i),他们依赖于运算符与操作数出现的位置。
二元操作符是插入的,因为它出现在两个操作符中间,比如1 + 1。
三元操作符有三个操作数。在Swift中,?:条件操作符是唯一一个三目运算符,比如a?b:c。
你应该基于你的运算符的操作数的个数选择合适得类型。你想要实现两个数组相加,那就定义二元运算符。
设置它的优先级和结合性
由于运算符定义是全局的,所以你要小心的选择你的自定义运算符的优先级和结合性。
这个是十分棘手的,所以有一个比较好的方法,在Swift language reference(https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-XID_720)中找到一个类似的标准的运算符,然后使用相同的语义。例如,在定义向量加的时候,可以使用与+运算符相同的优先级和结合性。
编写你的自定义运算符
回到你的playground,输入下面代码去定义你的自定义运算符。为了简单,你可能想去复制粘贴⊕。(译者注:这里可能指的是在使用的过程中去复制这个字符)
infix operator ⊕ { associativity left precedence 140 } // 1
func ⊕(left: [Int], right: [Int]) -> [Int] { // 2
    var sum = [Int](count: left.count, repeatedValue: 0)
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        sum[key] = left[key] + right[key]
    }
    return sum
}
这段代码与你前面在第一部分中的重载类似,这段代码主要做了以下几个步骤:
* 定义一个中缀/二元操作符,它有两个操作数并且位于操作符两侧。
* 命名操作符为⊕。
* 设置结合性为left,表明该操作符在相同优先级时候,将使用操作符的顺序从左到右结合。
* 设置优先级为140,这个是和Int加法有相同的优先级,这些优先级可以在Swift language reference(https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-XID_720)查看。
在第二部分的代码和你在前面看到的类似,它按照两个数组的顺序将其一个一个的相加。在你的playground中添加下面的代码,测试这个新的运算符:
var sumArray = [1, 2, 3] ⊕ [1, 2, 3]
你将看到和前面重载方法一样的结果,但是这次你有了一个拥有不同语义的操作符。
Bonus Round!
现在你已经知道了怎么去创建一个自定义的运算符,是时候挑战一下你自己了。你已经创建了一个⊕运算符去执行向量的相加,所以使用现在的知识去创建一个⊖操作符,使用相似的方法实现两个数组的减法。尽你最大的努力,然后再去查看下面的答案。
infix operator  ⊖ { associativity left precedence 140 }
func ⊖(left: [Int], right: [Int]) -> [Int] {
  var minus = [Int](count: left.count, repeatedValue: 0)
  assert(left.count == right.count, "vector of same length only")
  for (key, v) in enumerate(left) {
    minus[key] = left[key] - right[key]
  }
  return minus
}
测试:
var subtractionArray = [1, 2, 3] ⊖ [1, 2, 3]

记住相似的操作符
如果你定义了一个新的操作符,不要忘了定义任何相关得运算符。
例如,加等运算符(+=)组合了加和赋值两个运算符成为了一个运算符。由于你的新的运算符语义上是跟加是一样的,一个好的方法是也定义一个加等于运算符。
添加下面得代码到Operator2.playground:
infix operator  ⊕= { associativity left precedence 140 } // 1
func ⊕=(inout left: [Int], right: [Int]) { // 2
    left = left ⊕ right
}
第一行是与⊕运算符一样得声明,它使用了一个组合运算符。
需要注意第二行,声明这个组合运算符的左侧输入参数为inout,这个表示这个参数的值,将会在运算符方法内部直接被修改。作为一个结果,这个运算符不用返回一个值,它直接修改了你的输入值。
在你的playground添加如下的代码,测试这个运算符是否按照你想的方法运行。
你将在控制台看到如下输出:
[3, 5, 7]
现在看看,定义你自己的运算符一点也不难。

为不仅仅是一种类型定义运算符
现在想象你也想为小数定义一个向量加运算符。
一种方式是你按照为Int重载运算符的方式,为Double和Float重载一个新的运算符。它仅仅是几行的代码,但是你必须使用复制/粘贴。如果你像我一样--有代码洁癖--复制代码不是你的第一选择,它会使你的代码很难维护。
使用泛型来解救
幸运的,Swift泛型能帮助你实现这个功能。如果你需要复习一下Swift的泛型,可以找到我们之前发布的文章Swift Generics Tutorial(http://www.raywenderlich.com/82572/swift-generics-tutorial)。
为了有一个干净的上下文环境,我们新建一个playground。添加如下的代码到你的playground中:
infix operator ⊕ { associativity left precedence 140 }
func ⊕<T>(left: [T], right: [T]) -> [T] { // 1
    var minus = [T]()
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        minus.append(left[key] + right[key]) // 2
    }
    return minus
}
在第一行,你定义了一个泛型类型得函数⊕,它有一个类型占位符T。到这里playground不高兴了。你能看到一个编译错误:Could not find an overload for '+' that accepts the supplied arguments.
这个错误来源于第二行,当我们尝试使用+运算符作用在两个类型为T得left和right两个参数上的时候发生错误。Swift并不知道它应该怎么使用+运算符作用在这些参数上,因为它不知道这些参数是什么类型。
扩展一个协议
去掉你的代码,并且用下面的代码代替:
protocol Number {  // 1
     func +(l: Self, r: Self) -> Self // 2
}
 
extension Double : Number {} // 3
extension Float  : Number {}
extension Int    : Number {}
 
infix operator ⊕ { associativity left precedence 140 }
func ⊕<T: Number>(left: [T], right: [T]) -> [T] { // 4
    var minus = [T]()
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        minus.append(left[key] + right[key])
    }
    return minus
}
你在这里做了许多的事情,我们回过头来分解一下这些步骤:
1. 你定义了一个协议Number
2. 这个Number定义了一个运算符+
3. 你为Double、Float和Int创建了一个扩展,使它们能够实现Number协议
4. 你使用了一个类型约束去要求T必须实现Number协议
最后,你告诉编译器,T应该怎么去处理+运算符。既然你已经修复了编译错误,那就使用下面得代码分别使用Double数组和Int数组测试一下吧:
var doubleArray = [2.4, 3.6] ⊕ [1.6, 2.4]
var intArray = [2, 4] ⊕ [1, 2]
你将在控制台看到如下输出:
[4.0, 6.0]
[3, 6]
现在这个运算符能够正常在多种数据类型下面工作,并且没有复制代码。如果你想添加更多得数字类型,你只需要简单的生命其实现Number协议就可以了。

在真实得生活中我还能怎么使用运算符重载
难道你就没有想过,如果它没有作用,我会让你浪费这么多的时间在这篇教程上吗?这一部分将要展示给你一个真实得例子,让你了解怎么样在你的项目中更好得使用运算符重载。
运算符和CGPoints
对于这个Demo,你将使用SKTUtils library(https://github.com/raywenderlich/SKTUtils/tree/swift),它是一个方便得Sprite Kit帮助类的集合,当时是为了 iOS Games by Tutorials(http://www.raywenderlich.com/store/ios-games-by-tutorials)这本书的第二版而写的。
你能在github上找到这个框架的仓库。在你命令行界面输入如下的代码,可以Clone一份这个仓库的分支:
git clone https://github.com/raywenderlich/SKTUtils.git --branch swift
你在github上下载下来的是该仓库分支的压缩包zip。
注意:从Xcode6 beta 5开始,在playground中引入你自己的library成为了可能。你需要做的就是将框架和playground绑定在一个workspace中。如果你想知道更多关于这些的内容,请阅读这篇文章Playground has never been so fun(http://corinnekrych.blogspot.fr/2014/08/playground-has-never-been-so-fun.html)。
打开SKUTils/Examples/Playground/SKUTils.xcodeworkspace,并且编译这个项目。
然后从项目导航里面打开MyPlayground.playground。删除现在里面的内容并且添加如下的代码:
import SKTUtils 
 
let pt1 = CGPoint(x: 10, y: 20)
let pt2 = CGPoint(x: -5, y: 0)
let pt3 = pt1 + pt2 
let pt4 = pt3 * 100
你可能很惊讶,你已经在CGPoint上成功的使用+、*运算符,并且编译器并没有出现错误。
{x 10 y 20}
{x -5 y 0}
{x 5 y 20}
{x 500 y 2,000}
这个魔法来自于你在头部引入的SKTUtils。让我们仔细的看一下。

在SKTUtils中的重载
在项目导航中打开SKTUtils/CGPoint+Extension.swift文件。你将看到为CGPoint定义了一个扩展,重载了+和+=运算符。
public func + (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
 
public func += (inout left: CGPoint, right: CGPoint) {
  left = left + right
}
这段代码跟你前面写的类似,只是把访问控制符设置成了public。访问控制符约束着在其他得源文件和模块中能否访问到你的代码。由于SKTUtils是一个框架,所以它需要能够被它自己模块之外访问到,所以定义为了public。
这个戏法解释清楚了,它没有一点魔力,只是一个聪明得编码。
当然,在游戏中CGPoint加法和乘法也是一个很普通得运算,所以在那本书中,重载了CGPoint的运算符简化了代码,使它简洁、易读。我相信你能在你的项目中发现类似的例子。
运算符重载是Swift的一个强大得特性,如果你小心的使用它,它会使你的开发更加高效。

接下来该做什么?
你已经到了这个教程的结尾,我希望你喜欢它!你能在这里(http://cdn5.raywenderlich.com/wp-content/uploads/2014/09/OperatorsPlaygrounds.zip)找到最终的playground。
如果你想学习更多关于运算符重载和Swift的知识,可以查看我们的新书Swift by Tutorials(http://www.raywenderlich.com/store/swift-tutorials-bundle)。
我希望你找到一种方法,在你的项目中使用运算符重载!但是记住:with great power comes great responsibility(能力越大责任越大) – don’t be Troll Dev! ;]
如果你对于这个教程或者运算符重载有什么问题,可以在下面加入我们的论坛讨论。

Swift教程之运算符重载的更多相关文章

  1. Swift教程之运算符

    import Foundation //4.复合赋值操作符 var a = 1 a += 2 //一元减运算符(一个数值前加了符号-,叫作一元减运算符) let three = 3 let minus ...

  2. swift:高级运算符(位运算符、溢出运算符、优先级和结合性、运算符重载函数)

    swift:高级运算符 http://www.cocoachina.com/ios/20140612/8794.html 除了基本操作符中所讲的运算符,Swift还有许多复杂的高级运算符,包括了C语和 ...

  3. Swift语言精要 - Operator(运算符重载)

    运算符重载 Swift的这一语言特性或许应该启发于C++ class Vector2D { var x : Float = 0.0 var y : Float = 0.0 init (x : Floa ...

  4. Swift - 运算符重载和运算符函数

    让已有的运算符对自定义的类和结构进行运算或者重新定义已有运算符的运算规则,这种机制被称为运算符重载. 1,通过重载加号运算符,使自定义的两个坐标结构体对象实现相加: 1 2 3 4 5 6 7 8 9 ...

  5. C/C++对Lu系统内置动态对象进行运算符重载

    欢迎访问Lu程序设计 C/C++对Lu系统内置动态对象进行运算符重载 1 说明 要演示本文的例子,你必须下载Lu32脚本系统.本文的例子需要lu32.dll.lu32.lib.C格式的头文件lu32. ...

  6. 中文版 Apple 官方 Swift 教程《The Swift Programming Language》

    简介 欢迎使用 Swift 关于 Swift 版本兼容性 Swift 初见 Swift 版本历史记录 Swift 教程 基础部分 基本运算符 字符串和字符 集合类型 控制流 函数 闭包 枚举 类和结构 ...

  7. C++ 运算符重载时,将运算符两边对象交换问题.

    在C++进行运算符重载时, 一般来讲,运算符两边的对象的顺序是不能交换的. 比如下面的例子: #include <iostream> using namespace std; class ...

  8. C#高级编程笔记2016年10月12日 运算符重载

    1.运算符重载:运算符重重载的关键是在对象上不能总是只调用方法或属性,有时还需要做一些其他工作,例如,对数值进行相加.相乘或逻辑操作等.例如,语句if(a==b).对于类,这个语句在默认状态下会比较引 ...

  9. C++运算符重载

    C++运算符重载 基本知识 重载的运算符是具有特殊名字的函数,他们的名字由关键字operator和其后要定义的运算符号共同组成. 运算符可以重载为成员函数和非成员函数.当一个重载的运算符是成员函数时, ...

随机推荐

  1. 虚拟现实-VR-UE4-构建光照显示光照构建失败,Swarm启动失败

    闲的无聊折腾,发现想构建光照的时候,总是显示失败 如下图 百度许久,有大神指出是我在编译源码的的时候没有将其中的某个模块编译进去,只需要重新编译摸个模块就好 在UE4 的sln文件下,会看到一个Unr ...

  2. Java并发基础--线程安全

    一.线程安全 1.线程安全的概念 线程安全:某个类被单个线程,或者多个线程同时访问,所表现出来的行为是一致,则可以说这个类是线程安全的. 2.什么情况下会出现线程安全问题 在单线程中不会出现线程安全问 ...

  3. dell raid配置

    常用查看命令:待有dell裸机环境会详细列出 megacli -LDInfo -Lall -aALL 查raid级别 megacli -AdpAllInfo -aALL 查raid卡信息 megacl ...

  4. Flask 学习笔记(一)

    一.Web 服务器与 Web 框架 首先明确一下,要运行一个动态网页,我们需要 一个 Web 服务器来监听并响应请求,如果请求的是静态文件它就直接将其返回,如果是动态 url 它就将请求转交给 Web ...

  5. [热键冲突]MacOS下 Pycharm的全局搜索Ctrl+Shift+F失灵

    刚换了MacOS 发现Pycharm下的全局搜索Ctrl+Shift+F失灵了, 经过帖子 https://blog.csdn.net/pxinm/article/details/64444560 知 ...

  6. Beat 冲刺 (3/7)

    队名:起床一起肝活队 组长博客:博客链接 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去两天完成了哪些任务 描述: 1.界面的修改与完善 展示GitHub当日代码/文档签入记 ...

  7. linux服务器su之后变成bash-4.1#

    当前为root权限 cd /home/jboss 执行如下命令,将缺失的配置文件拷贝到指定位置即可 cp ./.bashrc /root cp ./.bash_profile /root 然后切换账号 ...

  8. Dubbo 的 Helloworld

    前提条件 安装好了 ZooKeeper 作为注册中心 服务端 <?xml version="1.0" encoding="UTF-8"?> < ...

  9. thead tfoot tbody标签的使用

    这三个都是<body>元素的子标签,不常用,因为其只是对<tr>标签做了一个区分 <thread>用于包裹表格头信息 <tfoot>用于包裹表格最后一行 ...

  10. powershell入门教程-v0.3版

    powershell入门教程-v0.3版 来源 https://www.itsvse.com/thread-3650-1-1.html 参考 http://www.cnblogs.com/piapia ...