原文地址:Operator Overloading in Swift Tutorial(raywenderlich)作者:Corinne Krych 译者:孟祥月(博客):

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

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

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

例如:我们在Swift Sprite Kit utility library代码中使用运算符重载去讲多个CGPoints对象相加,例如下面代码:

1
2
3
4
letpt1=CGPoint(x:10,y:20)
letpt2=CGPoint(x:-5,y:0)
letpt3=pt1+pt2
letpt4=pt3*100

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

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

运算符:概述

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

首先我们创建一个新的playground来帮助你去了解运算符。

添加如下的代码在你的playground中:

1
var simpleSum=1+3

你能看到我们希望的结果:

1
4

这里有两个我们熟悉的操作符:

  • 首先,你定义了一个叫做simpleSum的变量,并且使用赋值操作符(=)设置了它的值。

  • 然后,你使用加操作符(+)计算了两个整数的和。

在这篇教程里,你将像这样重载操作符。但是首先,你需要理解优先级的概念。

优先级

你可能还记得在学校里的数学课上学过的关于操作符的优先级的规则。这些规则使某些操作符比其他得操作符有一个更高的优先级,高优先级的操作符被优先计算。例如乘会在加或者减之前计算。

在你的playground中输入以下的代码,验证在Swift中是否也遵循这些规则。

1
var sumWithMultiplication=1+3-3*2

你能看到如下的结果:

1

当算数操作符有相同的优先级的时候,Swift从左到右去计算这些操作符。在这个例子中,运算符按照如下的顺序计算的:

  • 1.3 * 2:减去(译者注:这个减去可以忽略,主要是为了对应第三步)

  • 2.1 + 3:因为在操作符优先级一样得情况下,优先计算最左边得操作符。

  • 3.4 - 6:这个运算完全依赖于前面高优先级的运算符的运算结果。

注意:如果你想了解Swift中优先级的列表,你能在这里找到完成的运算符优先级的列表。

加法不仅仅适用于整数

整数运算会按照我们希望的运行,但是你能将+使用到其他的类型上吗?

下面代码证明了,你可以!在你的playground里面添加如下的代码试一试:

1
var sumArray=[1,2]+[1,2]

在这种情况下,Swift将+解释成为append指令。但是如果你是想把每一个位置的元素相加怎么办呢?我们都知道这个叫做向量加法(vector addition)。

当然,你能自己定义一个方法去实现这个功能,在你的playground添加如下的代码再试一试:

1
2
3
4
5
6
7
8
funcadd(left:[Int],right:[Int])->[Int]{
var sum=[Int]()
assert(left.count==right.count, "vectorofsamelengthonly" )
for (key,v) in enumerate(left){
sum.append(left[key]+right[key])
}
return sum
}

这样你就定义了一个全局的方法,这个方法实现了计算输入的两个数组的相加,首先检测两个输入的数组的长度是否一致,然后将两个数组每一个位置上的元素相加并且存储到一个新的数组里面。

现在添加下面的代码,验证一下你的新方法是否工作正常:

1
2
3
var arr1=[1,1]
var arr2=[1,1]
var arr3=add(arr1,arr2)

你将在控制台看到如下的输出:

1
[2,2]

它很棒!但是我们必须去调用一个方法去做这件事,为什么我们不可以使用+运算符代替呢?

运算符重载

运算符重载允许你改变现在的作用在特定在的结构体和类上的已经存在的操作符的工作方式(译者注:可能有点乱)。这个不正是你想要的吗–改变+操作符作用在int数组上的方式。

因为运算符重载是作用在playground的全局中的,所以新建一个playground,防止影响你原来写的例子。然后添加如下的代码到你的playground:

1
2
3
4
5
6
7
8
func+(left:[Int],right:[Int])->[Int]{ //1
var sum=[Int]() //2
assert(left.count==right.count, "vectorofsamelengthonly" ) //3
for (key,v) in enumerate(left){
sum.append(left[key]+right[key]) //4
}
return sum
}

你已经定义了一个全局的函数,叫做+,它将两个int数组相加然后返回一个int数组。下面分解一下它是怎么工作的:

  • 注意这个方法定义没有什么特殊。它是一个普通的方法定义,除了你使用了+作为它的函数名。

  • 你创建了一个空的Int数组。

  • 这个例子只能工作在两个数组是相同的情况上,所以这里使用assert保证它是这样。

  • 然后你枚举了左侧的数组,并且加上了右边的数组在相同位置的值。

在你的playground添加如下的代码,测试一下这个方法:

1
var sumArray1=[1,2,3]+[1,3]

最终–你期望的向量相加操作符结果出现了!你将看到如下的结果:

1
[2,4,6]

