您的位置:首页 > 移动开发 > Swift

[翻译]Swift编程语言——高级操作符

2015-06-17 11:18 232 查看

高级操作符

在前面的 基本操作符(Basic Operators)之外,为了做更复杂的值操作,Swift还提供了若干高级操作符。这些高级操作符包括在C和OC中已经习以为常的按位(bitwise)和移位( bit shifting)运算符。

不同于C中的算术操作符,Swfit中的算术操作符不会默认溢出。溢出行为会被捕捉并报告为一个错误。想要选择溢出行为,需要使用Swfit中的第二算术操作符集合,比如溢出加运算符(&+).所有的溢出操作符都以&开头。

当定义自己的结构体、类和枚举时,为这些类型提供自己对于标准Swift操作符的实现是很有用的。Swift使得为这些自定义的类型量身打造标准操作符的实现变得很轻松。

预定义操作符没有任何限制,Swift提供了定制中缀、前缀、后缀和指派(定制优先级和结合性)操作符的自由。这些操作符可以在像其他预定义操作符一样使用,甚至可以通过扩展存在的类型来支持自定义的操作符。

按位操作符

按位操作符(bitwise operator)能够操作数据结构中单独bit。这在低级程序语言中经常使用,比如图形图像编程和设备驱动编程。按位操作符在处理外部数据源的数据,比如通过特定协议进行通讯时的编码和解码。

Swfit提供所有的C支持的按位操作符,下文有具体描述。

按位非操作符

按位非操作符(~)将一个数字的所有bit反转:



按位非操作符是一个前置操作符,使用时要放置在要操作的数值之前,不能有空格:

​let​ ​initialBits​: ​UInt8​ = ​0b00001111
​let​ ​invertedBits​ = ~​initialBits​  ​// equals 11110000


UInt8整型有8bit,能够存储0到255的整型。这个例子用二进制值00001111构造了一个UInt8整型,这个二进制前4个bit都是0,接下来的4个bit都是1.它的值用十进制表示是15。

按位非操作符被用来创建一个新的常量invertedBits,这个新常量是initialBits所有bit的反转。零会被转换成一,一会转换成零。invertedBits的值是11110000,等于无符号的十进制数字240.

按位与操作符

按位与操作符(&)将两个数字的bit组合在一块,只有两个操作数对应的bit都是1的时候,结果数字的对应bit才被设置为1:



在下面的例子中,firstSixBits和lastSixBits中间位置的四个bit都是1.按位与操作符将二者组合起来生成操作结果00111100,它代表无符号的十进制数字60:

let​ ​firstSixBits​: ​UInt8​ = ​0b11111100
​let​ ​lastSixBits​: ​UInt8​  = ​0b00111111
​let​ ​middleFourBits​ = ​firstSixBits​ & ​lastSixBits​  ​// equals 00111100


按位或操作符

按位或操作符(|)比较两个数字的bit。这个操作符返回一个新的数字,只要操作的两个数字对应的bit有一个是1,结果数字对应的bit就被设置为1:



下面的例子,someBits和moreBits在不同的bit上是1.按位或操作符将其组合成数字11111110,表示无符号十进制254:

let​ ​someBits​: ​UInt8​ = ​0b10110010
​let​ ​moreBits​: ​UInt8​ = ​0b01011110
​let​ ​combinedbits​ = ​someBits​ | ​moreBits​  ​// equals 11111110


按位异或操作符(Bitwise XOR Operator)

按位异或操作符,或者“特殊或操作符”(^),比较两个数字的bit。操作符返回一个结果数字,当操作数的对应bit数字不同时,结果数字的对应bit被设置为1;当操作数的对应bit数字相同时,结果数字对应的bit被设置为0:



下面的例子中,firstBits和otherBits各自有一个位置的bit是1但对方相同位置的不是。在按位异或运算符的结果数字中,这些位置的bit被设置为1。firstBits和otherBits中完全匹配的bit,结果数字中对应的位置bit会被设置为0:

​let​ ​firstBits​: ​UInt8​ = ​0b00010100
​let​ ​otherBits​: ​UInt8​ = ​0b00000101
​let​ ​outputBits​ = ​firstBits​ ^ ​otherBits​  ​// equals 00010001


左右移位操作符

左移位操作符(bitwies left shift operator)(<<)和右移位操作符(bitwise right shift operator)(>>)将操作数字的所有位向左或向右移动特定位,遵循下面定义的规则。

