原文连接: www.raywenderlich.com/4018226-ove…html
在本Swift教程中,您将学习如何建立自定义运算符,重载现有运算符以及设置运算符优先级。git
运算符是任何编程语言的核心构建模块。 你能想象编程而不使用+或=吗? 运算符很是基础,大多数语言都将它们做为编译器(或解释器)的一部分进行处理。 可是Swift编译器并不对大多数操做符进行硬编码,而是为库提供了建立的操做符的方法。 它将工做留给了Swift标准库,用来提供您指望的全部常见操做符。 这种微妙的差别为巨大的定制潜力打开了大门。github
Swift运算符特别强大,由于您能够经过两种方式更改它们以知足您的需求:为现有运算符分配新功能(称为运算符重载),以及建立新的自定义运算符。 在本教程中,您将使用一个简单的Vector
结构并构建本身的一组运算符,以组合不一样的向量。编程
打开Xcode,而后经过File▶New▶Playground建立一个Playground。 选择空白模板并命名您的Playground为CustomOperators
。 删除全部默认代码,以便您能够从空白平板开始。swift
将如下代码添加到您的Playground:数组
struct Vector {
let x: Int
let y: Int
let z: Int
}
extension Vector: ExpressibleByArrayLiteral {
init(arrayLiteral: Int...) {
assert(arrayLiteral.count == 3, "Must initialize vector with 3 values.")
self.x = arrayLiteral[0]
self.y = arrayLiteral[1]
self.z = arrayLiteral[2]
}
}
extension Vector: CustomStringConvertible {
var description: String {
return "(\(x), \(y), \(z))"
}
}
复制代码
在这里,您能够定义一个含有三个属性的Vector
类型,它遵循两个协议。 CustomStringConvertible
协议和description
计算属性容许您打印一个友好的字符串用来表明Vector
。框架
在playground
的底部,添加如下代码:编程语言
let vectorA: Vector = [1, 3, 2]
let vectorB = [-2, 5, 1] as Vector
复制代码
在没有初始化器的状况下,你刚刚用了两个Array
来建立两个Vector
!那是如何发生的呢? ExpressibleByArrayLiteral
协议提供了一个平滑的接口来初始化Vector
。该协议须要一个具备可变参数的不可用初始化程序:init(arrayLiteral:Int ...)
ide
可变参数arrayLiteral
容许您传入由逗号分隔的无限数量的值。例如,您能够建立Vector
,像这样Vector(arrayLiteral:0)
或Vector(arrayLiteral:5,4,3)
。函数
该协议进一步方便,并容许您直接使用数组进行初始化,只要您明肯定义类型,这是您为vectorA
和vectorB
所作的。
这种方法的惟一警告是你必须接受任何长度的数组。若是您将此代码放入应用程序中,请记住,若是传入长度不是三的数组,它将会崩溃。若是您尝试初始化少于或多于三个值的Vector
,则初始化程序顶部的断言将在开发和内部测试期间在控制台中提醒您。
单独的矢量很好,但若是你能用它们作事情会更好。正如你在学校时所作的那样,你将开始你学习 加法 之旅。
运算符重载的一个简单示例是加法运算符。 若是您将它与两个数字一块儿使用,则会发生如下状况:
1 + 1 // 2
复制代码
可是,若是对字符串使用相同的加法运算符,则它具备彻底不一样的行为:
"1" + "1" // "11"
复制代码
当+与两个整数一块儿使用时,它会以算术形式把它们相加。 可是当它与两个字符串一块儿使用时,它会将它们链接起来。
为了使运算符重载,您必须实现一个名称为运算符符号的函数。
注意: 您能够将重载函数定义为类方法,这是您将在本教程中执行的操做。 这样作时,必须将其声明为static
函数,以即可以在没有定义它的实例的状况下访问它。
在playground
最后面添加以下代码:
// MARK: - Operators
extension Vector {
static func + (left: Vector, right: Vector) -> Vector {
return [
left.x + right.x,
left.y + right.y,
left.z + right.z
]
}
}
复制代码
此函数将两个向量做为参数,并将它们的和做为新向量返回。 为了让向量相加,只需组成向量的变量相加.
要测试此功能,请将如下内容添加到playground
的底部:
vectorA + vectorB // (-1, 8, 3)
复制代码
您能够在playground
的右侧边栏中看到向量相加的结果.
加法运算符是所谓的infx
运算符,意味着它在两个不一样的值之间使用。 还有其余类型的运算符:
infix
:在两个值之间使用,例如加法运算符(例如,1 + 1)prefix
:在值以前添加,如负号运算符(例如-3)。postfix
:在值以后添加,好比强制解包运算符(例如,mayBeNil!)ternary
:在三个值之间插入两个符号。 在Swift中,不支持用户自定义的三元运算符,只有一个内置的三元运算符,您能够在Apple的文档中阅读。 您要重载的下一个运算符是负号,它将更改Vector
的每一个变量的符号。 例如,若是将它应用于vectorA
,即(1,3,2)
,则返回(-1,-3,-2)
。在这个extension
中的前面一个static
函数下面添加以下代码:
static prefix func - (vector: Vector) -> Vector {
return [-vector.x, -vector.y, -vector.z]
}
复制代码
运算符默认是infix
类型,若是您但愿运算符是其余类型,则须要在函数声明中指定运算符类型。 负号运算符不是infix
类型,所以您将prefix
修饰符添加到函数声明中。
在playground
的底部,添加如下代码:
-vectorA // (-1, -3, -2)
复制代码
在侧栏中检查结果是否正确。
接下来是减法,我将留给你实现本身。 完成后,请检查以确保您的代码与个人代码相似。 提示:减法与加上一个负数相同。
试一试,若是您须要帮助,请查看下面的解决方案!
static func - (left: Vector, right: Vector) -> Vector {
return left + -right
}
复制代码
经过添加已下代码到playground
底部,测试你的新操做符的输出:
vectorA - vectorB // (3, -2, 1)
复制代码
您还能够经过标量乘法将向量乘以数字。 要将向量乘以2,能够将每一个份量乘以2。 你接下来就要实现这个.
您须要考虑的一件事是参数的顺序。 当你实现加法的时候,顺序可有可无,由于两个参数都是向量。
对于标量乘法,您须要考虑Int * Vector
和Vector * Int
。 若是您只实现其中一种状况,Swift编译器将不会自动知道您但愿它以其余顺序工做。
要实现标量乘法,请在刚刚添加的减法函数下添加如下两个函数:
static func * (left: Int, right: Vector) -> Vector {
return [
right.x * left,
right.y * left,
right.z * left
]
}
//这里用到了上面重载的*运算
static func * (left: Vector, right: Int) -> Vector {
return right * left
}
复制代码
为避免屡次写入相同的代码,第二个函数只是将其参数转发给第一个。
在数学中,向量有另外一个有趣的操做,称为叉乘。 叉乘超出了本教程的范围,但您能够在Cross product Wikipedia页面上了解有关它们的更多信息。
因为在大多数状况下不鼓励使用自定义符号(谁想在编码时打开表情符号菜单?),重复使用星号进行叉乘操做会很是方便。
与标量乘法不一样,叉乘将两个向量做为参数并返回一个新向量。
添加如下代码以在刚刚添加的乘法函数以后实现叉乘:
static func * (left: Vector, right: Vector) -> Vector {
return [
left.y * right.z - left.z * right.y,
left.z * right.x - left.x * right.z,
left.x * right.y - left.y * right.x
]
}
复制代码
如今,将如下计算添加到playground
的底部,同时利用乘法和叉乘运算符:
vectorA * 2 * vectorB // (-14, -10, 22)
复制代码
此代码找到vectorA
和2的标量乘法,而后找到该向量与vectorB
的交叉乘积。 请注意,星号运算符始终从左向右,所以前面的代码与使用括号分组操做相同,如(vectorA * 2)* vectorB
。
某些协议须要实现一些运算符符。 例如,符合Equatable
的类型必须实现==
运算符。 相似的,符合Comparable
的类型必须至少实现<
和==
,由于Comparable
继承自Equatable
。 Comparable
类型也能够选择实现>,>=和<=,但这些运算符具备默认实现。
对于Vector
,Comparable
并无太多意义,但Equatable
有意义,由于若是它们的组件所有相等,则两个向量相等。 接下来你将实现Equatable
。
去实现协议,请在playground
的末尾添加如下代码:
extension Vector: Equatable {
static func == (left: Vector, right: Vector) -> Bool {
return left.x == right.x && left.y == right.y && left.z == right.z
}
}
复制代码
添加如下代码在palyground
的底部,而且测试他的输出
vectorA == vectorB // false
复制代码
上述代码返回false
由于vectorA
和vectorB
有不一样的组件. 实现Equatable
协议,不只可以检查这些类型的相等性。 您还得到了自由访问contains(_:)
的矢量数组!
请记住咱们一般不鼓励使用自定义运算符. 固然该规则也有例外。
关于自定义符号的一个好的经验法则是,只有在知足如下条件时才应使用它们:
•
,您可使用键盘上的Option-8
轻松键入。 您可能会想,“我能够在本教程中对其余全部操做符执行相同的操做,对吧?”不幸的是,你还不能那样作。 在其余状况下,您重载已存在的运算符。 对于新的自定义运算符,您须要首先建立运算符。
直接在Vector实现最下面,但在CustomStringConvertible
扩展之上,添加如下声明:
infix operator •: AdditionPrecedence
复制代码
将•
定义为放在两个其余值之间的运算符,而且与加法运算符+
具备相同的优先级。 暂时忽略优先级,由于你会在学到它。
既然已经注册了此运算符,请在运算符扩展的末尾添加其实现,紧接在运算符*
的实现的下面:
static func • (left: Vector, right: Vector) -> Int {
return left.x * right.x + left.y * right.y + left.z * right.z
}
复制代码
添加下面代码到playground
测试他的输出:
vectorA • vectorB // 15
复制代码
到目前为止,一切看起来都不错......或者是吗? 在playground
的底部尝试如下代码:
vectorA • vectorB + vectorA // Error!
复制代码
如今,•
和+
具备相同的优先级,所以编译器从左到右解析表达式。 编译器将您的代码解释为:
(vectorA • vectorB) + vectorA
复制代码
此表达式归结为Int + Vector
,您还没有实现而且不打算实现。 你要如何来解决这个问题?
Swift中的全部运算符都属于一个优先级组,它描述了运算符的运算顺序。 还记得学习小学数学中的操做顺序吗? 这基本上就是你在这里所要处理的。 在Swift标准库中,优先顺序以下:
如下是关于这些运算符的一些注释,您以前可能历来没有看到过它们:
<<
和>>
用于二进制计算。is
和as
来肯定或更改值的类型。nil
合并运算符,??
为可选类型提供默认值。DefaultPrecedence
。? :
,相似于if-else语句。AssignmentPrecedence
,衍生出来的=
,在全部运算以后进行运算。 编译器解析类型具备左关联性,因此v1 + v2 + v3 ==(v1 + v2)+ v3
。操做符按它们在表中出现的顺序进行解析。 尝试使用括号重写如下代码:
v1 + v2 * v3 / v4 * v5 == v6 - v7 / v8
复制代码
当您准备好检查数学时,请查看下面的解决方案。
(v1 + (((v2 * v3) / v4) * v5)) == (v6 - (v7 / v8))
复制代码
在大多数状况下,您须要添加括号以使代码更易于阅读。 不管哪一种方式,理解编译器评估运算符的顺序都颇有用。
你新定义的dot-product
不适合任何这些类别。 它的优先级必须小于加号运算符(如前所述),但它是否真的适合CastingPrecedence
或RangeFormationPrecedence
?
取而代之,您将为您的点积运算符建立本身的优先级组。
用如下内容替换•
运算符的原始声明:
precedencegroup DotProductPrecedence {
lowerThan: AdditionPrecedence
associativity: left
}
infix operator •: DotProductPrecedence
复制代码
在这里,您建立一个新的优先级组并将其命名为DotProductPrecedence
。 您将它放在低于AdditionPrecedence
的位置,由于您但愿加号运算符优先。 你也能够将它设为左关联,由于你想要从左到右进行评估,就像加法和乘法中同样。 而后,将此新优先级组分配给•
运算符。
注意:除了lowerThan
以外,您还能够在DotProductPrecedence
中指定higherThan
。 若是您在单个项目中有多个自定义优先级组,这一点就变得很重要。 你以前写的代码返回了你但愿的结果:
vectorA • vectorB + vectorA // 29
复制代码
恭喜💐--你掌握了自定义运算符
你能够阅读本教程完整的代码,代码已经在最后面给出. 此时,您知道如何根据须要定义Swift操做符。 在本教程中,您专一于在数学领域中使用运算符。 在实践中,您将找到更多使用运算符的方法。
在ReactiveSwift框架中能够看到自定义运算符使用的一个很好的演示。 一个例子是<~
用来绑定,这是响应式编程中的一个重要功能。 如下是此运算符的使用示例:
let (signal, _) = Signal<Int, Never>.pipe()
let property = MutableProperty(0)
property.producer.startWithValues {
print("Property received \($0)")
}
property <~ signal
复制代码
Cartography是另外一个大量使用运算符重载的框架。 AutoLayout
工具重载相等和比较运算符,以使NSLayoutConstraint
建立更简单:
constrain(view1, view2) { view1, view2 in
view1.width == (view1.superview!.width - 50) * 0.5
view2.width == view1.width - 50
view1.height == 40
view2.height == view1.height
view1.centerX == view1.superview!.centerX
view2.centerX == view1.centerX
view1.top >= view1.superview!.top + 20
view2.top == view1.bottom + 20
}
复制代码
此外,您始终能够参考官方文档 custom operator documentation。
有了这些新的灵感来源,您能够走出世界,经过运算符重载使代码更简单。 当心不要对自定义运算符太迷恋!:]
extension Vector: ExpressibleByArrayLiteral {
init(arrayLiteral: Int...) {
assert(arrayLiteral.count == 3, "Must initialize vector with 3 values.")
self.x = arrayLiteral[0]
self.y = arrayLiteral[1]
self.z = arrayLiteral[2]
}
}
precedencegroup DotProductPrecedence {
lowerThan: AdditionPrecedence
associativity: left
}
infix operator •: DotProductPrecedence
extension Vector: CustomStringConvertible {
var description: String {
return "(\(x), \(y), \(z))"
}
}
let vectorA: Vector = [1, 3, 2]
let vectorB: Vector = [-2, 5, 1]
// MARK: - Operators
extension Vector {
static func + (left: Vector, right: Vector) -> Vector {
return [
left.x + right.x,
left.y + right.y,
left.z + right.z
]
}
static prefix func - (vector: Vector) -> Vector {
return [-vector.x, -vector.y, -vector.z]
}
static func - (left: Vector, right: Vector) -> Vector {
return left + -right
}
static func * (left: Int, right: Vector) -> Vector {
return [
right.x * left,
right.y * left,
right.z * left
]
}
static func * (left: Vector, right: Int) -> Vector {
return right * left
}
static func * (left: Vector, right: Vector) -> Vector {
return [
left.y * right.z - left.z * right.y,
left.z * right.x - left.x * right.z,
left.x * right.y - left.y * right.x
]
}
static func • (left: Vector, right: Vector) -> Int {
return left.x * right.x + left.y * right.y + left.z * right.z
}
}
vectorA + vectorB // (-1, 8, 3)
-vectorA // (-1, -3, -2)
vectorA - vectorB // (3, -2, 1)
extension Vector: Equatable {
static func == (left: Vector, right: Vector) -> Bool {
return left.x == right.x && left.y == right.y && left.z == right.z
}
}
vectorA == vectorB // false
vectorA • vectorB // 15
vectorA • vectorB + vectorA // 29
复制代码