今天我们来谈一谈Swift中的操作 符重载,这一功能非常实用,但是也相当有风险。正所谓“能力越大责任越大”,这句话用来形容操作符重载最合适不过了。它可以令你的代码更加简洁,也可以让 一个函数调用变得又臭又长。而对于那些没怎么读过你的代码的人来说,操作符的使用同时也会让代码的可读性大打折扣。


慎引入,按需使用。比如在连接两个字串的时候你就可以通过重载加法来实现。甚至于你仅在屏幕上输入一个加号,就能响应一个网络链接、播放一段音乐或者完成
你能实现的其他任何功能。然而过于复杂的功能对编码来说简直就是灾难,合理的做法就是仅在必要时重载操作符,不做任何多余的事。如果你非要做点奇怪的事,
那就为网络响应这样的功能取一个明显又合适的函数名。

无论你何时想采用运算符重载,你都需要考虑一下是否值得重载它而不是用一个函数调用。函数调用或许颇费周折,但更能确切的表达它的功能(如果函数名恰到好处的话)。假如你要频繁做类似于加减乘除赋值比较的操作,那么运算符重载的确是个很合适的解决方案。

运算符重载

在Swift里面你应该就像平时使用运算符那样来使用重载算符。虽然仍然有些不足的地方,不过下面的这个例子足够说明重载运算符的使用了。这段代码计算的是两个NSDate对象间的差值,使用了“laterDate–initialDate” 的形式:

func - (left: NSDate, right: NSDate) -> NSDateComponents
{
    let mostUnits: NSCalendarUnit = .YearCalendarUnit | .MonthCalendarUnit | .DayCalendarUnit | .HourCalendarUnit | .MinuteCalendarUnit | .SecondCalendarUnit
    
    let components = NSCalendar.currentCalendar().components(mostUnits, fromDate: right, toDate: left, options:nil)
    
    return components
}

在这个函数中,第一行看上去很多,然而mostUnits表示的不过是我们所关心的日期单元(NSCalendarUnit)的清 单,在这里我选择关注的是年、月、日、时、分、秒。值得注意的是这里并非只有这些日期单元的Flag,可选的还有很多,如果我想知道两个日期之间相隔的周 数我一样可以用别的Flag来替换掉上面这些,使用这些不同Flag可以满足我们对日期计算的大部分需求。往下的一行中,使用了当前时间 NSCalendar.currentCalendar()的components()方法创建了一个NSDateComponents对象,之后在方法 最后返回了这个对象components.

在两个值之间的运算成为中缀运算,运算重载还有后缀运算和前缀运算两种。比如++i和i++分别就是前缀和后缀运算。

如你所见,Swift中所谓重载不过是挂羊头卖狗肉,实际上也只是一个函数调用而已,只不过函数名变成了符号。我们指定了表达式中左右参数的类型,并且还指定了计算结果返回的数据类型必须是一个NSDateComponents对象,下面是代码:

let initialDate = NSDate()
let components = NSDateComponents()
components.year = 1
components.month = 5
components.minute = 42
let laterDate = NSCalendar.currentCalendar().dateByAddingComponents(components, toDate: initialDate, options: nil)!
//Test the overloaded operator
let difference = laterDate - initialDate
//Results
println(difference.year)    //Outputs: "1"
println(difference.month)   //Outputs: "5"
println(difference.day)     //Outputs: "0"
println(difference.hour)    //Outputs: "0"
println(difference.minute)  //Outputs: "42"
println(difference.second)  //Outputs: "0"

这里有一些设置,不过大部分很好理解。我们先 初始化一个日期数据,它的值就是这个NSDate对象的创建日期,另外还有一个NSDateComponents对象包含了它自己的年月和分钟的属性值。 之后使用NSCalendar的dateByAddingComponents方法设置laterDate为1年5个月又42分钟之后的时间。然后测试重 载后的新功能“-”操作符,你会看到最后通过减法输出的结果刚好是我们设置的时间差。