左移位和右移位会以2位倍数对操作数乘或者除。左移移位就是对操作数做加倍,右移一位就是对操作数减半。

无符号整型的移位

无符号整型的位移需要遵循:

1:已有的bit根据参数向左或向右移动。

2:移动超出整型范围的bit被舍弃。

3:在原来的bit移动后,原来的位置用0填充。

这被称作逻辑移位。

下面的图标展示了 11111111 << 1(11111111 左移一位)的结果,和11111111 >> 1(11111111 右移一位)的结果。蓝色的数字是被移动的,灰色的数字是被舍弃的,橙色的数字是被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


这个例子使用一个UInt32常量pink来存储一个CSS(译者:Cascading Style Sheets, 级联样式表)中的颜色:粉色。CSS颜色值#CC6699依据Swift的十六进制表示法被写作0xCC6699。这个颜色被分解为它的红色(CC)、绿色(66)和蓝色(99),通过按位与操作符(&)和右移位(>>)。

红色部分通过0xCC6699 和0xFF0000按位与操作得到。0xFF0000 中的零实际上标记了0xCC6699的前两个字节,导致6699被忽略,结果中只剩下0xCC0000 。

接下来数字像右移位16位(>>16)。每个十六进制字符占用8bit,所以向右移位16位将会将0xCC0000转化为0x0000CC。这个结果和0xCC一样,就是十进制的204。

类似的,绿色部分通过0xCC6699 和0x00FF00按位与操作得到。得到的结果是0x006600。向右移8位,得到0x66,也就是十进制的102.

最后,蓝色部分通过对0xCC6699 和0x0000FF按位与操作得到。得到的结果是0x000099。这里不需要做移位了,0x000099 已经等于0x99,也就是十进制的153。

有符号整型的移位

有符号整型的移位操作比无符号整型的移位要复杂的多,因为需要用二进制标记符号。(为了方便,下面的例子基于8bit的有符号整型,但是相同的规则也适用于其他尺寸的整型。)

有符号整型用它们的第一个bit(被称作符号位)来表示正负。0表示正,1表示负。

剩余的bit(称作数值位)存储实际的值。整数和无符号整型存储方式一样,从0开始计数。这里展示了一个Int8的bit如何表现数字4:



符号位是0(表示正数),7个值位表现数字4,用二进制表示。

负数的存储就不同了。实际存储的是2的n次幂减去负数的绝对值,这里n是有符号数的数字位的个数,一个8bit的数字有7个数字位,所以2的7次幂是128.

下面展示了一个Int8如何存储-4:



这次,符号位是1(表示是负数),7位2进制数字等于124(128-4):



负数的编码被称作二进制补码。看起来这是表示负数的常用方式,但它有若干优势。

首先,把-1添加到-4上,对所有8位(包括符号位)按照标准的二进制加法进行计算,舍弃这8位之外的其他位:



其次,这种表示方法能让负数向左移位,让整数向右移位,同时还能保持向左移位加倍,向右移位减半的结果。为了做到这些,有符号整形在向右移位的时候需要遵守一条额外的规则:

当将一个有符号整数向右移动时,和移动无符号数规则一样,除了需要用符号位的内容填充空位而不是用零,这一点之外。



这样确保了向右移位时符号位不变,这被称作算术移位。

通过这个特殊的存储方式,无论正数还是负数,在向右移位的时候都是像零靠拢。在移位过程中保持符号位不变,意味着负数仍然是负数,但它的值已经像零接近了。

溢出操作符(Overflow Operators)

如果给一个整型常量或者变量插入一个它不能容纳的数字,默认情况下Swift会报错而不是允许无效的值被创建。这样在操作数字可能过大或者过小的时候会有安全保障。

举例说明,Int16可以表现从-32768 到32767的有符号整型数字。超出范围会导致出错:

​var​ ​potentialOverflow​ = ​Int16​.​max
​// potentialOverflow equals 32767, which is the largest value an Int16 can hold
​potentialOverflow​ += ​1
​// this causes an error


当数字过大或者过小时提供这样的错误处理的机会,可以增加编程的灵活度。

但是,如果是想通过溢出截断数字的有效位,就没必要出发这种错误了。好在Swift提供了5个算数溢出操作符,允许整型计算过程中的溢出行为。这些运算符都以&开头:

