-
2016-07-14 07:24:07 yangmeng13930719363 阅读数 2445
-
高级运算符
文档地址
作为 基本运算符 的补充,Swift 提供了几个高级运算符执行对数传值进行更加复杂的操作。这样写运算包括所有你从 C 或 Objective-C 所熟悉的按位操作和移位运算符。
与 C 的算术运算符不同,Swift 中算术运算符默认是不会溢出的。溢出行为都会作为错误被捕获。为了允许溢出行为,可以使用 Swift 中另一套默认支持的溢出运算符,比如溢出加法运算符(
&+
)。所有这些溢出运算符都是以&
符号开始的。当你定义了你自己的结构体,类以及枚举的时候,那么为这些自定义类型也提供 Swift 标准的运算符将会有用的。Swift 简化了这些运算符的定制实现,并且精确地确定了你创建的每个类型的运算符所具有的行为。
你不会被预定义的运算符所限制。在 Swift 中你可以自由地定义你自己的中缀,前缀,后缀和赋值运算符,以及相对应的优先级和结合性。这些运算符可以像预先定义的运算符一样在你的代码里使用,甚至你可以扩展已存在的类型来支持你自己定义的运算符。
按位运算符
按位运算符可以操作数据结构中每一个独立的位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。按位运算符在处理外部资源的原始数据时也非常有用,比如为自定义的通信协议的数据进行编码和解码。
Swift 支持 C 里面所有的按位运算符,具体如下:
按位取反运算符
按位取反运算符(
~
)是对所有位的数字进行取反操作:按位取反运算符是一个前缀运算符,需要直接放在运算符的前面,并且不能有空格:
let initialBits: UInt8 = 0b00001111 let invertedBits = ~initialBits // equals 11110000
UInt8
类型的整数有八位,可以存储0
到255
之间的任意值。这个例子初始化了一个UInt8
类型的整数,二进制为00001111
,前四位全是0
,后四位都是1
。这和十进制的15
是相等的。然后使用取反运算符创建一个新的常量名为
invertedBits
,它和initialBits
相等,但是所有位都被取反了。0
变为了1
,1
变为了0
。invertedBits
的值是11110000
,和十进制的无符号整数240
相等。按位与运算符
按位与运算符(
&
)可以对两个数的比特位进行合并。它会返回一个新的数,只有当这两个数都是1
的时候才能返回1
。在下面的例子中,
firstSixBits
和lastSixBits
的中间四个位都为1
。按位与可以把它们合并为一个新值00111100
,对应十进制的值为60
。let firstSixBits: UInt8 = 0b11111100 let lastSixBits: UInt8 = 0b00111111 let middleFourBits = firstSixBits & lastSixBits // equals 00111100
按位或运算符
按位或运算符(
|
)可以对两个比特位进行比较,然后返回一个新数,只要两个操作位任意一个为1
时,那么对应的位数都为1
:在下面的例子中,
someBits
和moreBits
在不同的位设置了1
。按位或运算符把它们合并为11111110
,对应的十进制是无符号整数154
。let someBits: UInt8 = 0b10110010 let moreBits: UInt8 = 0b01011110 let combinedbits = someBits | moreBits // equals 11111110
按位异或运算符
按位异或运算符(
^
)可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为1
:在下面的例子中,
firstBits
和otherBits
的值有一位设置设置为1
,而对方设置为0
。按位异或运算符会将这两个位上的值设置为1
,firstBits
和otherBits
其他位都设置为了0
。let firstBits: UInt8 = 0b00010100 let otherBits: UInt8 = 0b00000101 let outputBits = firstBits ^ otherBits // equals 00010001
按位左移或右移运算符
按位左移(
<<
)或右移(>>
)运算符可以把所有位数的数字向左或向右移动一个确定的位数,但是需要遵守下面定义的规则。按位左移或右移具有乘以
2
或除以2
的效果。将一个数左移一位相当于把这个数乘以2
,将一个数右移一位相当于把这个数除以2
。无符号整数的移位操作
对无符号整数的移位规则如下:
已经存在的比特位按指定的位数进行左移和右移。
任何移动超出整型存储边界的位都会被丢弃。
用
0
来填充向左或向右移动后产生的空白位。
这种方法称之为 逻辑操作。
下图展示了
11111111 << 1
(即把11111111
向左移动1
位),11111111 >> 1
(即把11111111
向右移动1
位),蓝色的数字是被移位的,灰色的数字被舍弃,橙色的数字0
是新插入的:下面的代码展示了 Swift 的移位操作:
let shiftBits: UInt8 = 4 // 00000100 in binary shiftBits << 1 // 00001000 shiftBits << 2 // 00010000 shiftBits << 5 // 10000000 shiftBits << 6 // 00000000 shiftBits >> 2 // 00000001
可以使用移位操作对其他的数据类型进行编码和解码:
let pink: UInt32 = 0xCC6699 let redComponent = (pink & 0xFF0000) >> 16 // redComponent is 0xCC, or 204 let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent is 0x66, or 102 let blueComponent = pink & 0x0000FF // blueComponent is 0x99, or 153
这个示例使用了一个命名为
pink
的UInt32
型常量来存储层叠样式表(CSS
)中粉色的颜色值。该CSS
的十六进制颜色值#CC6699
, 在 Swift 中表示为0xCC6699
。然后利用按位与运算符(&
)和按位右移运算符(>>
)从这个颜色值中分解出红(CC
)、绿(66
)以及蓝(99
)三个部分。红色部分是通过对
0xCC6699
和0xFF0000
进行按位与运算后得到的。0xFF0000
中的0
部分作为掩码,掩盖了OxCC6699
中的第二和第三个字节,使得数值中的6699
被忽略,只留下0xCC0000
。然后,再将这个数按向右移动
16
位(>> 16
)。十六进制中每两个字符表示8
个比特位,所以移动16
位后0xCC0000
就变为0x0000CC
。这个数和0xCC
是等同的,也就是十进制数值的204
。同样的,绿色部分通过对
0xCC6699
和0x00FF00
进行按位与运算得到0x006600
。然后将这个数向右移动8
位,得到0x66
,也就是十进制数值的102
。最后,蓝色部分通过对
0xCC6699
和0x0000FF
进行按位与运算得到0x000099
。并且不需要进行向右移位,所以结果为0x99
,也就是十进制数值的153
。有符号整型的位移操作
对比无符号整型来说,有符整型的移位操作相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于
8
位有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。)有符号整型使用第一位(称作符号位)表示这个整数是正数还是负数。符号位为
0
表示为正数,1
表示为负数。其余的位数(称为数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从
0
开始算起。这是值为4
的Int8
型整数的二进制位表现形式:符号位是
0
(意味着是一个正数),另外7
位则代表了十进制数值4
的二进制表示。但是负数的存储方式略有不同。它存储的是
2
的n
次方减去它的真实值绝对值,这里的n
为数值位的位数。一个8
位的数有7
个数值位,所以是2
的7
次方,即128
。这是值为
-4
的Int8
型整数的二进制位表现形式:这次的符号位为
1
,说明这是一个负数,另外7
个位则代表了数值124
(即128 - 4
) 的二进制表示。负数的表示通常被称为二进制补码(two’s complement)表示法。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
首先,如果想对
1
和-4
进行加法操作,我们只需要将这两个数的全部8
个比特位进行相加,并且将计算结果中超出8
位的数值丢弃:其次,使用二进制补码可以使负数的按位左移和右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以
2
,每向右一位就将自身的数值除以2
。要达到此目的,对有符号整数的右移有一个额外的规则:- 当对正整数进行按位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是用
0
。
这个行为可以确保有符号整数的符号位不会因为右移操作而改变,这通常被称为算术移位(arithmetic shift)。
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近
0
。在移位的过程中保持符号位不变,意味着负整数在接近0
的过程中会一直保持为负。溢出运算符
在默认情况下,当向一个整数赋超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。这个行为给我们操作过大或着过小的数的时候提供了额外的安全性。
例如,
Int16
型整数能容纳的有符号整数范围是-32768
到32767
,当为一个Int16
型变量赋的值超过这个范围时,系统就会报错:var potentialOverflow = Int16.max // potentialOverflow 的值是 32767, 这是 Int16 能容纳的最大整数 potentialOverflow += 1 // 这里会报错
为过大或者过小的数值提供错误处理,能让我们在处理边界值时更加灵活。
然而,也可以选择让系统在数值溢出的时候采取截断操作,而非报错。可以使用 Swift 提供的三个溢出操作符(overflow operators)来让系统支持整数溢出运算。这些操作符都是以
&
开头的:- 溢出加法
&+
- 溢出减法
&-
- 溢出乘法
&*
数值溢出
数值可能出现向上溢出或向下溢出。
这个示例演示了当对一个无符号整数使用溢出加法(
&+
)进行上溢运算时会发生什么:var unsignedOverflow = UInt8.max // unsignedOverflow 等于 UInt8 所能容纳的最大整数 255 unsignedOverflow = unsignedOverflow &+ 1 // 此时 unsignedOverflow 等于 0
unsignedOverflow
被初始化为UInt8
所能容纳的最大整数(255
,二进制为11111111
)。溢出加法运算符(&+
)对其进行加1
操作。这使得它的二进制表示正好超出UInt8
所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在UInt8
边界内的值是00000000
,也就是十进制数值的0
。同样地,当我们对一个无符号整数使用溢出减法(
&-
)进行下溢运算时也会产生类似的现象:var unsignedOverflow = UInt8.min // unsignedOverflow 等于 UInt8 所能容纳的最小整数 0 unsignedOverflow = unsignedOverflow &- 1 // 此时 unsignedOverflow 等于 255
UInt8
型整数能容纳的最小值是0
,以二进制表示即00000000
。当使用溢出减法运算符(&-
)对其进行减1
操作时,数值会产生下溢并被截断为11111111
, 也就是十进制数值的255
。溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如按位左移/右移运算符所描述的。
var signedOverflow = Int8.min // signedOverflow 等于 Int8 所能容纳的最小整数 -128 signedOverflow = signedOverflow &- 1 // 此时 signedOverflow 等于 127
Int8
型整数能容纳的最小值是-128
,以二进制表示即10000000
。当使用溢出减法操作符对其进行减1
操作时,符号位被翻转,得到二进制数值01111111
,也就是十进制数值的127
,这个值也是Int8
型整数所能容纳的最大值。对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
优先级和结合性
运算符的优先级(precedence) 使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
结合性(associativity)定义了具有相同优先级的运算符是如何结合(或关联)的 —— 是与左边结合为一组,还是与右边结合为一组。可以这样理解:『它们是与左边的表达式结合的』或者『它们是与右边的表达式结合的』。
在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,为什么下面这个表达式的运算结果是
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 运算符优先级和结合性规则,请参考表达式。以及 Swift 标准库中的运算符。
注意:
对于C语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则是更加简洁和可预测的。但是,这也意味着它们于那些基于C的语言不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然是按照你所想的那样去执行。运算符函数
类和结构体可以为现有的操作符提供自定义的