Merge pull request #407 from buginux/develop

25_Advanced_Operators 翻译完成
This commit is contained in:
梁杰
2015-07-05 13:05:21 +08:00

View File

@ -12,75 +12,75 @@
- [运算符函数(Operator Functions)](#operator_functions)
- [自定义运算符](#custom_operators)
除了[基本操作](02_Basic_Operators.html)中所讲的运算符Swift还有许多复杂的高级运算符包括了C语言和Objective-C的位运算符和移位运算。
除了在之前介绍过的[基本运算](02_Basic_Operators.html)Swift还有许多可以对数值进行复杂操作的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算
不同于C语言中的数值计算Swift的数值计算默认是不溢出的。溢出行为会被捕获并报告为错误。你是故意的?好吧,你可以使用Swift为你准备的另一套默认允许溢出的数值运算符,如可溢出的加号为`&+`。所有允许溢出运算符都是以`&`开始的。
C语言中的算术运算符不同Swift 中的算术运算符默认是不溢出的。所有溢出行为会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
定义的结构类和枚举是否可以使用标准的运算符来定义操作当然可以在Swift中你可以为你创建的所有类型定制运算符的操作
定义自有的结构体、类和枚举最好也同时为它们提供标准swift运算符的实现。Swift简化了运算符的自定义实现也使判断不同类型所对应的行为更为简单
可定制的运算符并不限于那些预设的运算符,你可以自定义中置,前置,后置及赋值运算符,当然还有优先级结合性。这些运算符在代码中可以像预设的运算符一样使用,你也可以扩展已有的类型以支持自定义的运算符。
我们不用被预定义的运算符所限制。在 Swift 当中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级结合性。这些运算符在代码中可以像预设的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。
<a name="bitwise_operators"></a>
## 位运算符
操作符可以操作数据结构中原始数据的每个比特位。位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,位操作符在同外部资源的数据进行交互的时候也很有用,比如在使用用户协议进行通信的时候,运用位运算符来对原始数据进行编码和解码。
运算符(`Bitwise operators`)可以操作一个数据结构中每个独立的位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
Swift支持如下所有C语言的位运算符
Swift 支持C语言中的全部位运算符具体如下:
### 按位取反运算符
### 按位取反运算符(`bitwise NOT operator`)
按位取反运算符`~`对一个操作数的每一位都取反
按位取反运算符(`~`) 可以对一个数值的全部位进行取反
![Art/bitwiseNOT_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseNOT_2x.png "Art/bitwiseNOT_2x.png")
![Art/bitwiseNOT_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseNOT_2x.png)
这个运算符是前置的,所以请不加任何空格地写在操作数之前
按位取反操作符是一个前置运算符,需要直接放在操作数的之前,并且它们之间不能添加任何空格
```swift
```
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000
```
`UInt8`是8位无符整型可以存储0~255之间的任意数。这个例子初始化一个整型为二进制`00001111`(前4位`0`,后4位`1`),它的十进制值为`15`
`UInt8` 类型的整数有 8 个比特位,可以存储 0 ~ 255之间的任意数。这个例子初始化一个 `UInt8` 类型的整数,并赋值为二进制`00001111`,它的前 4 位都`0`,后 4 位都`1`。这个值等价于十进制`15`
使用按位取反运算`~``initialBits`操作,然后赋值给`invertedBits`这个新常量这个常量的值等于所有位都取反的`initialBits`,即`1`变成`0``0`变成`1`,变成了`11110000`十进制值为`240`
接着使用按位取反运算符创建了一个名为 `invertedBits`常量这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`同时所有的 `1`变成 `0``invertedBits` 的二进制值为 `11110000`等价于无符号十进制数的 `240`
### 按位与运算符
### 按位与运算符(Bitwise AND Operator)
按位与运算符对两个数进行操作然后返回一个新的数这个数的每个位都需要两个输入数的同一位都为1时才为1
按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个操作数的对应位*都*为 `1` 的时候,该数的对应位才为 `1`
![Art/bitwiseAND_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseAND_2x.png "Art/bitwiseAND_2x.png")
以下代码`firstSixBits``lastSixBits`中间4个位都为1。对它进行按位与运算后,就得到了`00111100`即十进制的`60`
在下面的示例当中`firstSixBits``lastSixBits` 中间 4 个位的值都为 1 。按位与运算符对它进行了运算,得到二进制数值 `00111100`等价于无符号十进制数的 `60`
```swift
```
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
```
### 按位或运算
### 按位或运算符(Bitwise OR Operator)
按位或运算符`|`比较两个数然后返回一个新的数这个数的每一位设置1的条件是两个输入数的同一位都不为0(即任意一个为1或都为1)
按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个操作数的对应位中有*任意*一个为 `1` 时,该数的对应位就为 `1`
![Art/bitwiseOR_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseOR_2x.png "Art/bitwiseOR_2x.png")
如下代码`someBits``moreBits`在不同位上有`1`位或运行的结果是`11111110`即十进制的`254`
在下面的示例当中`someBits``moreBits` 将不同的位设置为 `1`位或运算符对它们进行了运算,得到二进制数值 `11111110`等价于无符号十进制数的 `254`
```swift
```
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
```
### 按位异或运算符
### 按位异或运算符(Bitwise XOR Opoerator)
按位异或运算符`^`比较两个数,然后返回一个数,这个数的每个位设为`1`的条件是两个输入数的同一位不同,如果相同就设为`0`
按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为 `1`
![Art/bitwiseXOR_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseXOR_2x.png "Art/bitwiseXOR_2x.png")
以下代码`firstBits``otherBits`都有一个`1`跟另一个数不同的。所以按位异或的结果是把它这些位置为`1`其他都置为`0`
在下面的示例当中`firstBits``otherBits` 都有一个自己设置为 `1` 而对方设置为 `0` 的位。 按位异或运算符将这两个位都设置为 `1`同时将其它位都设置为 `0`
```swift
```
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
@ -88,21 +88,27 @@ let outputBits = firstBits ^ otherBits // 等于 00010001
### 按位左移/右移运算符
左移运算符`<<`右移运算符`>>`会把一个数的所有比特位按以下定义的规则向左或向右移动指定位数
按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数进行指定位数的左移和右移,但是需要遵守下面定义的规则
按位左移按位右移的效果相当把一个整数乘于或除于一个因子为`2`的整数。向左移动一个整型的比特位相当于把这个数乘于`2`,向右移一位就是除于`2`
对一个数进行按位左移按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2同样地将一个整数右移一位等价于将这个数除以 2
#### 无符整型的移位操作
#### 无符整型的移位操作
对无符整型移位的效果如下:
对无符整型进行移位的规则如下:
已经存在的比特位向左或向右移动指定的位数。被移出整型存储边界的的位数直接抛弃,移动留下的空白位用零`0`来填充。这种方法称为逻辑移位
1. 已经存在的比特位按指定的位数进行左移和右移
2. 任何移动超出整型存储边界的位都会被丢弃。
3. 用 0 来填充移动后产生的空白位。
以下这张把展示了 `11111111 << 1`(`11111111`向左移1位),和 `11111111 >> 1`(`11111111`向右移1位)。蓝色的是被移位的,灰色是被抛弃的,橙色的`0`是被填充进来的
这种方法称为逻辑移位(`logical shift`)
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 1 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 1 位) 的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的。
![Art/bitshiftUnsigned_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftUnsigned_2x.png "Art/bitshiftUnsigned_2x.png")
```swift
下面的代码演示了 Swift 中的移位操作:
```
let shiftBits: UInt8 = 4 // 即二进制的00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
@ -111,221 +117,205 @@ shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001
```
可以使用移位操作进行其他数据类型编码和解码
可以使用移位操作其他数据类型进行编码和解码
```swift
```
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
```
这个例使用了一个`UInt32`命名为`pink`常量来存储层叠样式表`CSS`中粉色的颜色值`CSS`颜色`#CC6699`Swift用十六进制`0xCC6699`来表示。然后使用按位与(&)和按位右移就可以从这个颜色值中解出红(CC),绿(66),蓝(99)三个部分。
这个例使用了一个命名为 `pink``UInt32`常量来存储层叠样式表(`CSS`)中粉色的颜色值。该 `CSS` 的十六进制颜色值 `#CC6699`Swift 中表示为 `0xCC6699`。然后用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
`0xCC6699``0xFF0000`进行按位与`&`操作就可以得到红色部分`0xFF0000`中的`0`了遮盖了`OxCC6699`的第二和第三个字节,这样`6699`被忽略,只留下`0xCC0000`
红色部分是通过对 `0xCC6699``0xFF0000` 进行按位与运算后得到的`0xFF0000` 中的 `0` 部分作为*掩码*,掩盖了 `OxCC6699`的第二和第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`
然后按向右移动16位,即 `>> 16`。十六进制中每两个字符是8比特位所以移动16位的结果是把`0xCC0000`变成`0x0000CC`。这和`0xCC`等的,就是十进制`204`
然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数`0xCC`是等的,就是十进制数值的 `204`
同样的,绿色部分来自于`0xCC6699``0x00FF00`的按位操作得到`0x006600`。然后向右移动8位,得到`0x66`即十进制的`102`
同样的,绿色部分通过对 `0xCC6699``0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`也就是十进制数值的 `102`
最后,蓝色部分`0xCC6699``0x0000FF`进行按位与运算得到`0x000099`,无需向右移位,所以结果就是`0x99`,即十进制的`153`
最后,蓝色部分通过对 `0xCC6699``0x0000FF` 进行按位与运算得到 `0x000099`。并且不需要进行向右移位,所以结果`0x99` ,也就是十进制数值的 `153`
#### 有符整型的移位操作
#### 有符整型的移位操作
有符整型的移位操作相对复杂得多,因为正负号也是用二进制位表示的。(这里举的例子虽然都是8位的但它的原理是通用的。)
对比无符号整型来说,有符整型的移位操作相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 位有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。)
有符整型通过第1个比特位(称为符号位)来表这个整数是正数还是负数。`0`代表正数,`1`代表负数。
有符号整数使用第 1 个比特位(通常被称为符号位)来表这个数的正负。符号位为 `0` 代表正数,`1` 代表负数。
其余的比特位(称为数值位)存储实值。有符正整数和无符正整数在计算机里的存储结果是一样的,下来我们来看`+4`内部的二进制结构。
其余的比特位(通常被称为数值位)存储了这个数的真实值。有符正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4``Int8` 型整数的二进制位表现形式:
![Art/bitshiftSignedFour_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedFour_2x.png "Art/bitshiftSignedFour_2x.png")
符号位为`0`代表正数另外7比特位二进制表示的实际值就刚好是`4`
符号位为 `0`说明这是一个正数,另外 7 位则代表了十进制数值 `4` 的二进制表示
负数呢,跟正数不同。负数存储的是2的n次方减去它的绝对值,n为数值位的位数。一个8比特的数有7个数值位,所以是2的7次方即128。
负数的存储方式略有不同。存储的是 `2` 的 n 次方减去它的真实值绝对值,这里的 n 为数值位的位数。一个 8 位的数有 7 个数值位,所以是 2 的 7 次方,即 128。
我们来看`-4`存储的二进制结构。
这是值为 `-4``Int8` 型整数的二进制位表现形式:
![Art/bitshiftSignedMinusFour_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedMinusFour_2x.png "Art/bitshiftSignedMinusFour_2x.png")
现在符号位为`1`代表负数7个数值位要表达的二进制值是124即128 - 4
这次的符号位为 `1`说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`) 的二进制表示
![Art/bitshiftSignedMinusFourValue_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedMinusFourValue_2x.png "Art/bitshiftSignedMinusFourValue_2x.png")
负数的编码方式称为二进制补码表示。这种表示方式看起来奇怪,但它有几个优点。
负数的表示通常被称为二进制补码(`two's complement`)表示法。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
首先,只需要对全部8个比特位(包括符号)做标准的二进制加法就可以完成 `-1 + -4` 的操作忽略加法过程产生的超过8个比特位表达的任何信息。
首先,如果想对 `-1``-4` 进行加法操作,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃:
![Art/bitshiftSignedAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedAddition_2x.png "Art/bitshiftSignedAddition_2x.png")
第二,由于使用二进制补码表示,我们可以和正数一样对负数进行按位左移右移同样也是左移1位时乘于`2`右移1位时除于`2`。要达到此目的,对有符整型的右移有一个特别的要求
其次,使用二进制补码可以使负数的按位左移右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则
对有符整型按位右移时不使用0填充空白位而是根据符号位(正数为`0`,负数为`1`)填充空白位
* 当对正整数进行按位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 0
![Art/bitshiftSigned_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSigned_2x.png "Art/bitshiftSigned_2x.png")
就确保了在右移的过程中,有符整型的符号不会发生变化。这称为算术移位
个行为可以确保有符号整数的符号不会因为右移操作而改变,这通常被称为算术移位(`arithmetic shift`)
正因为正数和负数特殊存储方式,向右移位使它接近于`0`移位过程中保持符号不变,数在接近`0`的过程中一直是负数
由于正数和负数特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 0。在移位过程中保持符号不变,意味着负整数在接近 `0` 的过程中一直保持为负
<a name="overflow_operators"></a>
## 溢出运算符
默认情况下,当你往一个整型常量或变量赋于一个它不能承载的大数时Swift不会让你这么干的它会报错。这样操作过大或过小的数的时候就很安全
默认情况下,当一个整数赋超过它容量的值时Swift 默认会报错,而不是生成一个无效的数。这个行为给我们操作过大或过小的数的时候提供了额外的安全
例如,`Int16`整型能承载的整数范围是`-32768``32767`如果给它赋上超过这个范围的数,就会报错:
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768``32767`当为一个 `Int16` 型变量赋的值超过这个范围时,系统就会报错:
```swift
```
var potentialOverflow = Int16.max
// potentialOverflow 等于 32767, 这是 Int16 能承载的最大整数
// potentialOverflow 的值是 32767, 这是 Int16 能容纳的最大整数
potentialOverflow += 1
// 噢, 出错了
// 这里会报错
```
过大或过小的数值进行错误处理让你的数值边界条件更灵活。
过大或过小的数值提供错误处理,能让我们在处理边界值时更加灵活。
当然你有意在溢出时对有效位进行截断你可采用溢出运算而非错误处理。Swfit为整型计算提供了5个`&`符号开头的溢出运算符。
然而,也可以选择让系统在数值溢出的时候采取截断操作,而非报错。可以使用 Swift 提供的三个溢出操作符(`overflow operators`)来让系统支持整数溢出运算。这些操作符都是以 `&` 开头的:
- 溢出加法 `&+`
- 溢出减法 `&-`
- 溢出乘法 `&*`
- 溢出除法 `&/`
- 溢出求余 `&%`
* 溢出加法 `&+`
* 溢出减法 `&-`
* 溢出乘法 `&*`
### 值的上溢出
### 值溢出
下面例子使用了溢出加法`&+`来解剖的无符整数的上溢出
数值有可能出现上溢或者下溢。
```swift
var willOverflow = UInt8.max
// willOverflow 等于UInt8的最大整数 255
willOverflow = willOverflow &+ 1
// 此时 willOverflow 等于 0
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
```
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
unsignedOverflow = unsignedOverflow &+ 1
// 此时 unsignedOverflow 等于 0
```
`willOverflow``Int8`所能承载的最大`255`(二进制`11111111`)然后`&+`加1。然后`UInt8`就无法表达这个新值的二进制了,也就导致了这个新值上溢出了,大家可以看下图。溢出后,新值在`UInt8`的承载范围内的那部分是`00000000`,也就是`0`
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)然后使用了溢出加法运算符(`&+`)对其进行加 1 操作。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 0
![Art/overflowAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowAddition_2x.png "Art/overflowAddition_2x.png")
### 值的下溢出
同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象:
数值也有可能因为太小而越界。举个例子:
```
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
`UInt8`的最小值是`0`(二进制为`00000000`)。使用`&-`进行溢出减1就会得到二进制的`11111111`即十进制的`255`
![Art/overflowUnsignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowUnsignedSubtraction_2x.png "Art/overflowUnsignedSubtraction_2x.png")
Swift代码是这样的:
```swift
var willUnderflow = UInt8.min
// willUnderflow 等于UInt8的最小值0
willUnderflow = willUnderflow &- 1
// 此时 willUnderflow 等于 255
unsignedOverflow = unsignedOverflow &- 1
// 此时 unsignedOverflow 等于 255
```
有符整型也有类似的下溢出,有符整型所有的减法也都是对包括在符号位在内的二进制数进行二进制减法的,这在 "按位左移/右移运算符" 一节提到过。最小的有符整数是`-128`,即二进制的`10000000`。用溢出减法减去去1后变成了`01111111`即UInt8所能承载的最大整数`127`
`UInt8` 型整数能容纳的最小值是 0以二进制表示即 `00000000`当使用溢出减法运算符对其进行减 1 操作时,数值会产生下溢并被截断为 `11111111` 也就是十进制数值的 255
![Art/overflowUnsignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowUnsignedSubtraction_2x.png "Art/overflowAddition_2x.png")
溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如[按位左移/右移运算符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID34)所描述的。
```
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容纳的最小整数 -128
signedOverflow = signedOverflow &- 1
// 此时 signedOverflow 等于 127
```
`Int8` 型整数能容纳的最小值是 -128以二进制表示即 `10000000`。当使用溢出减法操作符对其进行减 1 操作时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。
![Art/overflowSignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowSignedSubtraction_2x.png "Art/overflowSignedSubtraction_2x.png")
来看看Swift代码
```swift
var signedUnderflow = Int8.min
// signedUnderflow 等于最小的有符整数 -128
signedUnderflow = signedUnderflow &- 1
// 此时 signedUnderflow 等于 127
```
### 除零溢出
一个数除以0 `i / 0`或者对0求余数 `i % 0`,就会产生一个错误。
```swift
let x = 1
let y = x / 0
```
使用它们对应的可溢出的版本的运算符`&/``&%`进行除0操作时就会得到`0`值。
```swift
let x = 1
let y = x &/ 0
// y 等于 0
```
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
<a name="precedence_and_associativity"></a>
## 优先级和结合性
运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
运算符的优先级(`precedence`)使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
结合性定义相同优先级的运算符在一起时是怎么组合或关联的,是和左边的一组,还是右边的一组。意思就是,到底是和左边的表达式结合呢,还是和右边的表达式结合
结合性(`associativity`)定义相同优先级的运算符是如何结合(或关联)的 —— 是与左边结合为一组,还是右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
合表达式中,运算符的优先级和结合性是非常重要的。举个例子,为什么下表达式的结果为`4`
合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,为什么下面这个表达式的运算结果是 `4`
```swift
2 + 3 * 4 % 5
// 结果是 4
```
如果严格地从左计算到右,计算过程是这样:
如果严格地从左到右进行运算,则运算的过程是这样
- 2 + 3 = 5
- 5 * 4 = 20
- 20 / 5 = 4 余 0
- 20 % 5 = 0
但是正确答案是`4`而不是`0`。优先级高的运算符要先计算在Swift和C语言中都是先乘除后加减的。所以执行完乘法和求余运算才能执行加减运算。
但是正确答案是 `4` 而不是 `0`。优先级高的运算符要先于优先级低的运算符进行计算。与C语言类似,在 Swift 当中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
乘法和求余拥有相同的优先级,在运算过程中,我们还需要结合性乘法和求余运算都是左结合的。这相当于在表达式中有隐藏的括号让运算从左开始。
乘法与取余的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性乘法与取余运算都是左结合的。可以将这考虑成为这两部分表达式都隐式地加上了括号:
```swift
2 + ((3 * 4) % 5)
```
3 * 4 = 12所以相当于:
`(3 * 4) = 12`,所以表达式相当于:
```swift
2 + (12 % 5)
```
12 % 5 = 2,所这又相当于
`12 % 5 = 2`,所以表达式相当于
```swift
2 + 2
```
计算结果为 4
此时可以容易地看出计算结果为 `4`
查阅Swift运算符优先级和结合性的完整列表,请看[表达式](../chapter3/04_Expressions.html)。
如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。
> 注意:
Swift的运算符较C语言和Objective-C来得更简单和保守,这意味着基于C的语言可能不一样。所以在移植已有代码到Swift时注意确保代码按你想的那样去执行。
> 对于C语言和 Objective-C 来说Swift 的运算符优先级和结合性规则是更加简洁和可预测的。但是,这意味着它们于那些基于C的语言不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然是按照你所想的那样去执行。
<a name="operator_functions"></a>
## 运算符函数
让已有的运算符也可以对自定义的类和结构进行运算,这称为运算符重载
类和结构可以为现有的操作符提供自定义的实现,这通常被称为运算符重载(`overloading`)
这个例子展示了如何`+`让一个自定义的结构做加法。算术运算符`+`是一个两目运算符,因为它有两个操作数,而且它必须出现在两个操作数之间。
下面的例子展示了如何为自定义的结构实现加法操作符(`+`)。算术加法运算符是一个两目运算符(`binary operator`),因为它可以对两个目标进行操作,同时它还是中缀(`infix`)运算符,因为它出现在两个目标中间。
例子中定义了一个名为`Vector2D`二维坐标向量 `(xy)` 的结构,然后定义了让两个`Vector2D`的对象相加的运算符函数
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量`(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数(`operator function`)
```swift
struct Vector2D {
var x = 0.0, y = 0.0
}
@infix func + (left: Vector2D, right: Vector2D) -> Vector2D {
func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
```
该运算符函数定义一个全局`+`函数,这个函数需要两个`Vector2D`类型的参数,返回值也是`Vector2D`类型。需要定义和实现一个中置运算的时候,在关键字`func`之前写上属性 `@infix` 就可以了
该运算符函数定义一个全局函数,并且函数的名字与它要进行重载的 `+` 名字一致。因为算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的输入参数,同时有一个 `Vector2D` 类型的返回值
在这个代码实现中,参数被命名为`left``right`,代表`+`左边和右边的两个`Vector2D`对象。函数返回了一个新的`Vector2D`的对象,这个对象的`x``y`分别等于两个参数对象的`x``y`和。
在这个实现中,输入参数分别被命名为 `left``right`,代表`+` 运算符左边和右边的两个 `Vector2D` 对象。函数返回了一个新的 `Vector2D` 的对象,这个对象的 `x``y` 分别等于两个参数对象的 `x``y` 的值之和。
这个函数全局的,而不是`Vector2D`结构的成员方法,所以任意两个`Vector2D`对象都可以使用这个中运算符
这个函数被定义成全局的,而不是 `Vector2D` 结构的成员方法,所以任意两个 `Vector2D` 对象都可以使用这个中运算符
```swift
let vector = Vector2D(x: 3.0, y: 1.0)
@ -334,151 +324,153 @@ let combinedVector = vector + anotherVector
// combinedVector 是一个新的Vector2D, 值为 (5.0, 5.0)
```
这个例子实现两个向量 `(3.01.0)``(2.04.0)` 相加,得到向量 `(5.05.0)`过程如下图示:
这个例子实现两个向量 `(3.01.0)``(2.04.0)` 相加,得到新的向量 `(5.05.0)`。这个过程如下图示:
![Art/vectorAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/vectorAddition_2x.png "Art/vectorAddition_2x.png")
### 前和后运算符
### 前和后运算符
上个例子演示了一个双目中运算符的自定义实现,同样我们也可以玩标准单目运算符的实现。单目运算符只有一个操作数,在操作数之前就是前置的,如`-a`; 在操作数之后就是后置的,如`i++`
上个例子演示了一个双目中运算符的自定义实现。类与结构体也能提供标准单目运算符(`unary operators`)的实现。单目运算符只有一个操作目标。当运算符出现在操作目标之前时,它就是前缀(`prefix`)的(比如 `-a`),而当它出现在操作目标之后时,它就是后缀(`postfix`)的(比如 `i++`)
实现一个前置或后置运算符时,在定义该运算符的时候于关键字`func`之前标注 `@prefix``@postfix` 属性。
实现前缀或者后缀运算符,需要在声明运算符函数的时候`func` 关键字之前指定 `prefix` `postfix` 限定符:
```swift
@prefix func - (vector: Vector2D) -> Vector2D {
prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
```
这段代码为`Vector2D`类型提供了单目减运算`-a``@prefix`属性表明这是个前置运算符。
这段代码为 `Vector2D` 类型实现了单目减运算符(`-a`)。由于单目减运算符是前缀运算符,所以这个函数需要加上 `prefix` 限定符。
对于数值,单目减运算符可以把正数变负数,把负数变正数。对于`Vector2D`,单目减运算将其`x``y`都进进行单目减运算
对于简单数值,单目减运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,单目减运算将其 `x``y` 属性的正负性都进行了改变
```swift
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 为 (-3.0, -4.0)
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
let alsoPositive = -negative
// alsoPositive 为 (3.0, 4.0)
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
```
### 合赋值运算符
### 合赋值运算符
合赋值是其他运算符和赋值运算符一起执行的运算。如`+=`把加运算和赋值运算组合成一个操作。实现一个组合赋值符号需要使用`@assignment`属性,还需要把运算符的左参数设置成`inout`,因为这个参数会在运算符函数内直接修改它的值
合赋值运算符(`Compound assignment operators`)将赋值运算符(`=`)与其它运算符进行结合。比如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接修改。
```swift
@assignment func += (inout left: Vector2D, right: Vector2D) {
func += (inout left: Vector2D, right: Vector2D) {
left = left + right
}
```
因为加法运算在之前定义过了,这里无需重新定义。所以,加赋运算符函数使用已经存在的高级加法运算符函数来执行左值右值的运算。
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利用现有的加法运算符函数,用它来对左值右值进行相加,并再次赋值给左值:
```swift
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 现在为 (4.0, 6.0)
// original 的值现在为 (4.0, 6.0)
```
可以将 `@assignment` 属性和 `@prefix``@postfix` 属性起来组合,实现一个`Vector2D`的前置运算符。
可以将赋值与 `prefix``postfix` 限定符结合起来,下面的代码为 `Vector2D` 实例实现了前缀自增运算符(`++a`)
```swift
@prefix @assignment func ++ (inout vector: Vector2D) -> Vector2D {
prefix func ++ (inout vector: Vector2D) -> Vector2D {
vector += Vector2D(x: 1.0, y: 1.0)
return vector
}
```
这个前置使用了已经定义好的高级加赋运算,将自己加上一个值为 `(1.01.0)` 的对象然后赋给自己,然后再将自己返回
这个前缀自增运算符使用了前面定义的加法赋值操作。它对 `Vector2D``x``y` 属性都进行了加 `1` 操作,再将结果返回
```swift
var toIncrement = Vector2D(x: 3.0, y: 4.0)
let afterIncrement = ++toIncrement
// toIncrement 现在 (4.0, 5.0)
// afterIncrement 现在也是 (4.0, 5.0)
// toIncrement 的值现在 (4.0, 5.0)
// afterIncrement 的值同样为 (4.0, 5.0)
```
>注意:
默认的赋值符(=)是不可重载。只有组合赋值符可以重载。三目条件运算符 `abc` 也是不可重载。
> 注意:
> 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值符可以重载。同样地,也无法对三目条件运算符 `a ? b : c` 进行重载。
### 比较运算符
### 等价运算符
Swift无所知道自定义类型是否相等或不等,因为等于或者不等于由你的代码说了算了。所以自定义的类和结构要使用比较符`==``!=`就需要重载
自定义的类和结构体没有对等价操作符(`equivalence operators`)进行默认实现,等价操作符通常被称为“相等”操作符(`==`)与“不等”操作符(`!=`)。对于自定义类型Swift 无法判断其是否相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色
定义相等运算符函数跟定义其他中置运算符雷同
为了使用等价操作符来对自定义的类型进行判等操作,需要为其提供自定义实现,实现的方法与其它中缀运算符一样
```swift
@infix func == (left: Vector2D, right: Vector2D) -> Bool {
func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
@infix func != (left: Vector2D, right: Vector2D) -> Bool {
func != (left: Vector2D, right: Vector2D) -> Bool {
return !(left == right)
}
```
上述代码实现了相等运算符`==`来判断两个`Vector2D`对象是否有相等的值,相等的概念就是它们有相同的`x`值和相同的`y`值,我们就用这个逻辑来实现。接着使用`==`的结果实现了不相等运算符`!=`
上述代码实现了相等运算符(`==`)来判断两个 `Vector2D` 对象是否有相等。对于 `Vector2D` 类型来说,“相等”意味“两个实例的 `x`属性 和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”操作符(`!=`),它简单地将“相等”操作符进行取反后返回
现在我们可以使用这两个运算符来判断两个`Vector2D`对象是否相等。
现在我们可以使用这两个运算符来判断两个 `Vector2D` 对象是否相等。
```swift
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
println("这两个向量是相等的.")
print("These two vectors are equivalent.")
}
// prints "这两个向量是相等的."
// prints "These two vectors are equivalent."
```
### 自定义运算符
标准运算符不够玩,那你可以声明一些个性的运算符,但个性的运算符只能使用这些字符 `/ = - + * % < > ! & | ^ . ~`
除了实现标准运算符,在 Swift 当中还可以声明和实现自定义运算符(`custom operators`)。可以用来自定义运算符的字符列表请参考[操作符](../chapter3/02_Lexical_Structure.html#operators)
新的运算符声明需在全局域使用`operator`关键字声明,可以声明为前置,中置或后置的。
新的运算符要在全局作用域内,使用 `operator` 关键字进行声明,同时还要指定 `prefix``infix` 或者 `postfix` 限定符:
```swift
operator prefix +++ {}
```
这段代码定义了一个新的前置运算符叫`+++`此前Swift并不存在这个运算符。此处为了演示我们让`+++``Vector2D`对象的操作定义为 `双自增` 这样一个独有的操作,这个操作使用了之前定义的加赋运算实现了自已加上自己然后返回的运算。
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因为我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法操作符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y`属性的值翻倍:
```swift
@prefix @assignment func +++ (inout vector: Vector2D) -> Vector2D {
prefix func +++ (inout vector: Vector2D) -> Vector2D {
vector += vector
return vector
}
```
`Vector2D``+++` 的实现和 `++` 的实现很接近, 唯一不同的是前者是加自己, 后者是值为 `(1.0, 1.0)` 的向量.
`Vector2D``+++` 的实现和 `++` 的实现很相似, 唯一不同的是前者对自身进行相加, 后者是与另一个值为 `(1.0, 1.0)` 的向量相加.
```swift
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled 现在 (2.0, 8.0)
// afterDoubling 现在也是 (2.0, 8.0)
// toBeDoubled 现在的值为 (2.0, 8.0)
// afterDoubling 现在的值也为 (2.0, 8.0)
```
### 自定义中运算符的优先级和结合性
### 自定义中运算符的优先级和结合性
可以为自定义的中置运算符指定优先级和结合性。可以回头看看[优先级和结合性](#PrecedenceandAssociativity)解释这两个因素是如何影响多种中置运算符混合的表达式的计算的。
自定义的中缀(`infix`)运算符也可以指定优先级(`precedence`)和结合性(`associativity`)。[优先级和结合性](#PrecedenceandAssociativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
结合性(associativity)的值可取的值有`left``right``none`。左结合运算符跟其他优先级相同的左结合运算符写在一起时,会跟左边的操作数结合。同理,右结合运算符会跟右边的操作数结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。
结合性(`associativity`)可取的值有` left``right``none`左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的操作数进行结合。同理,右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的操作数进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。
结合性(associativity)的默认`none`,优先级(precedence)默认为`100`
结合性(`associativity`)的默认值是 `none`,优先级(`precedence`)如果没有指定,则默认为 `100`
以下例子定义了一个新的中置符`+-`,是左结合的`left`,优先级为`140`
以下例子定义了一个新的中缀运算符 `+-`此操作符是左结合的,并且它的优先级为 `140`
```swift
operator infix +- { associativity left precedence 140 }
infix operator +- { associativity left precedence 140 }
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 此时的值为 (4.0, -2.0)
// plusMinusVector 是一个 Vector2D 类型,并且它的值为 (4.0, -2.0)
```
这个运算符把两个向量的`x`相加,向量的`y`相减。因为他实际是属于加减运算,所以让它保持了和加法一样的结合性和优先级(`left``140`)。查阅完整的Swift默认结合性优先级的设置,请移步[表达式](../chapter3/04_Expressions.html);
这个运算符把两个向量的 `x`相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“加型”运算,所以将它的结合性和优先级被设置为(`left``140`),这与 `+``-` 等默认的中缀加型操作符是相同的。完整的 Swift 操作符默认结合性优先级请参考[表达式](../chapter3/04_Expressions.html)
> 注意:
> 当定义前缀与后缀操作符的时候,我们并没有指定优先级。然而,如果对同一个操作数同时使用前缀与后缀操作符,则后缀操作符会先被执行。