当然,运算符重载并不都是愉快的。当一个人查看你的代码,他们希望操作符的默认行为,这时候运算符重载会使他们迷惑。虽然这样,但是还是不能阻止你重写+运算符让它去执行数字的减法,当然这样的风险是明显的。

记住运算符重载的原则:能力越大责任越大

典型的,当你在一个新的对象上重载运算符的时候,需要保持它原始的语义,而不是定义不同(和让人费解)的行为。

在这个例子中,重载的行为还是保持了原始的语义:向量加法仍然是一种加法。但是当你覆盖了Int数组默认的加行为的时候,过了几个月你可能想要使用Int数组加得默认行为,这个将会使用感到很困惑。

幸运的是Swift让你能够定义属于你自己的自定义的运算符。

定义自定义运算符

这里有三个步骤去定义一个自定义操作符:

  • 命名你的运算符

  • 选择一种类型

  • 设置它的优先级和结合性

定义你的运算符

现在你必须选择一个字符作为你的运算符。自定义运算符可以以/、=、-、+、!、*、%、<、>、&、|、^、~或者Unicode字符开始。这个给了你一个很大的范围去选择你的运算符。但是别太高兴,选择的时候你还必须考虑重复输入的时候更少的键盘键入次数。

在这种情况下,你可以复制粘贴Unicode字符⊕作为很好适应你例子里面加法的实现。

选择一种类型

在Swift中你能定义一元、二元和三元的操作符。他们表明了运算符操作的数字的数目。

  • 一元操作符与一个操作数相关,比如后置++(i++)或者前置++(++i),他们依赖于运算符与操作数出现的位置。

  • 二元操作符是插入的,因为它出现在两个操作符中间,比如1 + 1。

  • 三元操作符有三个操作数。在Swift中,?:条件操作符是唯一一个三目运算符,比如a?b:c。

你应该基于你的运算符的操作数的个数选择合适得类型。你想要实现两个数组相加,那就定义二元运算符。

设置它的优先级和结合性

由于运算符定义是全局的,所以你要小心的选择你的自定义运算符的优先级和结合性。

这个是十分棘手的,所以有一个比较好的方法,在Swift language reference中找到一个类似的标准的运算符,然后使用相同的语义。例如,在定义向量加的时候,可以使用与+运算符相同的优先级和结合性。

编写你的自定义运算符

回到你的playground,输入下面代码去定义你的自定义运算符。为了简单,你可能想去复制粘贴⊕。(译者注:这里可能指的是在使用的过程中去复制这个字符)

1
2
3
4
5
6
7
8
9
infixoperator⊕{associativityleftprecedence140} //1
func⊕(left:[Int],right:[Int])->[Int]{ //2
var sum=[Int](count:left.count,repeatedValue:0)
assert(left.count==right.count, "vectorofsamelengthonly" )
for (key,v) in enumerate(left){
sum[key]=left[key]+right[key]
}
return sum
}

这段代码与你前面在第一部分中的重载类似,这段代码主要做了以下几个步骤:

  • 定义一个中缀/二元操作符,它有两个操作数并且位于操作符两侧。

  • 命名操作符为⊕。

  • 设置结合性为left,表明该操作符在相同优先级时候,将使用操作符的顺序从左到右结合。

  • 设置优先级为140,这个是和Int加法有相同的优先级,这些优先级可以在Swift language reference查看。

在第二部分的代码和你在前面看到的类似,它按照两个数组的顺序将其一个一个的相加。在你的playground中添加下面的代码,测试这个新的运算符:

1
var sumArray=[1,3]⊕[1,3]

你将看到和前面重载方法一样的结果,但是这次你有了一个拥有不同语义的操作符。

奖励内容

现在你已经知道了怎么去创建一个自定义的运算符,是时候挑战一下你自己了。你已经创建了一个⊕运算符去执行向量的相加,所以使用现在的知识去创建一个?操作符,使用相似的方法实现两个数组的减法。尽你最大的努力,然后再去查看下面的答案。

1
2
3
4
5
6
7
8
9
infixoperator?{associativityleftprecedence140}
func?(left:[Int],right:[Int])->[Int]{
var minus=[Int](count:left.count,v) in enumerate(left){
minus[key]=left[key]-right[key]
}
return minus
}

测试:

1
var subtractionArray=[1,3]?[1,3]

记住相似的操作符

如果你定义了一个新的操作符,不要忘了定义任何相关得运算符。

例如,加等运算符(+=)组合了加和赋值两个运算符成为了一个运算符。由于你的新的运算符语义上是跟加是一样的,一个好的方法是也定义一个加等于运算符。

添加下面得代码到Operator2.playground:

1
2
3
4
infixoperator⊕={associativityleftprecedence140} //1
func⊕=(inoutleft:[Int],right:[Int]){ //2
left=left⊕right
}

