优先级和结合性
运算符的优先级使得一些运算符优先于其他运算符;它们会先被执行。
结合性定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将其理解
为“它们是与左边的表达式结合的”,或者“它们是与右边的表达式结合的”。
当考虑一个复合表达式的计算顺序时,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个
表达式的运算结果会是 17 。
2+3%4*5 // 结果是 17
如果你直接从左到右进⾏运算,你可能认为运算的过程是这样的:
2 +3 =5
5 %4 = 1
1 * 5 =5
但是正确答案是 17 而不是 5 。优先级高的运算符要先于优先级低的运算符进⾏计算。与 C 语言类似,在 Swift 中, 乘法运算符
( * )与取余运算符( % )的优先级高于加法运算符( + )。因此,它们的计算顺序要先于加法运算。
⽽乘法运算与取余运算的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性。乘法运算与取余运算都是左结合的。可
以将这考虑成,从它们的左边开始为这两部分表达式都隐式地加上括号:
2 + ((3 % 4) * 5)
(3 % 4) 等于 3 ,所以表达式相当于:
2 + (3 * 5)
3 * 5 等于 15 ,所以表达式相当于:
2 + 15
因此计算结果为 17 。
有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结核性设置的完整列表,请参见《操作符声明》。
注意
相对 C 语⾔和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语⾔
及其衍生语言并不是完全一致。在对现有的代码进⾏移植的时候,要注意确保运算符的⾏为仍然符合你的预期。
运算符函数
类和结构体可以为现有的运算符提供自定义的实现。这通常被称为运算符重载。
下⾯的例子展示了如何让自定义的结构体支持加法运算符( + )。算术加法运算符是一个二元运算符,因为它是对两个值进⾏运
算,同时它还可以称为中缀运算符,因为它出现在两个值中间。
例子中定义了一个名为 Vector2D 的结构体用来表示二维坐标向量 (x, y) ,紧接着定义了一个可以将两个Vector2D 结构体实例进
⾏相加的运算符函数:
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
该运算符函数被定义为 Vector2D 上的一个类⽅法,并且函数的名字与它要进⾏重载的 + 名字一致。因为加法运算并不是一个向
量必需的功能,所以这个类方法被定义在 Vector2D 的一个扩展中,⽽不是 Vector2D 结构体声明内。⽽算术加法运算符是二元
运算符,所以这个运算符函数接收两个类型为 Vector2D 的参数,同时有一个 Vector2D 类型的返回值。
在这个实现中,输入参数分别被命名为 left 和 right ,代表在 + 运算符左边和右边的两个 Vector2D 实例。函数返回了一个新的
Vector2D 实例,这个实例的 x 和 y 分别等于作为参数的两个实例的 x 和 y 的值之和。
这个类方法可以在任意两个 Vector2D 实例中间作为中缀运算符来使用:
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
这个例子实现两个向量 (3.0,1.0) 和 (2.0,4.0) 的相加,并得到新的向量 (5.0,5.0) 。这个过程如下图示:
前缀和后缀运算符
上个例子演示了一个二元中缀运算符的自定义实现。类与结构体也能提供标准⼀元运算符的实现。⼀元运算符只运算⼀个值。当
运算符出现在值之前时,它就是前缀的(例如 -a ),而当它出现在值之后时,它就是后缀的(例如 b! )。
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 修饰 符:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
这段代码为 Vector2D 类型实现了一元运算符( -a )。由于该运算符是前缀运算符,所以这个函数需要加上prefix 修饰符。
对于简单数值,一元负号运算符可以对它们的正负性进⾏改变。对于 Vector2D 来说,该运算将其 x 和 y 属性的正负性都进行了
改变:
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
let alsoPositive = -negative
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
复合赋值运算符
复合赋值运算符将赋值运算符( = )与其它运算符进⾏结合。例如,将加法与赋值结合成加法赋值运算符( += )。在实现的时候,需
要把运算符的左参数设置成 inout 类型,因为这个参数的值会在运算符函数内直接被修改。
在下面的例子中,对 Vector2D 实例实现了一个加法赋值运算符函数:
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利⽤现有的加法运算符函数,用它来对左值和右值
进⾏相加,并再次赋值给左值:
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 的值现在为 (4.0, 6.0)
注意
不能对默认的赋值运算符( = )进⾏重载。只有复合赋值运算符可以被重载。同样地,也无法对三元条件运算符 ( a ? b :c ) 进行重
载。
等价运算符
通常情况下,自定义的类和结构体没有对等价运算符进⾏默认实现,等价运算符通常被称为相等运算符( == )与不等运算符( !=
)。
为了使用等价运算符对自定义的类型进⾏判等运算,需要为“相等”运算符提供自定义实现,实现的方法与其它中缀运算符一样,
并且增加对标准库 Equatable 协议的遵循:
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
}
上述代码实现了“相等”运算符( == )来判断两个 Vector2D 实例是否相等。对于 Vector2D 来说,“相等”意味着“两个实例的 x 和 y
都相等”,这也是代码中用来进⾏判等的逻辑。如果你已经实现了“相等”运算符,通常情况下你并不需要⾃己再去实现“不等”运
算符( != )。标准库对于“不等”运算符提供了默认的实现,它简单地将“相等”运算符的结果进⾏取反后返回。
现在我们可以使用这两个运算符来判断两个 Vector2D 实例是否相等:
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// 打印“These two vectors are equivalent.”
多数简单情况下,您可以使用 Swift 为您提供的等价运算符默认实现。Swift 为以下数种自定义类型提供等价运算符的默认实现:
1.只拥有存储属性,并且它们全都遵循 Equatable 协议的结构体
2.只拥有关联类型,并且它们全都遵循 Equatable 协议的枚举
3.没有关联类型的枚举
在类型原始的声明中声明遵循 Equatable 来接收这些默认实现。
下⾯为三维位置向量 (x, y, z) 定义的 Vector3D 结构体,与 Vector2D 类似。由于 x , y 和 z 属性都是 Equatable 类型,
Vector3D 获得了默认的等价运算符实现。
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent.")
}
// 打印“These two vectors are also equivalent.”
⾃定义运算符
除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。可以用来自定义运算符的字符列表请参考《运算符》。 新的
运算符要使用 operator 关键字在全局作用域内进⾏定义,同时还要指定 prefix 、 infix 或者 postfix 修饰符:
prefix operator +++
上面的代码定义了一个新的名为 +++ 的前缀运算符。对于这个运算符,在 Swift 中并没有已知的意义,因此在针对Vector2D 实
例的特定上下文中,给予了它⾃定义的意义。对这个示例来讲, +++ 被实现为“前缀双自增”运算符。它使用了前面定义的复合
加法运算符来让矩阵与自身进⾏相加,从而让 Vector2D 实例的 x 属性和 y 属性值翻倍。
你可以像下面这样通过对 Vector2D 添加一个 +++ 类方法,来实现 +++ 运算符:
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled 现在的值为 (2.0, 8.0)
// afterDoubling 现在的值也为 (2.0, 8.0)
自定义中缀运算符的优先级
每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。《优先级和
结合性》中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。 以下例子定义
了一个新的自定义中缀运算符 +- ,此运算符属于 AdditionPrecedence 优先组:
infix operator +-: AdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
这个运算符把两个向量的 x 值相加,同时从第一个向量的 y 中减去第二个向量的 y 。因为它本质上是属于“相加型”运算符,所以
将它放置在 + 和 - 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组
和结合性设置,请参考《运算符声明》。⽽更多关于优先级组以及自定义操作符和优先级组的语法,请参考《运算符声明》。
注意
当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使⽤前缀与后缀运算符,则后缀运算符会
先参与运算。