高级运算符(Advanced Operators)
1. 位运算符
位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,使用它可以单独操作数据结构中原始数据的比特位。在使用一个自定义的协议进行通信的时候,运用位运算符来对原始数据进行编码和解码也是非常有效的。
Swift支持如下所有C语言的位运算符:
按位取反运算符
按位取反运算符(~)
取反所有位:
按位取反运算符是前缀运算符,紧接在其操作数之前,不使用任何空格:
let initialBits: UInt8 = 0b00001111 //相当于十进制的15
let invertedBits = ~initialBits // equals 11110000(相当于十进制的240)
按位与运算符
按位与运算符(&)
对两个操作数进行计算,然后返回一个新的数,这个数的每个位都需要两个操作数的同一位都为1
时才为1
。
let firstSixBits: UInt8 = 0b1111_1100//相当于十进制的252
let lastSixBits: UInt8 = 0b0011_1111//相当于十进制的63
let middleFourBits = firstSixBits & lastSixBits // equals 0011_1100(相当于十进制的60)
按位或运算符
按位或运算符(|)
对两个操作数进行计算,然后返回一个新的数,这个数的每个位只需要两个操作数的同一位任何一个为1
时就为1
。
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // equals 11111110
按位异或运算符
按位异或运算符(|)
对两个操作数进行计算,然后返回一个新的数,这两个操作数的同一位不相同返回1
,相同返回0
.
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // equals 00010001
按位左移/右移运算符
左移运算符<<
和右移运算符>>
会把一个数的所有位向左或向右移动指定位数。
按位向左移一位相当于将这个数乘以2;按位向右移一位相当于将这个数除以2。
-
无符整型的移位操作
操作规则如下:- 1.已经存在的位向左或向右移动指定的位数;
- 2.被移出整型存储边界的的位数直接抛弃;
- 3.移动留下的空白位用零0来填充。
这种方法称为逻辑移位。
- 下图显示了
1111_1111 << 1
(1111_1111按1位置向左移动),蓝色数字被移位,灰色数字被丢弃,并插入橙色零:
let shiftBits: UInt8 = 0b1111_1111
shiftBits << 1 // 1111_1110
- 下图显示了`1111_1111 >> 1`(1111_1111按1位置向右移动),蓝色数字被移位,灰色数字被丢弃,并插入橙色零:
let shiftBits: UInt8 = 0b1111_1111
shiftBits >> 1 // 0111_1111
-
有符整型的移位操作
有符整型的正负号使用首位(符号位)表示(0为正,1为负),所以移位操作相对无符整型复杂得多。
符号位为0
,代表正数,另外7比特位二进制表示的实际值就刚好是4
。
负数呢,跟正数不同。负数存储的是2的n次方减去它的绝对值,n为数值位的位数。一个8位的数有7个数值位,所以是2的7次方,即128。现在符号位为1,代表负数,7个数值位要表达的二进制值是124,即128 - 4。
负数的编码方式称为二进制补码表示。它有以下优点:
-
- 只需要对全部8个位(包括符号)做标准的二进制加法就可以完成
-1 + -4
的操作,忽略加法过程产生的溢出8位表达的任何信息。如下,
- 只需要对全部8个位(包括符号)做标准的二进制加法就可以完成
-
2.二进制补码表示的负数可以像正数一样按位左移右移的,且左移1位时乘于2,右移1位时除于2;但对有符整型按位右移时,使用符号位(正数为0,负数为1)填充空白位。
为确保有符号整数在向右移位后符号保持不变的操作称为算术移位。
蓝色数字被移位,灰色数字被丢弃,并插入橙色零:
-
2. 溢出运算符
默认情况下,Swift在你向一个整型常量或变量赋于一个它不能承载的数值时会报错。但使用溢出运算符可以在你使用太大或太小的数字时提供额外的安全性。
例如,Int16
整型能承载的整数范围是-32768
到32767
,如果给它赋上超过这个范围的数,就会报错:
三种溢出运算符:
- 溢出加法
(&+)
- 溢出减法
(&-)
- 溢出乘法
(&*)
值溢出
数字可以在正方向和负方向上溢出。
对于有符号和无符号整数,在正方向上的溢出的最大有效整数值变成最小值,在负方向上的溢出的最小值变成最大值。
- 无符号整数正方向溢出
下面是一个无符号整数允许在正方向溢出时,使用溢出加法运算符(&+)
的例子:
var unsignedOverflow = UInt8.max
// unsignedOverflow equals 255, which is the maximum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &+ 1
// unsignedOverflow is now equal to 0
变量unsignedOverflow
以UInt8
最大值(255或11111111二进制)初始化。然后使用溢出加法运算符(&+)增加1
。得到的结果将超过了UInt8可以容纳的大小,导致溢出边界,仅保留在UInt8溢出加法之后的边界内的00000000值为0,如下图所示:
- 无符号整数负方向溢出
当无符号整数允许在负方向上溢出时,与正方向溢出类似。使用溢出减法运算符(&-)
的例子:
var unsignedOverflow = UInt8.min
// unsignedOverflow equals 0, which is the minimum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &- 1
// unsignedOverflow is now equal to 255
变量unsignedOverflow
以UInt8
最小值(0或0000_0000二进制)初始化。然后使用溢出减法运算符(&-)减去1
。得到的结果将超过了UInt8可以容纳的大小,导致溢出边界,仅保留在UInt8溢出加法之后的边界内的1111_1111值为255,如下图所示:
-
有符号整数溢出
有符号整数的所有加法和减法以按位方式执行,符号位作为数字的一部分被添加或减去。
3. 运算符函数
让已有的运算符也可以对自定义的类和结构进行运算,这称为运算符重载。
这个例子展示了如何用+
让一个自定义的结构做加法。算术运算符+
是一个两目运算符,因为它有两个操作数,而且它必须出现在两个操作数之间。
例子中定义了一个名为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)
}
}
//调用
let ve1:Vector2D = Vector2D.init(x: 1.3, y: 3.3)
let ve2:Vector2D = Vector2D(x: 1.3, y: 3.3)
let ve3:Vector2D = ve1 + ve2
print(ve3.x,ve3.y) //2.6 6.6
前缀运算符
单目运算符只有一个操作数,在操作数之前就是前置的,如-a; 在操作数之后就是后置的,如i! 。
实现一个前缀或后缀运算符时,在定义该运算符的时候于关键字func之前标注 @prefix
(前缀) 或 @postfix
(后缀) 属性。
一元减运算符是前缀运算符,因此此方法必须使用prefix
修饰符限定。
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
//调用
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)
组合赋值运算符
组合赋值是其他运算符和赋值运算符一起执行的运算。例如,加法赋值运算符(+=)
将加法和赋值合并到单个操作中。复合赋值运算符的左输参数类型标记为inout
,因为该参数的值将在运算符方法内直接修改。
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)
}
}
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
//调用
var ve1 = Vector2D(x: 1.0, y: 2.0)
let ve2 = Vector2D.init(x: 1.3, y: 3.3)
ve1 += ve2
print(ve1.x,ve1.y)//2.3 5.3
注意:不能重载赋值操作符(=)。只有复合赋值运算符可以重载。类似地,三元条件运算符(a ? b : c)不能被重载。
比较运算符
Swift不能知道自定义的类和结构是否相等或不等,所以自定义的类和结构要使用比较符==
或!=
就需要重载。
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func == (left: Vector2D, right: Vector2D) -> Bool {
//left和right中的x和y都分别相等返回true
return (left.x == right.x) && (left.y == right.y)
}
static func != (left: Vector2D, right: Vector2D) -> Bool {
//left和right中的x和y都有一组不相等返回true
return !(left == right)
}
}
//调用
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.")
}
// Prints "These two vectors are equivalent."
自定义运算符
除了Swift提供的标准运算符之外, 还可以声明和实现自己的自定义运算符。但自定义运算符的只能使用 /,=,-,+,!,*,%,<,>,&,|,^,?,~字符,或以点(.)开头的自定义运算符。如果操作符不以点(.)开头,则其他位置不能包含点(.)。
虽然可以定义包含问号(?)的自定义运算符,但它们不能仅由一个问号(?)字符组成。此外,虽然操作符可以包含感叹号(!),但是后缀操作符不能以问号(?)或感叹号(!)开头。
运算符周围的空格用于确定运算符是用作前缀运算符、后缀运算符还是二元运算符。总结规则如下:
- 如果操作符在两侧都有空格或两侧都没有空格,则将其视为二元运算符。示例,
+++
运算符in a+++b
和a +++ b
被视为二元运算符。 - 如果操作符只有在左侧有空格,它被视为前缀一元运算符。示例,
+++
运算符in a +++b
被视为前缀一元运算符。 - 如果操作符只在右侧有空格,它被视为后缀一元运算符。示例,
+++
操作符a+++ b
被视为后缀一元运算符。 - 如果操作符左侧没有空格,但后面紧跟一个点(.),它被视为后缀一元运算符。示例,
+++
操作符in a+++.b
被视为后缀一元运算符(a+++ .b
而不是a +++ .b
)。
注意:以上规则需注意一点,如果运算符!
或?
左侧没有空格,则无论其右侧是否有空格都将被视为后缀运算符。如果将?
用作可选类型(optional type
)修饰,左侧必须无空格。在三目条件(? :)运算符中使用,必须在两边都有空格。
自定义运算符示例
新的运算符声明需在全局域使用operator
关键字声明,使用 prefix
(前置)、或infix
(中置)、或postfix
(后置)关键字修饰。
//前缀运算符+++,被视为新的“前缀加倍”运算符
prefix operator +++
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)
}
}
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
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 now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)
自定义中置运算符的优先级和结合性
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 is a Vector2D instance with values of (4.0, -2.0)
注意:在定义前缀或后缀运算符时,不需要指定优先级。但是,如果将前缀和后缀运算符应用于同一个操作数,则后缀运算符优先级更高。
参考链接1
参考链接2
用来记录平时遇到的问题,不对之处还望指教。