swift高级表达式_按位运算符_溢出运算符_优先级和结合性

分享到:

基本运算符除了基本运算符中所描述的运算符外,Swift还提供了几种高级运算符,以执行更复杂的值处理流程。其中包括你将在C及Objective-C语言中所熟悉的所有按位和移位运算符。

不像C语言算术运算符,Swift算术运算符默认不会溢出。溢出行为会被捕获,并发出错误报告。要想选择溢出行为,请使用Swift中默认会溢出的第二套算术运算符,如溢出加法运算符(&+)。所有这些溢出运算符都以一个符号⑻开始。

当你定义自己的结构、类及枚举类型时,你自己执行这些自定义类型的标准Swift运算符可能会有用。Swift可以很容易地自定义执行这些运算符,并针对你所创建的每个类型准确地确定其应有的行为。

不仅限于预定义运算符。Swift让你利用自定义优先级和结合值随意定义自己的自定义中缀、前缀、后缀和赋值运算符。如同使用任何预定义运算符一样,在代码中使用这些运算符,你甚至可以扩展现有类型,以支持你所界定的自定义运算符。

 

按位运算符

 

按位运算符使你能够处理数据结构中的单个原始数据位。通常将它们用于底层编程,如图形编程和设备驱动程序的创建。按位运算符还可帮助你处理外部来源原始数据,如帮助你编码和解码自定义协议通信数据。

Swift支持所有C语言中发现的按位运算符,如下所述。

按位“非”运算符

 

按位“非”运算符(〜)将一个数值的所有位进行倒置:

按位“非”运算符是一个前缀运算符,直接显示在它作用的值之前,没有任何空格。

 

let initialBits: UInt8 = 0b00001111

let invertedBits = ~initialBits // equals 11110000

 

UInt8整数有8位,可存储0到255之间的任意值。该示例对一个带有二进制值00001111的UInt8整数进行了初始化,该二进制值前四位设置为0,后四设置为1,其等于十进制值15。

然后使用按位“非”运算符创建一个新的常数,称为invertedBits,其等于initalBits,但所有位元都倒置了。所有的零都变为一,所有的一都变为零。 invertedBits的值是11110000,等于无符号十进制值:240.

按位“和”运算符

 

按位“和”运算符(&)结合两个数的位元。只有当两个输入数字中位元等于1时,它才会返回一个将位元设置为1的新数:

在以下示例中,firstSxBits和lastSixBits均有四个中间位元为1。按位“和”运算符将它们进行组合,生成数值00111100,其等于无符号十进制值60:

 

let firstSixBits: UInt8 = 0b11111100

let lastSixBits: UInt8 = 0b00111111

let middleFourBits = firstSixBits & lastSixBits // equals 00111100

 

按位“或”运算符

 

按位“或”运算符(︱)将比较两个数的位元。如果任何一个输入数字中位元等于1,它会返回一个将位元设置为1的新数:

在以下示例中,someBits和moreBts值中不同位元被设置为1。按位“或”运算符将它们进行组合,生成数值11111110,其等于无符号十进制值254:

 

let someBits: UInt8 = 0b10110010

let moreBits: UInt8 = 0b01011110

let combinedbits = someBits | moreBits // equals 11111110

 

按位“异或”运算符

 

按位“异或”运算符或“互斥或运算符”(︿)),比较两个数的位元。当输入数字中的位元不同并被设置成1,以及当输入数字中的位元相同并被设置为0时,运算符返回一个新数:

在以下示例中,firstBits和otherBits的每一个值都有一个位元被设置为1,而另一值中相同的位元不设置为1。按位“异或”运算符将在其输出值中把两个数值的位元设置为1。firstBits和otherBits中所有其他位元都匹配,且输出值设置为0:

 

let firstBits: UInt8 = 0b00010100

let otherBits: UInt8 = 0b00000101

let outputBits = firstBits ^ otherBits // equals 00010001

 

按位左移位、右移位运算符

 

据以下定义的规则,按位左移位运算符(<<)和按位右移位运算符(>>)将一个数的所有位向左或向右移动一定位置。

按位左移位和按位右移位运算都具有整数乘以或除以二者的系数的作用。整数的位向左移动一位会使其值加倍,而向右移动一位会使其值减半。

无符号整数的移位行为

 

无符号整数的的移位行为过程如下:

1. 将现有的位元向左或向右移位到所要求的位置。