我认为这样的重载还是有点用的,我们可以像平时使用数 学减法那样来计算。然而,这里隐藏某些假定的条件:首先我已经提到过关于NSCalendarUnit的Flag只是使用了一些而非全部;其次,我在重载 函数里明确使用了NSCalendar的当期日期(currentCalendar)在这里没有显示。最后它把计算结果difference的属性值都设 为了0,但我们其实并没有想使用其他的属性。不过就我所知,某些人也许会想到封装所有的属性来解决。

如果使用了普通的函数调用来代替重载运 算符,你可以很清楚使用了哪种格式的日期,选中哪些Flag,以及在diffrence里面可以发现哪些属性被设置过。这就是为什么我说重载运算符只是 “有点用”。原因就是它对代码的读者而言,想要知道函数中究竟隐藏了哪些假定条件的话只能扒开重载运算符的源码看了。

Equatable协议

Swift中的重载运算符里面还是有一些比较不错的地方。你可以通过重载"=="算符来实现对自定义类的比较。假如你想实现这一功能你就需要使用Equatable协议,Equatable协议主要应用于泛型编程(有关Swift的泛型编程可以参考这里)。如果你按照Equatable协议重载了"=="操作符,那么你无需再重载"!="操作符(因为“!=”的重载就是"=="的逻辑否)。

重载"=="的方式与其他的中缀运算的重载方式一致,就像我们在上面所提到的"-"重载一样:

//Globally scoped operator function
func == (left: Temperature, right: Temperature) -> Bool
{
    if left.C == right.C
    {
        return true
    }
    else
    {
        return false
    }
}
//Custom type itself
struct Temperature:Equatable
{
    let C: Double
}
//Test
let tempOne = Temperature(C: 15)
let tempTwo = Temperature(C: 35)
let tempThree = Temperature(C: 15)
let OneTwoEquality = (tempOne == tempTwo)       //Stores:   false
let OneThreeEquality = (tempOne == tempThree)   //Stores:   true

现在我们可以使用“==”重载运算来比较两个Temperature实例了。如果要重载一个算符那么它必须要有一个对应的全局函数,甚至定义在类型之外。

Swift 中关于运算重载还有一个很不错的地方就是Comparable协议。如同遵照Equatable协议一样你在照着Comparable协议实现的时候你需 要重载一个“<”方法。另外如果你重载了"=="和">"运算符之后编译器会为你计算出其它几个运算符"!="、"<"、"& lt;="和">="的判定。可以看这里的一个例子,它同样也是Equatable协议的例子。

总结

本文中所有代码的测试环境均为Xcode 6.0.1。

前面我讨论了一下NSDate的减法,是因为它在被调用时隐藏了不少的假定条件。在这里提醒一下:当你在考虑编码中是否要实现重载运算符的时候可以想想这个示例。如果你想在代码里一直使用相同的unitFlag来重载实现日期的减法,你需要多写一些代码来考虑多种情况。

如果你想重载内建类型以及不想修改(或者无法修改)的类型的运算符重载,你可以在扩展里面添加这个重载函数。且这个重载函数需要为全局作用域,并放在你随便哪个扩展存放的文件里面。

有一些运算符是不能重载的,最为明显的就是赋值运算"="(一个等号)。就算你重载了,想一想赋值运算的使用频率,我不会对产生的错误有任何兴趣的。这里有Tammo Freese同学的一篇文章,你可以了解一下哪些运算符是不可以被重载的。

