26 高级运算符

2019-10-11  本文已影响0人  微笑中的你

高级运算符

除了Basic Operators中描述的基本运算符之外,Swift还提供了几个执行更复杂值操作的高级运算符。这些包括C和Objective-C中您熟悉的所有按位和位移运算符。

与C中的算术运算符不同,Swift中的算术运算符默认不会溢出。溢出行为被捕获并报告为错误。要选择溢出行为,请使用Swift默认溢出的第二组算术运算符,例如溢出加法运算符(&+)。所有这些溢出运算符都以&符号开头&

定义自己的结构,类和枚举时,为这些自定义类型提供自己的标准Swift运算符实现会很有用。Swift可以轻松地为这些运算符提供定制的实现,并确切地确定它们对您创建的每种类型的行为。

您不仅限于预定义的运算符。Swift为您提供了自定义自定义中缀,前缀,后缀和赋值运算符的自由,具有自定义优先级和关联性值。这些运算符可以像任何预定义的运算符一样在代码中使用和采用,甚至可以扩展现有类型以支持您定义的自定义运算符。

按位运算符

按位运算符使您可以处理数据结构中的各个原始数据位。它们通常用于低级编程,例如图形编程和设备驱动程序创建。当您使用来自外部源的原始数据时,按位运算符也很有用,例如编码和解码数据以通过自定义协议进行通信。

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

NOT 按位非运算符 (~)

按位非运算符是一个前缀运算符,它出现在它操作的值之前,没有任何空格:

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

计算规则:0变1,1变0

AND按位和运算符 (&)

结合了两个数字的位数。它返回一个新的数字

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

计算规则:相同位都为1,结果为1,否则为0

OR按位或运算符 (|)

结合了两个数字的位数。它返回一个新的数字

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

计算规则:相同时返回相同的数字,不同返回1

XOR按位异或运算符 (^)

结合了两个数字的位数。它返回一个新的数字

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

计算规则:相同时返回0, 不同时返回1

无符号 按位左移运算符 (<<),按位右移运算符(>>)

let shiftBits: UInt8 = 4   // 00000100 in binary
shiftBits << 1             // 00001000
shiftBits << 2             // 00010000
shiftBits << 5             // 10000000
shiftBits << 6             // 00000000
shiftBits >> 2             // 00000001

计算规则:按位左移时,右边补0,数值变大;按位右移时,左边补0,数值变大。

有符号整数的移位行为

对于有符号整数而言,移位行为比无符号整数更复杂,因为有符号整数用二进制表示。(为简单起见,下面的示例基于8位有符号整数,但相同的原则适用于任何大小的有符号整数。)

有符号整数使用它们的第一位(称为符号位)来指示整数是正还是负。符号位0表示正数,符号1表示负数。

其余位(称为值位)存储实际值。正数以与无符号整数完全相同的方式存储,向上计数0。以下是Int8查找数字的位数4:


bitshiftSignedFour_2x.png

符号位是0(意思是“正”),七个值位 只是4用二进制表示法写的数字。

但是,负数以不同方式存储。它们的存储方式是将它们的绝对值减去2幂的值n,其中n是值的位数。一个八位数有七个值的位,所以这意味着2到的7次方,也就是128。

以下是Int8查找数字的位数-4:

bitshiftSignedMinusFour_2x.png

这一次,符号位是1(意思是“负”),七个值位的二进制值为124(即):128 - 4


bitshiftSignedMinusFourValue_2x.png

负数的这种编码称为二进制补码表示。这似乎是一种代表负数的不寻常方式,但它有几个优点。

首先,您可以添加-1到-4,简单地通过执行一个标准二进制加法全部八个位(包括符号位),并丢弃任何不适合在八位一旦你完成:


bitshiftSignedAddition_2x.png

