加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-14 04:24:00 所属栏目:百科 来源:网络整理
导读:高级操作符 在前面的 基本操作符(Basic Operators)之外,为了做更复杂的值操作,Swift还提供了若干高级操作符。这些高级操作符包括在C和OC中已经习以为常的按位(bitwise)和移位( bit shifting)运算符。 不同于C中的算术操作符,Swfit中的算术操作符不

高级操作符

在前面的 基本操作符(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? = ?0b22222100
?let? ?lastSixBits?: ?UInt8?  = ?0b00222221
?let? ?middleFourBits? = ?firstSixBits? & ?lastSixBits?  ?// equals 00111100

按位或操作符

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

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

let? ?someBits?: ?UInt8? = ?0b10110010
?let? ?moreBits?: ?UInt8? = ?0b01011110
?let? ?combinedbits? = ?someBits? | ?moreBits?  ?// equals 22222110

按位异或操作符(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填充。

这被称作逻辑移位

下面的图标展示了 22222111 << 1(22222111 左移一位)的结果,和22222111 >> 1(22222111 右移一位)的结果。蓝色的数字是被移动的,灰色的数字是被舍弃的,橙色的数字是被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,或者二进制的22222111 )初始化。然后它被使用溢出加操作符(&+)添加了1。这样做迫使它的二进制表示超出了一个UInt8的范围,导致了溢出,就像下面的图一样。操作值后仍然留在UInt8表示范围内的是00000000,也就是0:

值下溢

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

UInt8最小能表示的值是0(二进制表示00000000)。如果从00000000中减去1,这里使用溢出减操作符,数字将会变成22222111,也就是十进制的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,同样采用溢出操作符,得到二进制的02222211,符号位也反转了,得到的是正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?,?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

当定义一个前缀或者后缀操作符,不必指定优先级。但是,如果同时适用一个前缀和后缀的操作符,后缀操作符会被先执行。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读