第一行是与⊕运算符一样得声明,它使用了一个组合运算符。

需要注意第二行,声明这个组合运算符的左侧输入参数为inout,这个表示这个参数的值,将会在运算符方法内部直接被修改。作为一个结果,这个运算符不用返回一个值,它直接修改了你的输入值。

在你的playground添加如下的代码,测试这个运算符是否按照你想的方法运行。

你将在控制台看到如下输出:

1
[3,5,7]

现在看看,定义你自己的运算符一点也不难。

为更多类型定义运算符

现在想象你也想为小数定义一个向量加运算符。

一种方式是你按照为Int重载运算符的方式,为Double和Float重载一个新的运算符。它仅仅是几行的代码,但是你必须使用复制/粘贴。如果你像我一样–有代码洁癖–复制代码不是你的第一选择,它会使你的代码很难维护。

使用泛型来解救

幸运的,Swift泛型能帮助你实现这个功能。如果你需要复习一下Swift的泛型,可以找到我们之前发布的文章Swift Generics Tutorial。

为了有一个干净的上下文环境,我们新建一个playground。添加如下的代码到你的playground中:

1
2
3
4
5
6
7
8
9
infixoperator⊕{associativityleftprecedence140}
func⊕(left:[T],right:[T])->[T]{ //1
var minus=[T]()
assert(left.count==right.count,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并不知道它应该怎么使用+运算符作用在这些参数上,因为它不知道这些参数是什么类型。

扩展一个协议

去掉你的代码,并且用下面的代码代替:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protocolNumber{ //1
func+(l:Self,r:Self)->Self //2
}
extensionDouble:Number{} //3
extensionFloat:Number{}
extensionInt:Number{}
infixoperator⊕{associativityleftprecedence140}
func⊕(left:[T],right:[T])->[T]{ //4
var minus=[T]()
assert(left.count==right.count, "vectorofsamelengthonly" )
for (key,v) in enumerate(left){
minus.append(left[key]+right[key])
}
return minus
}

你在这里做了许多的事情,我们回过头来分解一下这些步骤:

  • 你定义了一个协议Number

  • 这个Number定义了一个运算符+

  • 你为Double、Float和Int创建了一个扩展,使它们能够实现Number协议

  • 你使用了一个类型约束去要求T必须实现Number协议

最后,你告诉编译器,T应该怎么去处理+运算符。既然你已经修复了编译错误,那就使用下面得代码分别使用Double数组和Int数组测试一下吧:

1
2
var doubleArray=[2.4,3.6]⊕[1.6,2.4]
var intArray=[2,4]⊕[1,2]

你将在控制台看到如下输出:

1
2
[4.0,6.0]
[3,6]

现在这个运算符能够正常在多种数据类型下面工作,并且没有复制代码。如果你想添加更多得数字类型,你只需要简单的生命其实现Number协议就可以了。

实际工作中如何使用运算符重载

难道你就没有想过,如果它没有作用,我会让你浪费这么多的时间在这篇教程上吗?这一部分将要展示给你一个真实得例子,让你了解怎么样在你的项目中更好得使用运算符重载。

运算符和CGPoints

对于这个Demo,你将使用SKTUtils library,它是一个方便得Sprite Kit帮助类的集合,当时是为了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。

打开SKUTils/Examples/Playground/SKUTils.xcodeworkspace,并且编译这个项目。

然后从项目导航里面打开MyPlayground.playground。删除现在里面的内容并且添加如下的代码:

1
2
3
4
5
importSKTUtils
letpt1=CGPoint(x:10,y:20)
letpt2=CGPoint(x:-5,y:0)
letpt3=pt1+pt2
letpt4=pt3*100

你可能很惊讶,你已经在CGPoint上成功的使用+、*运算符,并且编译器并没有出现错误。

1
2
3
4
{x10y20}
{x-5y0}
{x5y20}
{x500y2,000}

这个魔法来自于你在头部引入的SKTUtils。让我们仔细的看一下。

在SKTUtils中的重载

在项目导航中打开SKTUtils/CGPoint+Extension.swift文件。你将看到为CGPoint定义了一个扩展,重载了+和+=运算符。

1
2
3
4
5
6
7
publicfunc+(left:CGPoint,right:CGPoint)->CGPoint{
return CGPoint(x:left.x+right.x,y:left.y+right.y)
}
publicfunc+=(inoutleft:CGPoint,right:CGPoint){
left=left+right
}

这段代码跟你前面写的类似,只是把访问控制符设置成了public。访问控制符约束着在其他得源文件和模块中能否访问到你的代码。由于SKTUtils是一个框架,所以它需要能够被它自己模块之外访问到,所以定义为了public。

这个戏法解释清楚了,它没有一点魔力,只是一个聪明得编码。

当然,在游戏中CGPoint加法和乘法也是一个很普通得运算,所以在那本书中,重载了CGPoint的运算符简化了代码,使它简洁、易读。我相信你能在你的项目中发现类似的例子。

运算符重载是Swift的一个强大得特性,如果你小心的使用它,它会使你的开发更加高效。

接下来该做什么?

你已经到了这个教程的结尾,我希望你喜欢它!你能在这里找到最终的playground。

如果你想学习更多关于运算符重载和Swift的知识,可以查看我们的新书Swift by Tutorials。

我希望你找到一种方法,在你的项目中使用运算符重载!但是记住:with great power comes great responsibility(能力越大责任越大) – don’t be Troll Dev!

如果你对于这个教程或者运算符重载有什么问题,可以在下面加入我们的论坛讨论。

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

  1. ios – Swift相当于`[NSDictionary initWithObjects:forKeys:]`

    Swift的原生字典是否与[NSDictionaryinitWithObjects:forKeys:]相当?假设我有两个带键和值的数组,并希望将它们放在字典中.在Objective-C中,我这样做:当然我可以通过两个数组迭代一个计数器,使用vardict:[String:Int]并逐步添加东西.但这似乎不是一个好的解决方案.使用zip和enumerate可能是同时迭代两者的更好方法.然而,这种方法

  2. ios – 在Swift中获取Cocoa Touch Framework项目版本字符串

    有谁知道这是否是我的项目设置中的缺陷,Xcode中的一个错误,或者是否有一种方法可以将Swift中的框架版本作为String或数组获取,这样我可以提供比major.minor更精细的版本控制?

  3. ios – 搜索数组swift中的对象

    我正在尝试使用UISearchController创建搜索功能.但是,我似乎无法使其与我的团队对象一起工作.我首先创建了一个包含id,name和shortname的TeamObject.然后我从一个url中检索teamData,并将TeamObjects添加到一个填充到tableView中的数组中.这个tableView包含一个searchController,它假设过滤数据,但没有任何反应.阵列

  4. iOS推送通知优先级

    我已设置推送通知并正常工作,但是,有时我会遇到终端设备上的延迟交付.有没有办法我可以将推送的“优先级”键设置为10,以便立即发送推送?

  5. ios – Swift可选项:语言问题,还是做错了什么?

    应该有可选的类型;type是但是,如果我这样做,它的工作原理:它似乎是基本的替代,但我可能会遗漏一些语言的细微差别.谁能对此有所了解?之后就像暧昧一样,更多,这是我的解决方案:这适用于所有非对象Swift对象,包括Swift字符串,数字等.感谢Viktor提醒我String不是Swift中的对象.如果您知道值的类型,您可以替换任何?使用适当的可选类型,如String?

  6. ios – Swift – 使用字典数组从字典访问数据时出错

    我有一个非常简单的例子,说明我想做什么基本上,我有一个字典,其值包含[String:String]字典数组.我把数据填入其中,但当我去访问数据时,我收到此错误:Cannotsubscriptavalueoftype‘[([String:String])]?’withanindexoftype‘Int’请让我知道我做错了什么.解决方法您的常量数组是可选的.订阅字典总是返回一个可选项.你必须打开它.更

  7. ios – 在Swift中使用“Map”创建两个数组的超集

    假设我有两个数组:我想组合两个数组,以便我得到一个输出我该怎么做呢?

  8. 模糊地使用’下标’ – ios 9 Swift 2.0

    我在xcode7.0上使用Swift2.0编写iosapp.在更新到最新版本的xCode7.1之前,完全相同的代码完全正常更新后,我收到此错误:Ambiguoususeof‘subscript’在这些方面:这是全班:Theoriginallibrary解决方法编译器不知道self.itemAttributes[indexPath.section]返回的内容,因为它定义为NSMutableArray

  9. ios – 何时使用Semaphore而不是Dispatch Group?

    我会假设我知道如何使用DispatchGroup,为了解问题,我尝试过:结果–预期–是:为了使用信号量,我实现了:并在viewDidLoad方法中调用它.结果是:从概念上讲,dispachGroup和Semaphore都有同样的目的.老实说,我不熟悉:什么时候使用信号量,尤其是在与dispachGroup合作时–可能–处理问题.我错过了什么部分?

  10. xcode – Swift – 检索子视图

    解决方法UIViewController没有子视图属性.它有一个view属性,它有一个subviews属性:但通常这不是一个好主意.您应该将所需的标签放入IBOutletCollection并迭代它.否则,您与确切的子视图集密切相关.要创建IBOutletCollection,请在IB中选择所需的所有标签,然后将其控制拖动到源代码中.它应该询问您是否要创建一个集合数组.

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部