第二,这两个补码的表示也让你把负数的位元像正数一样向左或向右移动,每向左移动一位,或者每向右移动一位。为了实现这一点,当有符号整数向右移位时,将使用一个额外的规则:当您将有符号整数向右移位时,应用与无符号整数相同的规则,但要用符号位填充左边的任何空位,而不是用零填充。

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

由于正数和负数存储的特殊方式,将它们右移会使它们更接近于零。在这个移位过程中保持符号的位不变意味着负整数在其值趋于零时仍然为负。

溢出运算符

如果您试图将一个数字插入一个不能保存该值的整数常量或变量,默认情况下Swift将报告一个错误,而不是允许创建一个无效的值。当您处理太大或太小的数字时,这种行为会提供额外的安全性。

例如,Int16整数类型可以保存-32768到32767之间的任何带符号整数。试图将Int16常量或变量设置为超出此范围的数字会导致错误:

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

swift 提供了三个溢出运算符

值溢出

数字可以正向和负向溢出。

下面是使用overflow加法运算符(&+)允许无符号整数向正方向溢出时会发生的情况:

var unsignedOverflow = UInt8.max
 ***unsignedOverflow equals 255, which is the maximum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &+ 1
 ***unsignedOverflow is now equal to 0

变量unsignedOverflow初始化时使用UInt8可以容纳的最大值(255或二进制中的11111111)。然后使用溢出加法运算符(&+)将其增加1。这使得它的二进制表示刚好超过UInt8所能容纳的大小,导致溢出超出其边界,如下图所示。在溢出添加之后,UInt8的范围内保留的值为00000000或0。

overflowAddition_2x.png

当允许无符号整数向负方向溢出时,也会发生类似的情况。下面是一个使用溢出减法运算符(&-)的例子:

var unsignedOverflow = UInt8.min
*** unsignedOverflow equals 0, which is the minimum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &- 1
*** unsignedOverflow is now equal to 255

UInt8可以容纳的最小值是0,或者二进制中的00000000。如果您使用溢出减法运算符(&-)从00000000中减去1,这个数字将溢出并四舍五入到11111111,或者十进制255。

overflowUnsignedSubtraction_2x.png

带符号整数也会发生溢出。所有有符号整数的加法和减法都是按位执行的,符号位包含在加减的数字中,如位左和位右移位运算符所述。

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

Int8可以容纳的最小值是-128,或者二进制的10000000。用溢出操作符从这个二进制数中减去1,得到一个二进制值01111111,它切换符号位并得到正127,这是Int8所能容纳的最大正值。

overflowSignedSubtraction_2x.png

对于有符号整数和无符号整数,正向溢出从最大有效整数值绕回最小值,负向溢出从最小值绕回最大值。

优先级和结合性

在计算复合表达式的计算顺序时,考虑每个操作符的优先级和结合度是很重要的。例如,运算符优先级解释了为什么下面的表达式等于17。

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

3取余4等于 3 ,然后乘以5 ,最后加上2 等于17.

类和结构可以提供它们自己的现有操作符实现。这称为重载现有操作符。

下面的示例展示了如何为自定义结构实现算术加法运算符(+)。算术加法运算符是一个二进制运算符,因为它对两个目标进行运算,而且据说是中缀运算符,因为它出现在这两个目标之间。

本例为二维位置向量(x, y)定义了一个Vector2D结构,然后定义了一个+方法来将Vector2D结构的实例相加:

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

extension Vector2D {
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}

运算符方法被定义为Vector2D上的类型方法,方法名与要重载的运算符匹配(+)。因为加法不是向量的基本行为的一部分,所以类型方法是在Vector2D的扩展中定义的,而不是在Vector2D的主结构声明中定义的。因为算术加法运算符是一个二进制运算符,所以这个运算符方法接受两个Vector2D类型的输入参数,并返回一个也是Vector2D类型的输出值。

在这个实现中,输入参数分别命名为left和right,以表示将位于+操作符左侧和右侧的Vector2D实例。该方法返回一个新的Vector2D实例,该实例的x和y属性由两个Vector2D实例的x和y属性之和初始化,这两个向量和y属性相加。

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),如下图所示。