再议Swift操作符重载的更多相关文章

  1. c++ 操作符重载和友元

    操作符重载(operator overloading)是C++中的一种多态,C++允许用户自定义函数名称相同但参数列表不同的函数,这被称为函数重载或函数多态.操作符重载函数的格式一般为: operat ...

  2. [置顶] operator overloading(操作符重载,运算符重载)运算符重载,浅拷贝(logical copy) ,vs, 深拷贝(physical copy)

    operator overloading(操作符重载,运算符重载) 所谓重载就是重新赋予新的意义,之前我们已经学过函数重载,函数重载的要求是函数名相同,函数的参数列表不同(个数或者参数类型).操作符重 ...

  3. C++基础学习笔记----第十三课(操作符重载-下)

    本节主要讲使用成员函数重载操作符,包括[],=,(),->四种操作符的重载以及&&和||的问题. 类的成员函数进行操作符重载 基本概念 类的成员函数也可以进行操作符的重载.类的普 ...

  4. C++ operator overload -- 操作符重载

    C++ operator overload -- 操作符重载 2011-12-13 14:18:29 分类: C/C++ 操作符重载有两种方式,一是以成员函数方式重载,另一种是全局函数. 先看例子 # ...

  5. C#构造函数、操作符重载以及自定义类型转换

    构造器 构造器(构造函数)是将类型的实例初始化的特殊方法.构造器可分为实例构造器和类型构造器,本节将详细介绍有关内容. 实例构造器 顾名思义,实例构造器的作用就是对类型的实例进行初始化.如果类没有显示 ...

  6. 15.C++-操作符重载

    首先回忆下以前学的函数重载 函数重载 函数重载的本质为相互独立的不同函数 通过函数名和函数参数来确定函数调用 无法直接通过函数名得到重载函数的入口地址 函数重载必然发生在同一个作用域中 类中的函数重载 ...

  7. 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- 操作符重载和隐式类型转换

    C#中,某些类型会定义隐式类型转换和操作符重载.Unity中,有些对象也定义了隐式类型转换和操作符重载.典型情况有:UnityEngine.Object.UnityEngine.Object的销毁是调 ...

  8. 5.1 C++基本操作符重载

    参考:http://www.weixueyuan.net/view/6379.html 总结: 操作符重载指的是将C++提供的操作符进行重新定义,使之满足我们所需要的一些功能. 长度运算符“sizeo ...

  9. 15.C++-操作符重载、并实现复数类

    首先回忆下以前学的函数重载 函数重载 函数重载的本质为相互独立的不同函数 通过函数名和函数参数来确定函数调用 无法直接通过函数名得到重载函数的入口地址 函数重载必然发生在同一个作用域中 类中的函数重载 ...

随机推荐

  1. PHP 文件打开/读取

    PHP Open File - fopen() 打开文件的更好的方法是通过 fopen() 函数.此函数为您提供比 readfile() 函数更多的选项. 在课程中,我们将使用文本文件 "w ...

  2. 删除CentOS / RHEL的库和配置文件(Repositories and configuraiton files)

    1 删除库配置文件 以root权限执行以下的命令: # cd /etc/yum.repos.d/ 列出全部库(repo) #ls CentOS-Base.repo epel.repo mirrors- ...

  3. linux下php扩展curl的安装

    方法一 安装cURL wget http://curl.haxx.se/download/curl-7.17.1.tar.gz tar -zxf curl-7.17.1.tar.gz ./config ...

  4. Log4Net配置 转

    http://www.cnblogs.com/qingyi/archive/2010/01/14/1647915.html 用一些东西老是忘记,先记在这啦.. <!--log4net相关说明一. ...

  5. NET基础课--JIT编译器如何工作1

    1..Net运行时调用JIT编译器,用来把由C#编译器生成的IL指令编译成机器代码.这一任务在应用程序的运行期间是分步进行的.JIT并不是在程序一开始就编译整个应用程序,取而代之的是,CLR是一个函数 ...

  6. 未找到具有固定名称“System.Data.SQLite”的 ADO.NET 提供程序的实体框架提供程序

    用户代码未处理 System.InvalidOperationException   HResult=-2146233079   Message=未找到具有固定名称"System.Data. ...

  7. win7+IE11 中开发工具报错occurredJSLugin.3005解决办法

    系统环境 win7+IE11 报错描述: Exception in window.onload: Error: An error has ocurredJSPlugin.3005 Stack Trac ...

  8. Android中使用开源框架android-image-indicator实现图片轮播部署

    之前的博文中有介绍关于图片轮播的实现方式,分别为(含超链接): 1.<Android中使用ViewFlipper实现屏幕切换> 2.<Android中使用ViewPager实现屏幕页 ...

  9. 【ecos学习3】redboot on vmware 网络配置

    需要图形化编译的时候必须 Build->Packages 加入:Common ethernet support 才可以有网络功能 导入vmare需要修改网络连接模式: 1- 设置redbootI ...

  10. Retrofit的使用

    参照文档:http://gank.io/post/56e80c2c677659311bed9841 一.创建Retrofit mRetrofit = new Retrofit.Builder() .b ...