除了之前介绍过的基本运算符,swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了 C 和 OC 中已经被大家所熟知的位运算符和移位运算符。
与 C 语言中的算术运算符不同,swift 中的算术运算符默认是不会溢出的。所有溢出的行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(&+)。所有这些溢出运算符都是以 & 开头的。
自定义结构体、类和枚举时,如果也为它们提供标准 swift 运算符的实现,将会非常有用。在 swift 中自定义运算符非常简单,运算符也会针对不同类型使用对应实现。
我们不用被预定义的运算符所限制。在 swift 中可以自由地定义中缀、前缀、后缀和赋值运算符。以及相应的优先级与结合性。这些运算符在代码中可以像预定义的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义运算符。
位运算符
位运算符可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
按位取反运算符
按位取反运算符(~)可以对一个数值的全部比特位进行取反:
按位取反运算符是一个前缀运算符,需要直接放在运算的数之前,并且它们之间不能添加任何空格:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000
UInt8 类型的整数有 8 个比特位,可以存储 0~255 之间的任意整数。这个例子初始化了一个 UInt8 类型的整数,并赋值为二进制的 00001111,它的前四位都是 0,后 4 位都是 1。这个值等价于十进制的 15。接着使用按位取反运算符创建了一个名为 invertedBits 的常量,这个常量的值与全部按位取反后的 inteialBits 相等。即所有的 0 都变成了 1 ,同时所有的 1 都变成 0。invertedBits 的二进制值为 11110000,等价于无符号十进制数的 240。
按位与运算符
按位与运算符(&)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位数都为 1 的时候,新数的对应位才为 1:
按位或运算符
按位或运算符(|)可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 1 时,新数的对应位就为 1:
按位异或匀速符
按位异或运算符(^)可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 1:
按位左移、右移运算符
按位左移运算符(<<)和按位右移运算符(>>)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或 除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。
无符号整数的移位运算
对无符号整数进行移位的规则如下:
已经存在的位按指定的位数进行左移和右移
任何因移动而超出整型存储范围的位都会被丢弃
用 0 来填充移位后产生的空白位
有符号整数的移位运算
对比无符号整数,有符号整数的移位运算相对复杂的多,这种复杂性源于有符号整数的二进制表现形式。
有符号整数使用第 1 个比特位(通常称为符号位)来表示这个数的正负。符号位为 0 代表正数,为 1 代表负数。
其余比特位(通常称为数值位)存储实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 0 开始算起。
溢出运算符
在默认情况下,当向一个整数赋予超出它的容量的值时,swift 默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大和过小的数的时候提供了额外的安全性。
为过大和过小的数值提供错误处理,能让我们处理边界值时更加灵活。
然而,也可以选择让系统在数值溢出的时候采取截断处理,而非报错。可以使用 swift 提供的三个溢出运算符来让系统支持整数溢出运算。这些运算符都是以 & 开头的:
溢出加法 &+
溢出减法 &-
溢出乘法 &*
数值溢出
数值有可能出现上溢或者下溢。
这个示例演示了当我们对一个无符号整数使用溢出加法(&+)进行上溢运算时会发生什么:
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
unsignedOverflow = unsignedOverflow &+ 1
// 此时 unsignOverflow 等于 0
同样的,当我们对一个无符号整数使用溢出减法(&-)进行下溢运算也会发生类似的现象:
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
// 此时 unsignedOverflow 等于 255
溢出也会发生在有符号整型数值上。
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样的,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
优先级和结合性
运算符函数
类和结构体可以为现有的运算符提供自定义的实现,这通常被称为运算符重载。
下面的例子展示了如何为自定义的结构体实现加法运算符(+)。算术运算符是一个双目运算符,因为它可以对两个值进行运算,同时它还是中缀运算符,因为它出现在两个值中间。
例子中定义了一个名为 Vector2D 的结构体用来表示二维坐标向量(x, y),紧接着定义了一个可以对两个 Vector2D 结构体进行相加运算符函数:
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: inout 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 结构体的成员方法,所以任意两个 Vector2D 实例都可以使用这个中缀运算符。
前缀和后缀运算符
上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是前缀(例如 -a),而当它出现在值之后时,它就是后缀的(例如 b!)。
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 修饰符:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector3D(x: -vector,x, y: -vector.y)
}
}
这段代码为 Vector2D 实现了单目负号运算符。由于该运算符是前缀运算符,所以这个函数需要加上 prefix 修饰符。
对于简单数值,单目负号运算符可以对它们的正负性进行改变。对于 Vector2D 来说,该运算符将其 x 和 y 属性的正负性都进行了改变。
复合赋值运算符
复合赋值运算符将赋值运算符(=)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(+=)。在实现的时候,需要把运算符的左参数设置成 inout 类型,因为这个参数的值会在运算符函数内直接被修改。
注意
不能对默认的赋值运算符(=)进行重载。同样地,也无法对三目条件运算符(a?b:c)进行重载。
等价运算符
自定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为 "相等" 运算符(==)与 “不等” 运算符(!=)。对于自定义类型,swift 无法判断其是否 “相等”,因为 “相等” 的含义取决于这些自定义的类型在你的代码中所扮演的角色。
为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样:
extension Vector2D {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == reght.x) && (left.y == right.y)
}
static func != (left: Vector2D, right: Vector2D) -> Bool {
return !(left == right)
}
}
上述代码实现了 “相等” 运算符(==)来判断两个 Vector2D 实例是否相等。对于 Vector2D 类型来说,“相等” 意味着“两个实例的 x 属性和 y 属性都相等”, 这也是代码中用来进行判断的逻辑。示例里同时也实现了 “不等” 运算符(!=),它简单地将 “相等” 运算符的结果进行取反后返回。
自定义运算符
除了实现标准运算符,在 swift 中还可以声明和实现自定义运算符。
新的运算符要使用 operator 关键字在全局作用域内进行定义,同时还要指定 prefix、infix 或者 postfix 修饰符:
prefix operator +++ {}
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
自定义中缀运算符的优先级
...
END