vectorAddition_2x.png

前缀和后缀操作符

上面显示的示例演示了二进制中缀运算符的自定义实现。类和结构还可以提供标准一元运算符的实现。一元运算符只操作一个目标。如果它们位于目标(如-a)之前,则为前缀;如果它们位于目标(如b!)之后,则为后缀操作符。

在声明运算符方法时,通过在func关键字之前编写前缀或后缀修饰语来实现前缀或后缀一元运算符:

extension Vector2D {
    static 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
// 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)

复合赋值操作符

复合赋值操作符将赋值(=)与另一个操作组合在一起。例如,加法赋值操作符(+=)将加法和赋值合并到一个操作中。将复合赋值运算符的左输入参数类型标记为inout,因为该参数的值将直接从运算符方法中修改。

下面的例子为Vector2D实例实现了一个加法赋值运算符方法:

extension Vector2D {
    static func += (left: inout 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)

注意:只有复合赋值运算符可以重载

等价运算符

默认情况下,自定义类和结构没有等效操作符的实现,即等号操作符(==)和不等号操作符(!=)。通常实现==操作符,并使用标准库的默认实现!=操作符,该操作符否定==操作符的结果。实现==操作符有两种方法:您可以自己实现它,或者对于许多类型,您可以要求Swift为您合成一个实现。在这两种情况下,都要向标准库的Equatable协议添加一致性。

您提供了==运算符的实现方法,与实现其他中缀运算符的方法相同:

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

上面的例子实现了一个==操作符来检查两个Vector2D实例是否具有相同的值。在Vector2D上下文中,将“equal”视为“两个实例具有相同的x值和y值”是有意义的,因此这是操作符实现使用的逻辑。

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

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

在许多简单的情况下,您可以要求Swift为您提供等效操作符的综合实现。Swift为以下几种自定义类型提供了综合实现:

要接收==的合成实现,请在包含原始声明的文件中声明Equatable一致性,而无需自己实现==操作符。

下面的示例为三维位置向量(x, y, z)定义了一个Vector3D结构,类似于Vector2D结构。因为x、y和z属性都是可相等的类型,Vector3D接收等效操作符的合成实现。

struct Vector3D: Equatable {
    var x = 0.0, y = 0.0, z = 0.0
}

let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
    print("These two vectors are also equivalent.")
}
// Prints "These two vectors are also equivalent."

自定义运算符

除了Swift提供的标准操作符外,您还可以声明和实现自己的自定义操作符。有关可用于定义自定义运算符的字符列表,请参见运算符。

使用运算符关键字在全局级别声明新运算符,并使用前缀、中缀或后缀修饰符进行标记:

prefix operator +++

上面的示例定义了一个名为+++的前缀操作符。这个操作符在Swift中没有现有的含义,因此在下面使用Vector2D实例的特定上下文中,它被赋予了自己的自定义含义。在本例中,c++被视为一个新的“前缀加倍”操作符。通过使用前面定义的加法赋值运算符将向量添加到自身,它将Vector2D实例的x和y值加倍。要实现c++操作符,您需要向Vector2D添加一个名为c++的类型方法,如下所示:

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

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)

自定义中缀运算符的优先级

自定义中缀操作符每个都属于一个优先组。优先组指定操作符相对于其他中缀操作符的优先级,以及操作符的相联性。有关这些特性如何影响中缀运算符与其他中缀运算符的交互的说明,请参阅优先级和关联性。

未显式放入优先组的自定义中缀运算符将被赋予一个默认优先组,其优先级立即高于三元条件运算符的优先级。

下面的例子定义了一个新的自定义中缀运算符+-,它属于优先级组additionpriority:

infix operator +-: AdditionPrecedence
extension Vector2D {
    static 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)

<<返回目录

上一篇下一篇

猜你喜欢

热点阅读