[翻译]Swift编程语言——高级操作符
高级操作符在前面的 基本操作符(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位倍数对操作数乘或者除。左移移位就是对操作数做加倍,右移一位就是对操作数减半。 无符号整型的移位无符号整型的位移需要遵循: 这被称作逻辑移位。 下面的图标展示了 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; 但是,实际的结果是4,而不是0。高优先级的操作符会比低优先级的操作符先执行。Swfit和C语言一样,乘法(*)和取余操作符(%)的优先级都比加法操作符(+)高。结果就是它们会在加法执行前被执行。 然而,乘法和取余运算符具有相同的优先级。为了得到准确的执行顺序,就必须考虑他们的结合性了。乘法和取余运算符都是向左结合的。设想一下从左侧给这些表达式添加上原本隐藏的括号: ?2? + ((?3? * ?4?) % ?5?) (3 * 4)得到 12,所以上面等价于: ?2? + (?12? % ?5?) (12 % 5) 得到 2,所以等价于: ?2? + ?2 计算的最终结果是4。 完整的Swift操作符的优先级和结合性,参见 表达式(Expressions)。 NOTE 操作符函数类和结构体可以提供他们自己的对于已有操作符的实现。这被称作操作符重载。 下面的例子展示了在一个结构体内如何实现算术操作符加(+),定义了一个操作符函数来将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 当定义一个前缀或者后缀操作符,不必指定优先级。但是,如果同时适用一个前缀和后缀的操作符,后缀操作符会被先执行。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |