Swift学习笔记 (四十) 高级运算符(上)

除了之前介绍过的《基本运算符》,Swift 还提供了数种可以对数值进⾏复杂运算的高级运算符。它们包含了在 C 和 Objective-C

中已经被大家所熟知的位运算符和移位运算符。

与 C 语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系

统允许溢出⾏为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符( &+ )。所有的这些溢出运算符都

是以 & 开头的。

自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中为这些运算符提供自定义

的实现⾮常简单,运算符也会针对不同类型使用对应实现。

我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,它们具有自定义的优先级与关

联值。这些运算符在代码中可以像预定义的运算符一样使用,你甚⾄可以扩展已有的类型以支持自定义运算符。

 

位运算符

位运算符可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理

外部资源的原始数据时也十分有用,⽐如对自定义通信协议传输的数据进⾏编码和解码。

Swift 支持 C 语言中的全部位运算符,接下来会⼀一介绍。

 

Bitwise NOT Operator(按位取反运算符)

按位取反运算符( ~ )对一个数值的全部比特位进行取反:

按位取反运算符是一个前缀运算符,直接放在运算数之前,并且它们之间不能添加任何空格:

let initialBits: UInt8 = 0b00001111

let invertedBits = ~initialBits                    // 等于 0b11110000

UInt8 类型的整数有 8 个比特位,可以存储 0 ~ 255 之间的任意整数。这个例子初始化了一个 UInt8 类型的整数,并赋值为二进

制的 00001111 ,它的前 4 位为 0 ,后 4 位为 1 。这个值等价于十进制的 15 。

接着使用按位取反运算符创建了一个名为 invertedBits 的常量,这个常量的值与全部按位取反后的 initialBits 相等。即所有的 0 

都变成了 1 ,同时所有的 1 都变成 0 。 invertedBits 的二进制值为 11110000 ,等价于无符号十进制数的 240 。

 

Bitwise AND Operator(按位与运算符) 

按位与运算符( & ) 对两个数的比特位进⾏合并。它返回一个新的数,只有当两个数的对应位都为 1 的时候,新数的对应位才为 1 :

在下面的示例当中, firstSixBits 和 lastSixBits 中间 4 个位的值都为 1 。使用按位与运算符之后,得到二进制数值 00111100 ,等

价于无符号十进制数的 60 :

let firstSixBits: UInt8 = 0b11111100

let lastSixBits: UInt8 = 0b00111111

let middleFourBits = firstSixBits & lastSixBits                  // 等于 00111100

 

Bitwise OR Operator(按位或运算符)

按位或运算符( | )可以对两个数的比特位进⾏比较。它返回一个新的数,只要两个数的对应位中有任意一个为 1 时,新数的对应

位就为 1 :

在下⾯的示例中, someBits 和 moreBits 存在不同的位被设置为 1 。使用按位或运算符之后,得到二进制数值 11111110 ,等价于

无符号十进制数的 254 :

let someBits: UInt8 = 0b10110010

let moreBits: UInt8 = 0b01011110

let combinedbits = someBits | moreBits                    // 等于 11111110

 

Bitwise XOR Operator(按位异或运算符)

按位异或运算符,或称“排外的或运算符”( ^ ),可以对两个数的比特位进⾏比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 1 ,并且对应位相同时则为 0 :

在下面的示例当中, firstBits 和 otherBits 都有一个⾃己为 1 ,⽽对方为 0 的位。按位异或运算符将新数的这两个位都设置为 1 

。在其余的位上 firstBits 和 otherBits 是相同的,所以设置为 0 :

let firstBits: UInt8 = 0b00010100

let otherBits: UInt8 = 0b00000101

let outputBits = firstBits ^ otherBits              // 等于 00010001

 

Bitwise Left and Right Shift Operators(按位左移、右移运算符)

按位左移运算符( << ) 和 按位右移运算符( >> )可以对一个数的所有位进⾏指定位数的左移和右移,但是需要遵守下面定义的规

则。

对一个数进⾏按位左移或按位右移,相当于对这个数进⾏乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以

2,同样地,将一个整数右移一位,等价于将这个数除以 2。

 

无符号整数的移位运算

对无符号整数进⾏移位的规则如下:

    1. 已存在的位按指定的位数进⾏左移和右移。

    2. 任何因移动而超出整型存储范围的位都会被丢弃。

    3. ⽤ 0 来填充移位后产生的空白位。

这种方法称为逻辑移位。

以下这张图展示了了 11111111 << 1 (即把 11111111 向左移动 1 位),和 11111111 >> 1 (即把 11111111向右移动 1 位)的结果。蓝色的