溢出加(&+)

溢出减(&-)

溢出乘(&*)

溢出除(&/)

溢出取余(&%)

值溢出

这里有一个例子展示了当一个有符号数被允许溢出后发生的情形,当然这里用到了溢出加的操作符号(&+):

var​ ​willOverflow​ = ​UInt8​.​max
​// willOverflow equals 255, which is the largest value a UInt8 can hold
​willOverflow​ = ​willOverflow​ &+ ​1
​// willOverflow is now equal to 0


变量willOverflow 被用UInt8的最大值(255,或者二进制的11111111 )初始化。然后它被使用溢出加操作符(&+)添加了1。这样做迫使它的二进制表示超出了一个UInt8的范围,导致了溢出,就像下面的图一样。操作值后仍然留在UInt8表示范围内的是00000000,也就是0:



值下溢

数字小于能够表现的最大范围是另外一种情况,这里有另外一个例子。

UInt8最小能表示的值是0(二进制表示00000000)。如果从00000000中减去1,这里使用溢出减操作符,数字将会变成11111111,也就是十进制的255:



这里是Swift代码:

​var​ ​willUnderflow​ = ​UInt8​.​min
​// willUnderflow equals 0, which is the smallest value a UInt8 can hold
​willUnderflow​ = ​willUnderflow​ &- ​1
​// willUnderflow is now equal to 255


类似的向下溢出也发生在有符号整型的情形。所有的有符号整型的减法被直接按照二进制计算,符号位也成了被减数的一部分,这在 向左和向右移位操作符(Bitwise Left and Right Shift Operator)中有讲过。Int8的能表现的最小值是-128,也就是二进制的10000000 。从这个最小数减去1,同样采用溢出操作符,得到二进制的01111111,符号位也反转了,得到的是正127,也就是Int8能够表现的最大数:



下面是Swift代码:

​var​ ​signedUnderflow​ = ​Int8​.​min
​// signedUnderflow equals -128, which is the smallest value an Int8 can hold
​signedUnderflow​ = ​signedUnderflow​ &- ​1
​// signedUnderflow is now equal to 127


总结一下对于有符号和无符号整数的向上和向下溢出行为,向上溢出总是从最大的合法值到最小的合法值,向下溢出总是从最小的合法值到最大的。

0做除数

用0做除数(i / 0),或是对0取余数 (i % 0),会导致错误:

let​ ​x​ = ​1
​let​ ​y​ = ​x​ / ​0


但是使用了溢出版本(&/和&%)会得到一个0:

let​ ​x​ = ​1
​let​ ​y​ = ​x​ &/ ​0
​// y is equal to 0


优先级别和结合性

操作符优先级给某些操作符提供了相比其他操作符的优先权,这些操作符会被先执行。

操作符结合性定义了两个优先级相等的操作符怎么组合(或结合)在一起——是从左组合,还是从右组合。意思是这些操作符是“和它左侧的表达式结合”还是“和它右侧的表达式结合”。

当面对一个复杂表达式,需要确认操作符的执行顺序时,考虑每个操作符的优先级和结合性是非常重要的。这里就有一个例子。为什么下面的表达式结果等于4?

2​ + ​3​ * ​4​ % ​5
​// this equals 4


严格依据从左到右的顺序,会得到如下内容:

2加上3得到5;

5乘以4得到20;

20对5取余得到0

但是,实际的结果是4,而不是0。高优先级的操作符会比低优先级的操作符先执行。Swfit和C语言一样,乘法(*)和取余操作符(%)的优先级都比加法操作符(+)高。结果就是它们会在加法执行前被执行。

然而,乘法和取余运算符具有相同的优先级。为了得到准确的执行顺序,就必须考虑他们的结合性了。乘法和取余运算符都是向左结合的。设想一下从左侧给这些表达式添加上原本隐藏的括号:

​2​ + ((​3​ * ​4​) % ​5​)


(3 * 4)得到 12,所以上面等价于:

​2​ + (​12​ % ​5​)


(12 % 5) 得到 2,所以等价于:

​2​ + ​2


计算的最终结果是4。

完整的Swift操作符的优先级和结合性,参见 表达式(Expressions)。

NOTE

同C语言和OC中能找到的操作符相比,Swfit的操作符的优先级和结合性很类似,但是更具可预测性。这也意味着和以C语言为基础的语言有不同之处。在使用多个操作符时要确保执行的顺序是你想要的。