2. 丢弃任何移位超过整数存储边界的位元。

3. 待原始位元向左或向右移动后,将“零”插入所留空间内。

这种方法被称为逻辑移位。

下图显示了11111111 << 1(即11111111左移1位)和11111111 >> 1(即11111111右移1位)的结果。蓝色数字移位,灰色数字删除,插入橙色数字“零”:

下面展示的是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常数,标为“粉色”,用来存储层叠样式表,颜色值位粉色。在Swift十六进制数表示法中将CSS颜色值#CC6699写为0xCC6699。然后利用按位“与”运算符(&)和按位右移位运算符(>>)将这种颜色分解成红色(CC)、绿色(66)和蓝色(99)三种分量。

通过执行数字0xcc6699和0xFF0000之间的按位“与”运算得到红色分量。0xFF0000中的“零”有效“掩模”了0xcc6699的第二和第三字节,从而忽略6699,将0xcc0000留作结果。

然后,该数字会向右移动16个位数(>> 16)。十六进制数中每对字符都有8位,所以右移16位会将0xcc0000转换为0x0000cc。这与具有一个十进制的值为204的0xCC是一样的。

同样,通过执行数字0xcc6699和0x00FF00之间的按位“与”运算得到绿色分量,输出值为0x006600。然后将该输出值向右移动8位,得出数值0x66,即为十进制值102。

最后,通过执行数字0xcc6699和0x0000ff之间的按位“与”运算得到蓝色分量,输出值为0x000099。没有必要将它向右移位,因为0x000099已经是0x99,即为十进制值153。

将行为移位为符号整数

 

由于有符号整数用二进制表示,所以有符号整数的移位行为比无符号整数的移位行为要复杂。(为了简便,以下以8位有符号整数为例,但这些法则同样适用于任何位数的有符号整数。)

有符号整数利用其第一位元(称为符号位)来表示正负数。符号位0表示正,符号位1表示负。

其余位(被称为值位)用来存储实际值。正数的保存方式与无符号整数的保存方式完全相同,从0开始向上计数。以下为Int8类数中各位元找出数值4的方法:

符号位为0(表示“正数”),7值位正好是以二进制符号表示的数值4。

然而,负数的存储方式不同。从2的n次幂减去其绝对值,保存结果,其中,n是值位数。一个8位的数值有七个值位,所以这指的是2到7或128的功率。

下面所示的是一个Int8内部的元是找出数值-4的方法:

此时,符号位为1(表示“负数”),7值位表示二进制值124(即128-4):

负数的编码表示方式被称为是“2”的补码。这看似不是表示负数的常规方式,但其有几个优点。

首先,仅仅通过添加一个标准的8位(包括符号位)二进制数,并且在完成之后删去不适于8位二进制数的任何数字,你就可以从-1添加至-4:

其次,使用二进制补码表示法也可以使你像移动正数那样向左及向右移动负数的位,并且仍然得到这样的结果:即每次左移位会使其值加倍,每次右移位会使其值减半。为此,当有符号整数右移位时,需使用一个附加法则:

 

此操作可确保有符号整数右移位后具有相同的符号,称为算术移位。

由于正负数保存方案特殊,二者任一者右移位后都会更接近于零。在此移位过程中保持符号位相同

溢出运算符

 

如果您尝试在一个整数常量或其值不固定的变量中插入一个数字时,Swift会默认报告“出错”而不是允许创建一个无效值。当您处理的数字过大或过小时,这种行为可以提供更多的安全性。

例如,Int16整数类可容纳-32768〜32767之间的任何的有符号整数。尝试为此范围外的数字设定一个Int16常量或变量时会导致“出错”:

 

var potentialOverflow = Int16.max

// potentialOverflow equals 32767, which is the largest value an Int16 can hold

potentialOverflow += 1

// this causes an error

 

提供错误处理功能,当数值过大或过小时,可以使您更灵活地编码边界值条件。

不过,当您特别希望使用溢出条件截断有效位数字时,您可以选择性地认可该行为而不是触发一个“错误”。Swift提供了五种可以选择性地认可整数运算中溢出行为的算术溢出运算符。这些运算符都开始于符号(&):

上溢加法 (&+)

上溢减法 (&-)

上溢乘法 (&*)

上溢除法 (&/)

上溢余数(&%)

值溢出

 