数字是被移位的,灰色的数字是被抛弃的,橙色的 0 则是被填充进来的:

下⾯面的代码演示了了 Swift 中的移位运算:

let shiftBits: UInt8 = 4            // 即⼆二进制的 00000100

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 是 0xCC,即 204

let greenComponent = (pink & 0x00FF00) >> 8          // greenComponent 是 0x66, 即 102

let blueComponent = pink & 0x0000FF                        // blueComponent 是 0x99,即 153

 

这个示例使用了一个命名为 pink 的 UInt32 型常量来存储 Cascading Style Sheets(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 。这⾥不需要再向右移位,而 0x000099 也就

是 0x99 ,也就是十进制数值的 153 。

 

有符号整数的移位运算

对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的

示例都是基于 8 比特的有符号整数,但是其中的原理对任何位数的有符号整数都是通用的。)

有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 0 代表正数,为 1 代表负数。

其余的比特位(通常被称为数值位)存储了实际的值。有符号正整数和无符号数的存储⽅式是一样的,都是从 0 开始算起。这是值

为 4 的 Int8 型整数的二进制位表现形式:

符号位为 0 (代表这是一个“正数”),另外 7 位则代表了十进制数值 4 的二进制表示。 负数的存储方式略有不同。它存储 2 的 n 

次方减去其实际值的绝对值,这里的 n 是数值位的位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 2 的 7 次方,即 128 

这是值为 -4 的 Int8 型整数的二进制表现形式:

这次的符号位为 1 ,说明这是⼀个负数,另外 7 个位则代表了数值 124 (即 128 - 4 )的二进制表示:

负数的表示通常被称为二进制补码。用这种方法来表示负数乍看起来有点奇怪,但它有⼏个优点。

首先,如果想对 -1 和 -4 进行加法运算,我们只需要对这两个数的全部 8 个比特位执⾏标准的二进制相加(包括符号位),并且将

计算结果中超出 8 位的数值丢弃:

其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将⾃身的数值乘以 2,每向

右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则:当对有符号整数进⾏按位右移运算时,遵

循与⽆符号整数相同的规则,但是对于移位产生的空白位使用符号位进⾏填充,⽽不是用 0 。

这个⾏为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为算术移位。 由于正数和负数的特殊存储方式,在

对它们进⾏右移的时候,会使它们越来越接近 0 。在移位的过程中保持符号位不变,意味着负整数在接近 0 的过程中会一直保持

为负。

 

溢出运算符

当向⼀个整数类型的常量或者变量赋予超过它容量的值时,Swift 默认会报错,⽽不是允许生成⼀个⽆效的数。这个⾏为为我们

在运算过大或者过小的数时提供了额外的安全性。

例如, Int16 型整数能容纳的有符号整数范围是 -32768 到 32767 。当为一个 Int16 类型的变量或常量赋予的值超过这个范围

时,系统就会报错:

var potentialOverflow = Int16.max       // potentialOverflow 的值是 32767,这是 Int16 能容纳的最大整数

potentialOverflow += 1                         // 这⾥里里会报错

在赋值时为过大或者过小的情况提供错误处理,能让我们在处理边界值时更加灵活。

然而,当有时候你也希望可以选择让系统在数值溢出的时候采取截断处理,⽽非报错。Swift 提供的三个溢出运算符来让系统支

持整数溢出运算。这些运算符都是以 & 开头的:

    溢出加法 &+

    溢出减法 &-

    溢出乘法 &*

 

数值溢出

数值有可能出现上溢或者下溢。

这个示例演示了当我们对一个无符号整数使用溢出加法( &+ )进⾏上溢运算时会发⽣什么:

var unsignedOverflow = UInt8.max                                      // unsignedOverflow 等于 UInt8 所能容纳的最大整数 

255unsignedOverflow = unsignedOverflow &+ 1              // 此时 unsignedOverflow 等于 0

unsignedOverflow 被初始化为 UInt8 所能容纳的最大整数( 255 ,以二进制表示即 11111111 )。然后使用溢出加法运算符( &+ )对

其进⾏加 1 运算。这使得它的二进制表示正好超出 UInt8 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,

仍然留在 UInt8 边界内的值是 00000000 ,也就是十进制数值的 0 。

当允许对一个无符号整数进⾏下溢运算时也会产⽣类似的情况。这⾥有一个使⽤溢出减法运算符( &- )的例子:

var unsignedOverflow = UInt8.min                                 // unsignedOverflow 等于 UInt8 所能容纳的最小整数 

0unsignedOverflow = 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 型整所能容纳的最大值。


对于⽆符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢
时,它们会从所能容纳的最小数变成最大数。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值