操作符函数

类和结构体可以提供他们自己的对于已有操作符的实现。这被称作操作符重载。

下面的例子展示了在一个结构体内如何实现算术操作符加(+),定义了一个操作符函数来将Vector2D结构体实例“加”在一起:

​struct​ ​Vector2D​ {
​    ​var​ ​x​ = ​0.0​, ​y​ = ​0.0
​}
​func​ + (​left​: ​Vector2D​, ​right​: ​Vector2D​) -> ​Vector2D​ {
​    ​return​ ​Vector2D​(​x​: ​left​.​x​ + ​right​.​x​, ​y​: ​left​.​y​ + ​right​.​y​)
​}


操作符函数被作为一个全局函数定义,函数名字和姚重载的操作符(+)一样。因为算术加法操作符是二元操作符,所以这个操作符函数需要带两个Vector2D 类型的参数,而且返回一个Vector2D 类型的结果。

在这个实现中,两个参数分别没命名为left和right代表在+操作符左边和右边的Vector2D 实例。这个函数返回一个新的Vector2D 实例,它的x和y属性被初始化为被“加”到一起的两个Vector2D 实例的x和y的属性的和。

这个函数被定义为全局的,而不是Vector2D 结构体的方法,所以可以在Vector2D 实例之间当中缀操作符使用:

​let​ ​vector​ = ​Vector2D​(​x​: ​3.0​, ​y​: ​1.0​)
​let​ ​anotherVector​ = ​Vector2D​(​x​: ​2.0​, ​y​: ​4.0​)
​let​ ​combinedVector​ = ​vector​ + ​anotherVector
​// combinedVector is a Vector2D instance with values of (5.0, 5.0)


这个例子将向量(3.0, 1.0)和(2.0, 4.0)加在了一起,构造了一个新的向量(5.0, 5.0),如图所示:



前缀和后缀操作符

上面的例子展示了一个自定义二元中缀操作符的实现。类和结构体还可以提供标准的一元操作符的实现。一元操作符操作单一的一个对象。如果它们出现在操作对象前,它们就是前缀操作符(比如-a),如果出现在值后,就是后缀操作符(比如i++)。

实现一元的前缀或后缀操作符需要当定义时在func关键字前写上prefix 或者postfix 修饰符:

​prefix​ ​func​ - (​vector​: ​Vector2D​) -> ​Vector2D​ {
​    ​return​ ​Vector2D​(​x​: -​vector​.​x​, ​y​: -​vector​.​y​)
​}


上面的例子为Vector2D 实现了一元取负操作符(-a)。这个操作符是前缀的,所以函数用prefix修饰符修饰。

对于数值,一元取负操作符将正数转换为等值的负数并且符号取反。对于Vector2D 实例恰当的一元取负操作符的实现基于x和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)


组合赋值操作符(Compound Assignment Operators)

组合复制操作符将赋值操作符(=)和其他操作符结合起来。比如,加赋值操作符(+=)将加操作符和赋值操作符组合在一起作为单独操作符。组合赋值操作符左侧操作数是操作符函数中用inout标记的参数,因为这个参数的值会在操作符函数内部被直接修改。

下面的例子实现了对于Vector2D 实例的加赋值操作符函数:

​func​ += (​inout​ ​left​: ​Vector2D​, ​right​: ​Vector2D​) {
​    ​left​ = ​left​ + ​right
​}


因为加操作符已经定义了,所以不必再实现加操作符了。加赋值操作符函数使用已经存在的加操作符函数,用它将左右两个参数相加后复制给左边:

var​ ​original​ = ​Vector2D​(​x​: ​1.0​, ​y​: ​2.0​)
​let​ ​vectorToAdd​ = ​Vector2D​(​x​: ​3.0​, ​y​: ​4.0​)
​original​ += ​vectorToAdd
​// original now has values of (4.0, 6.0)


可以用prefix或postfix修饰符修饰组合赋值操作符函数,比如为了Vector2D 实例实现前缀自增操作符(++a):

​prefix​ ​func​ ++ (​inout​ ​vector​: ​Vector2D​) -> ​Vector2D​ {
​    ​vector​ += ​Vector2D​(​x​: ​1.0​, ​y​: ​1.0​)
​    ​return​ ​vector
​}