以下示例揭示了运用溢出加法运算符(&+)允许无符号值溢出时的结果:

 

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

 

利用一个uInt8类可以容纳的最大值(255或二进制11111111)对变量willOverfow进行初始化。然后,通过使用溢出加法运算符(&+),该值将以1 为单位递增。这会使得其二进制表示值超出UInt8可容纳的量值,使其溢出其边界,如下图所示。溢出加法运算后仍在Uint8范围内的值是00000000或零:

 

值下溢

 

数字也可能会变得太而无法适应其类型的最大边界。举一例来说明:

UInt8类型可容纳的最小值是0(即,8位二进制数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

 

有符号整数会发生类似的下溢。所有有符号整数的减法运算都按直接按照二进制减法执行,正如《按位左、右移位运算符》中所描述的将符号位作为被减数字的一部分。一个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

 

上述溢出及下溢行为的最终结果归纳如下,对有符号和无符号整数而言,溢出行为总是从最大有效整数值折回至最小有效整数值,而下溢行为总是从最小有效整数值折回至最大有效整数值。

被零除

 

某一数字除以零(i / 0)或尝试计算某一数除以零的余数(ⅰ%0)都会导致“出错”:

 

let x = 1

let y = x / 0

 

然而,如果你除以零,这些操作符(&/和&%)的溢出版本会返回零值:

 

let x = 1

let y = x &/ 0

// y is equal to 0

 

优先级和结合性

 

运算符优先级使得一些运算符的优先级高于其他运算符;我们会首先计算这些优先级高的运算符。

运算符结合性界定了将优先级相同的运算符组合(或关联)在一起的方法 即,从左侧开始组合或从右侧开始组合。即指“它们关联它们左侧的表达式,”或“它们关联它们右侧的表达式。”

处理复合表达式的计算顺序时,重要的是要考虑每个运算符的优先级及结合性。举一例来说:为什么下面的表达式的值是4?

 

 2 + 3 * 4 % 5

 // this equals 4

 

 如果严格按照从左至右的顺序,你可能会将该表达式解读如下:

 加 3 等于5;

 5 乘以 4 等于 20;

 20 余除 5 等于 0

然而,实际答案是4而不是0。首先要评估高优先级运算符然后才是低优先级运算符。如同在C语言中,在Swift中,乘法运算符(*)及求余运算符(%)的运算优先级都有要高于加法运算符(+)的运算优先级。结果是,它们都在增加值被考虑之前进行了评估。

然而,乘法和余数有相同的优先级。要制定出所使用的精确的评估顺序,您还需要考虑它们的结合性。乘法和余数都与其左边的表达式有联系。 即指,围绕表达式的这些部分从其左侧开始加上隐式括号:

 

2 + ((3 * 4) % 5)

(3 * 4) is 12, so this is equivalent to:

2 + (12 % 5)

(12 % 5) is 2, so this is equivalent to:

2 + 2

 

通过这种计算方法得出的最终答案是4。

欲获得Swift运算符优先级和结合性法则完整列表,请参阅表达式。

注:Swift运算符的优先级和结合性法则比C语言和Objective-C语言中的法则更简单,更易于预测。然而,这也意味着它们与C语言不同。将现有代码植入Swift时,务必确保运算符仍按您预定的方式发挥交互作用。

运算符函数

 

类和结构可以自行实现现有的运算符。这被称为现有运算符过载。

以下示例显示了自定义结构算术加法运算符(+)生效的方法。算术加法运算符是一个可在两个目标上进行运算的二元运算符,并且因为它出现在两个目标中间,故而,也被称为中缀。

该示例首先定义了一个二维位置矢量(x,y)的vector2d结构,然后定义了添加到vector2d结构实例的运算符函数:

 

struct Vector2D {var x = 0.0, y = 0.0}

@infix func + (left: Vector2D, right: Vector2D) -> Vector2D {

return Vector2D(x: left.x + right.x, y: left.y + right.y)}

 

将运算符函数定义为一个名为+的全局函数,其接受两个vector2d类输入参数并返回一个vector2d类输出值。描述运算符函数时,您可以通过在函数关键字前写上@中缀属性使中缀运算符生效。

在实现过程中,将输入参数命名为“左”和“右”,用以表示+运算符左侧和右侧的vector2d实例。该函数返回一个新的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 + +),则它们为后缀运算符。

描述运算符函数时,您可以通过在函数关键字前写上@前缀或@后缀属性的方法,使前缀或后缀一元运算符生效:

 

