类和结构体可以为现有的运算符提供自定义的实现。这通常被称为运算符重载。
下面的例子展示了如何让自定义的结构体支持加法运算符(+
)。算术加法运算符是一个二元运算符,因为它是对两个值进行运算,同时它还可以称为中缀运算符,因为它出现在两个值中间。
例子中定义了一个名为 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 为以下数种自定义类型提供等价运算符的默认实现:
只拥有存储属性,并且它们全都遵循 Equatable 协议的结构体
只拥有关联类型,并且它们全都遵循 Equatable 协议的枚举
没有关联类型的枚举
在类型原始的声明中声明遵循 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.”
<、<=、>、>=
func < (left: Vector3, right: Vector3) -> Bool {
if left.x != right.x{ return left.x < right.x }
if left.y != right.y{ return left.y < right.y }
if left.z != right.z{ return left.z < right.z }
return false
}
func <=(left: Vector3, right: Vector3) -> Bool {
return left < right || left == right
}
func > (left: Vector3, right: Vector3) -> Bool {
return !(left <= right)
}
func >=(left: Vector3, right: Vector3) -> Bool {
return !(left < right)
}
var a = [2,3,1,5]
a.sort(by: >) //按照从大到小排序