上面前缀自增操作符函数使用了前面定义的高级加赋值操作符。它给调用的Vector2D 实例中的x和y的值都加了1,然后返回结果:

​var​ ​toIncrement​ = ​Vector2D​(​x​: ​3.0​, ​y​: ​4.0​)
​let​ ​afterIncrement​ = ++​toIncrement
​// toIncrement now has values of (4.0, 5.0)
​// afterIncrement also has values of (4.0, 5.0)


NOTE

重载默认的赋值操作符是不可能的。只有组合赋值操作符可以被重载。类似的,三元操作符( ? b : c)也是不可以重载的。

相等操作符(Equivalence Operators)

自定义的类和结构体没有得到默认的相等操作符(等于操作符==和不等于操作符!=)的实现。Swfit不可能能够猜测出对于自定义类型怎么样才是“相等”,因为“相等”需要根据这些类型所发挥的作用来定。

为了对自定义的类型也能使用相等操作符,需要像其他中缀操作符一样提供一个操作符函数实现:

func​ == (​left​: ​Vector2D​, ​right​: ​Vector2D​) -> ​Bool​ {
​    ​return​ (​left​.​x​ == ​right​.​x​) && (​left​.​y​ == ​right​.​y​)
​}
​func​ != (​left​: ​Vector2D​, ​right​: ​Vector2D​) -> ​Bool​ {
​    ​return​ !(​left​ == ​right​)
​}


上面的例子实现了“相等”操作符(==),用来检查两个Vector2D示例是否等价。对于Vector2D而言,“相等”意味着“两个实例都有相同的x和y值”,操作符实现也是这样的一个逻辑。例子同样实现了“不等于”操作符(!=),这个简单,仅仅是是反转了相等操作符的结果。

现在可以使用这些操作符来检查两个Vector2D实例是否相等了:

let​ ​twoThree​ = ​Vector2D​(​x​: ​2.0​, ​y​: ​3.0​)
​let​ ​anotherTwoThree​ = ​Vector2D​(​x​: ​2.0​, ​y​: ​3.0​)
​if​ ​twoThree​ == ​anotherTwoThree​ {
​    ​println​(​"These two vectors are equivalent."​)
​}
​// prints "These two vectors are equivalent."


定制操作符

可以在Swfit提供的标准操作符之外自己定义操作符。所有可以被用来自定义操作符的字符列表,参见 操作符(Operators)。

用operator关键字定义的新操作符是全局的,可以用perfix,infix或者postfix修饰符修饰:

​prefix​ ​operator​ +++ {}


上面定义了一个新的前缀操作符+++。这个操作符Swift中没有,下面给出了针对Vector2D实例的实现。这个例子中,+++是一个新的“前缀两倍自增”操作符。它会将一个Vector2D实例的x和y变成原来的两倍,通过前面定义的加赋值操作符将结果赋值给自身:

​prefix​ ​func​ +++ (​inout​ ​vector​: ​Vector2D​) -> ​Vector2D​ {
​    ​vector​ += ​vector
​    ​return​ ​vector
​}


这个+++实现和Vector2D的++实现很类似,不同点是这个是将向量自身相加,而++只会在原来向量上加上Vector2D(1.0, 1.0):

​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)


定制中缀操作符的优先级和结合性

自定义的中缀操作符可以指定优先级和结合性。在 优先级和结合性 (Precedence and Associativity)一节中解释了在有其他中缀操作符的时候这两个特性带来的影响。

associativity 的可能值是left、right和none。左结合操作符的后面碰到其他同等优先级的左结合操作符时,会像左结合。类似的,右结合操作符在后面碰到其他优先级相同的右结合操作符时,会向右结合。非结合操作符不能写在其他优先级别相同的操作符之后。

没有指定时,associativity 的默认值是none,precedence 的默认值是100。

下面的例子定义了一个新的自定义中缀操作符+-,它是左结合的优先级是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 is a Vector2D instance with values of (4.0, -2.0)


这个操作符将两个向量的x值相加,从第一个向量的y值中减去第二个向量的y值。因为本质上它还算是“加”,所以它被赋予了和默认中缀操作符+和-相同的结合性和优先级。完整的Swfit中操作符的优先级设置,参见 表达式(Expressions)。

NOTE

当定义一个前缀或者后缀操作符,不必指定优先级。但是,如果同时适用一个前缀和后缀的操作符,后缀操作符会被先执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编程语言