@prefix func - (vector: Vector2D) -> Vector2D {

return Vector2D(x: -vector.x, y: -vector.y)

}

 

上面的例子中对Vector2D实例执行了一元减运算符(-a)。一元减运算符是前缀运算符,所以该函数必须具有@前缀属性。

对于简单的数值,一元减运算符可将正数转换为等效负数,反之亦然。相应的vector2d实例实现过程在x和y属性上均进行此运算:

 

let positive = Vector2D(x: 3.0, y: 4.0)

let negative = -positive

// 负值为一个值为(-3.0,-4.0)的vector2D实例中

let alsoPositive = -negative

// 同样,正值为一个值为(3.0,4.0)的vector2D实例

 

复合赋值运算符

 

复合赋值运算符将赋值(=)与另一个运算结合在一起。例如,加法赋值运算符(+=)将加法及赋值合并到一次运算中。实现复合赋值运算的运算符函数必须具备@ assgnment属性。您还必须将一个复合赋值运算符的左侧输入参数标记为inout,因为该参数值将直接在运算符函数中予以修改。 以下示例给出了vector2d实例加法赋值运算符函数的生效过程:

 

@assignment 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)

 

在vector2d实例的前缀增量运算符(++a)的生效过程中,您可以将@ assgnment属性与@ prefx或@ postfx属性合并:

 

@prefix @assignment func ++ (inout vector: Vector2D) -> Vector2D {

vector += Vector2D(x: 1.0, y: 1.0)

return vector

}

 

上述前缀增量运算符函数运用了先前定义的加法赋值运算符。将x、y值为1.0的vector2d与据此所称的vector2d相加,并返回结果:

 

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)

 

注:不可能使默认的赋值运算符(=)过载。只有复合赋值运算符可以过载。同样,三元条件运算符(a ?b:c)也不能过载。

等价运算符

 

自定义类和结构不接收等价运算符,即不接收“等于”运算符(==),与“不等于”运算符(!=)的默认生效过程。Swift无法猜出针对您自己的自定义类型中,什么具备“等于”资格,因为“等于”的含义取决于这些类型在您的代码中所发挥的作用。

欲使用等价运算符核对自己的自定义类型的等价性,请按照与其他中缀运算符相同的方式给出运算符的生效过程:

 

@infix func == (left: Vector2D, right: Vector2D) -> Bool {

return (left.x == right.x) && (left.y == right.y)

}

@infix 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."

 

自定义运算符

 

除了Swift提供的标准运算符外,您还可以描述并展示自己的自定义运算符的生效过程。自定义运算符只能用字符/ =来定义

 

- + * % < > ! & | ^ . ~.

 

可在全局范围内使用operator关键字用来描述新的运算符,并可将其描述为前缀、中缀或后缀运算符:

 

operator prefix +++ {}

 

上面的例子定义了一个新的名为+ + +的前缀运算符。该运算符在Swift中不具有实际意义,所以,下面给出了vector2d实例在具体处理情境中的自定义含义过程。为了说明该示例,将+++视为一个新的“前缀倍增加法器”运算符。利用先前定义的加法赋值运算符使矢量与自身相加,使vector2d实例的x和y值翻倍:

 

@prefix @assignment 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)

 

自定义中缀运算符的优先级和结合性

 

自定义中缀运算符也可以指定一种优先级和结合性。参见《优先级和结合性》,查看这两个特点是如何影响一个中缀运算符与其他中缀运算符的交互作用的。

结合性的可能的值为left、 right和 none。如果左结合运算符写入时紧临相同优先级的其他左结合运算符,则其关联其左侧。同样地,如果右结合运算符写入时紧临相同优先级的其他右结合运算符,则其关联其右侧。无法紧临优先级相同的其他运算符写入非关联运算符。

如果未指定,则结合性的值默认为none。如果未指定,则优先级的值默认为100。

以下示例定义了一个新的自定义中缀运算符,即+-,其具有左结合性,且优先级为140:

 

operator infix +- { 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值。因为其本质上是一个“加法”运算符,所以其被赋予与默认加法中缀运算符(如+和-)相同的(左)结合性和优先级值(140)。欲获得Swift运算符优先级和结合性法则完整列表,请参阅《表达式》。

昵    称:
验证码:

相关文档:

swift
IOS实例
ObjectiveC