在 Swift 中,运算符重载是一种功能,允许开发者为自定义类型(如结构体、类或枚举)定义已有运算符的行为。这使得可以使用熟悉的运算符(如 +、-、*、/ 等)直接对自定义类型进行操作,从而简化代码的可读性和表达力。
定义运算符重载
要重载运算符,需要实现一个全局函数,该函数的名字是所需的运算符,并且至少有一个参数是自定义类型。
示例代码
重载 + 运算符
struct Vector {
var x: Double
var y: Double
static func + (lhs: Vector, rhs: Vector) -> Vector {
return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
}
let v1 = Vector(x: 1.0, y: 2.0)
let v2 = Vector(x: 3.0, y: 4.0)
let result = v1 + v2 // Vector(x: 4.0, y: 6.0)
定义了 Vector 类型的 + 运算符。
lhs 和 rhs 是左右两个操作数。
重载比较运算符
struct Point {
var x: Int
var y: Int
static func == (lhs: Point, rhs: Point) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
static func < (lhs: Point, rhs: Point) -> Bool {
return lhs.x < rhs.x || (lhs.x == rhs.x && lhs.y < rhs.y)
}
}
let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 1, y: 3)
print(p1 == p2) // false
print(p1 < p2) // true
这里重载了 == 和 < 运算符,用于比较两个点的相等性和排序。
重载自定义运算符
可以创建自己的运算符,并对其进行重载。
1、定义自定义运算符:
prefix operator ++
2、重载该运算符:
struct Counter {
var count: Int
static prefix func ++ (value: inout Counter) -> Counter {
value.count += 1
return value
}
}
var counter = Counter(count: 5)
let updatedCounter = ++counter // Counter(count: 6)
复合赋值运算符
可以重载 += 等复合运算符:
struct Vector {
var x: Double
var y: Double
static func += (lhs: inout Vector, rhs: Vector) {
lhs = Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
}
var v = Vector(x: 1.0, y: 2.0)
let increment = Vector(x: 3.0, y: 4.0)
v += increment // Vector(x: 4.0, y: 6.0)
支持自定义类型的 Comparable
通过重载 < 运算符,可以让自定义类型符合 Comparable 协议:
struct Point: Comparable {
var x: Int
var y: Int
static func < (lhs: Point, rhs: Point) -> Bool {
return lhs.x < rhs.x || (lhs.x == rhs.x && lhs.y < rhs.y)
}
}
let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 2, y: 1)
print(p1 < p2) // true
泛型运算符重载
可以使用泛型来重载运算符以支持多种类型:
func +<T: Numeric>(lhs: T, rhs: T) -> T {
return lhs + rhs
}
let sum = 3 + 4.5 // 结果是 7.5
运算符种类
在重载自定义运算符石,定义的是前缀运算符:
prefix operator ++
运算符根据其放置的位置可以分为三种:
1)前缀运算符(Prefix Operator): 运算符位于操作数之前,例如 -value、++value。
2)后缀运算符(Postfix Operator): 运算符位于操作数之后,例如 value++、value!。
3)中缀运算符(Infix Operator): 运算符位于两个操作数之间,例如 a + b。
prefix operator 的作用
prefix operator ++ 声明了一个新的前缀运算符 ++。此时,运算符仅仅被声明,但还没有实现任何功能。需要为该运算符定义一个实现。
定义和实现 ++ 前缀运算符
prefix operator ++
struct Counter {
var count: Int
// 实现 ++ 运算符
static prefix func ++ (value: inout Counter) -> Counter {
value.count += 1
return value
}
}
var counter = Counter(count: 5)
let updatedCounter = ++counter // Counter(count: 6)
print(counter.count) // 6
1、声明 prefix operator ++
这一步告诉 Swift,++ 是一个新的前缀运算符。
2、实现运算符逻辑
使用 static prefix func ++ 定义运算符的行为:
参数 value 是操作数。
使用 inout 修饰,表明该操作数可以被修改。
value.count += 1 表示操作数 Counter 的 count 属性加 1。
3、使用运算符
当使用 ++counter 时:
counter 的 count 属性加 1。
返回更新后的 Counter 对象。
运算符的使用范围
prefix operator 只能用于前缀操作,不支持作为中缀或后缀运算符使用。如果需要在后缀位置使用,则需要定义 postfix operator。
定义后缀运算符
postfix operator ++
struct Counter {
var count: Int
// 实现后缀 ++ 运算符
static postfix func ++ (value: inout Counter) -> Counter {
let oldValue = value
value.count += 1
return oldValue
}
}
var counter = Counter(count: 5)
let previousCounter = counter++ // Counter(count: 5)
print(counter.count) // 6
后缀运算符返回操作之前的值。
这是后缀运算符与前缀运算符的主要区别。
定义中缀运算符
infix operator >>>
struct Fang {
var name: String
static func >>> (left: Fang, right: Fang) -> String {
return left.name + right.name + "小火车"
}
}
var fang1 = Fang(name: "fang1")
var fang2 = Fang(name: "fang2")
print(fang1 >>> fang2) // fang1fang2小火车
中缀运算符与前缀和后缀运算符相比,没有prexfix或postfix,两个参数可以自定义名称,如left或right。
注意事项
1、不要滥用重载: 重载运算符应该具有明确的含义,否则会让代码难以理解。
2、避免歧义: 确保重载运算符的行为是直观的,并且不会与已有运算符行为冲突。
3、性能问题: 复杂的运算符重载可能会对性能产生影响,尤其是泛型或递归调用的情况。
通过合理使用运算符重载,可以让代码更简洁,同时增强可读性。
附录
Swift运算符名称的规则
在 Swift 中,运算符名称的定义必须遵循特定的规则。不符合 Swift 对运算符名称的要求,则会报错:
'fang' is considered an identifier and must not appear within an operator name
因为它被解析为一个普通的标识符(identifier),而不是合法的运算符名称。
1、运算符名称只能由特定的符号组成:
运算符必须使用 Swift 支持的 运算符字符,而不是普通的标识符字符。
可用作运算符的符号包括:
数学符号:+、-、*、/
比较符号:<、>、=
逻辑符号:&、|、!
其他符号:?、^、%、~
例如:+?、>>>、!== 是有效的运算符名称。
2、不能包含字母或数字:
像 fang、op1 这样的名称包含字母或数字,因此会被解析为普通标识符,而不是运算符名称。
3、运算符名称的作用:
Swift 中运算符的设计是为了增强代码的数学表达能力,而标识符(如 fang)更适合用于函数、变量等。