Swift5.2 语言指南(二十九)高级运算符

人走茶凉 提交于 2020-05-08 02:06:39

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/10973030.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

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

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

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

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

按位运算符

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

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

按位NOT运算符

位NOT运算符~)反转数所有位:

../_images/bitwiseNOT_2x.png

按位NOT运算符是一个前缀运算符,它紧接在其运算的值之前出现,没有任何空格:

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

UInt8整数有8位,可以存储0之间的任何值255本示例UInt8使用二进制值初始化一个整数,该二进制值00001111的前四位设置为0,后四位设置为1等效于的十进制值15

然后使用按位NOT运算符创建一个名为的新常量invertedBits,该常量等于initialBits,但所有位都取反。零变成1,而1变成零。invertedBitsis 的值11110000,它等于的无符号十进制值240

按位与运算符

位AND运算符&)结合了两个数字的位数。它返回一个新的号码,其位被设置为1仅当位是等于1两个输入数字:

../_images/bitwiseAND_2x.png

在下面的例子中,值firstSixBitslastSixBits两个具有四个中间位等于1按位AND运算符将它们组合成数字00111100,该数字等于无符号十进制值60

  1. let firstSixBits: UInt8 = 0b11111100
  2. let lastSixBits: UInt8 = 0b00111111
  3. let middleFourBits = firstSixBits & lastSixBits // equals 00111100

按位或运算符

位或运算符|)两个数的比特进行比较。操作者返回一个新的数字,其比特被设置为1如果所述比特等于1任一输入号码:

../_images/bitwiseOR_2x.png

在下面的例子中,的值someBitsmoreBits具有不同的位设置为1按位OR运算符将它们组合起来以生成数字11111110,该数字等于的无符号十进制数254

  1. let someBits: UInt8 = 0b10110010
  2. let moreBits: UInt8 = 0b01011110
  3. let combinedbits = someBits | moreBits // equals 11111110

按位XOR运算符

按位XOR运算符,或“异或运算符”( ^),比较两个数的位。运算符返回一个新数字,该数字的位设置为1输入位不同的地方,并设置为0输入位相同的地方:

../_images/bitwiseXOR_2x.png

在下面的例子中,的值firstBitsotherBits各自具有位设定为1在该其他没有一个位置。按位XOR运算符将这两个位都设置1为其输出值。和中的所有其他位都firstBitsotherBits匹配,并0在输出值中设置为

  1. let firstBits: UInt8 = 0b00010100
  2. let otherBits: UInt8 = 0b00000101
  3. let outputBits = firstBits ^ otherBits // equals 00010001

按位左移和右移运算符

按位左移位运算符<<)和逐位向右移位运算符>>)中的数向左或由特定数量的地方向右移动的所有位,根据下面定义的规则。

按位左移和右移具有将整数乘以或除以2的作用。将整数的位向左移一位将其值加倍,而将其向右移一位将其值减半。

无符号整数的移位行为

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

  1. 现有位向左或向右移动所请求的位数。
  2. 任何超出整数存储范围的位都将被丢弃。
  3. 在将原始位向左或向右移动后,零被插入到留在后面的空间中。

这种方法称为逻辑转换

下图显示了的结果移到左边的位置)和移到右边的位置)的结果。蓝色数字将移位,灰色数字将被丢弃,而橙色零将被插入:11111111 << 111111111111111111 >> 1111111111

../_images/bitshiftUnsigned_2x.png

这是Swift代码中移位的外观:

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

您可以使用位移来编码和解码其他数据类型内的值:

  1. let pink: UInt32 = 0xCC6699
  2. let redComponent = (pink & 0xFF0000) >> 16 // redComponent is 0xCC, or 204
  3. let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent is 0x66, or 102
  4. let blueComponent = pink & 0x0000FF // blueComponent is 0x99, or 153

本示例使用一个UInt32常量pink来存储粉红色的级联样式表颜色值。CSS颜色值以Swift的十六进制数字表示形式#CC6699编写0xCC6699然后,按位AND运算符()和按位右移运算符(将该颜色分解为红色(CC),绿色(66)和蓝色(99)分量&>>

红色分量是通过在数字0xCC6699之间执行按位与运算而获得的0xFF00000xFF0000有效地“掩盖了”的第二个和第三个字节0xCC6699,导致6699忽略并留下0xCC0000结果。

然后,此数字向右(移16位十六进制数中的每对字符使用8位,因此向右移16位将转换这与相同,后者的十进制值为>> 160xCC00000x0000CC0xCC204

类似地,绿色分量是通过在数字0xCC6699之间执行按位与运算而获得的0x00FF00,其输出值为0x006600然后,此输出值向右移动八位,给出一个值为0x66,其十进制值为102

最后,将蓝色成分是通过执行按位与数字之间获得0xCC66990x0000FF,其给出的输出值0x000099无需将其右移,因为0x000099已经等于0x99,其十进制值为153

有符号整数的移位行为

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

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

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

../_images/bitshiftSignedFour_2x.png

符号位是0(表示“正”),而七个值位只是数字4,以二进制表示法书写。

但是,负数的存储方式不同。通过将的绝对值减去2的幂来存储它们n,其中n是值位数。一个八位数字具有七个值位,因此这意味着2的幂7128

这是Int8查找数字中的位的方式-4

../_images/bitshiftSignedMinusFour_2x.png

这次,符号位为1(表示“负”),而七个值位的二进制值为124(即):128 4

../_images/bitshiftSignedMinusFourValue_2x.png

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

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

../_images/bitshiftSignedAddition_2x.png

其次,二进制补码表示法还使您可以像正数一样向左和向右移动负数的位,并且仍然会在每次向左移时将它们加倍,或者将向右移的每一位减半。为此,将有符号整数向右移动时会使用一条额外的规则:当将有符号整数向右移动时,应用与无符号整数相同的规则,但是用符号bit填充左侧的任何空位,而不是而不是零。

../_images/bitshiftSigned_2x.png

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

由于存储正数和负数的特殊方式,将它们中的任意一个向右移动都会使它们更接近于零。在此移位期间将符号位保持不变意味着负整数在其值接近零时仍为负。

溢出运算符

如果您尝试将数字插入不能容纳该值的整数常量或变量中,则默认情况下,Swift会报告错误,而不是允许创建无效值。当您使用太大或太小的数字时,此行为可提供额外的安全性。

例如,Int16整数类型可以包含-32768之间的任何有符号整数32767尝试将Int16常量或变量设置为该范围之外的数字会导致错误:

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

当值太大或太小时提供错误处理,可以为边界值条件编码提供更大的灵活性。

但是,当您特别希望溢出条件截断可用位数时,可以选择采用这种行为,而不是触发错误。Swift提供了三种算术溢出运算符,它们选择采用溢出行为进行整数计算。这些运算符都以“&”号开头&

  • 溢流加法(&+
  • 溢出减法(&-
  • 溢出乘法(&*

价值溢出

数字可以在正方向和负方向上溢出。

这是使用溢出加法运算符(&+允许无符号整数沿正方向溢出的情况的示例

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

变量unsignedOverflow使用a UInt8可以保持的最大值255,或11111111二进制)进行初始化然后1使用溢出加法运算符(&+将其递增这将其二进制表示形式推到a UInt8可以容纳的大小之上,从而导致其溢出超出其范围,如下图所示。UInt8溢出相加后保留在的范围内的值为00000000或零。

../_images/overflowAddition_2x.png

当允许无符号整数沿负方向溢出时,也会发生类似的情况。这是使用溢出减法运算符(&-的示例

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

UInt8可以保持的最小值为零或00000000二进制。如果减去100000000使用溢出减法运算符(&-),这个数字将溢出并环绕到11111111,或255十进制。

../_images/overflowUnsignedSubtraction_2x.png

有符号整数也会发生溢出。对带符号整数的所有加法和减法都是按位方式执行的,其中包括符号位作为要添加或减去的数字的一部分,如按位左移和右移运算符中所述

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

Int8可以保留的最小值-128,或者10000000为二进制。1用溢出运算符减去该二进制数可得出二进制值01111111,该二进制值将切换符号位并给出正数127,即an Int8可以保持的最大正值

../_images/overflowSignedSubtraction_2x.png

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

优先与关联

运算符优先级给予某些运算符更高的优先级;首先应用这些运算符。

运算符关联性定义了如何将具有相同优先级的运算符组合在一起-从左侧分组还是从右侧分组。可以将其理解为“它们与左侧的表达式关联”或“它们与右侧的表达式关联”。

在计算复合表达式的计算顺序时,考虑每个运算符的优先级和关联性很重要。例如,运算符优先级解释了为什么以下表达式等于17

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

如果严格从左到右阅读,则可能希望表达式的计算如下:

  • 23等于5
  • 5余数4等于1
  • 1时间5等于5

但是,实际答案17不是5较高优先级的运算符先于较低优先级的运算符进行评估。与C语言一样,在Swift中,余数运算符(%)和乘法运算符(*)的优先级高于加法运算符(+)。结果,在考虑添加之前对它们都进行了评估。

但是,余数和乘法的优先级彼此相同要确定要使用的确切评估顺序,您还需要考虑它们的关联性。余数和乘法都与左侧的表达式相关联。可以认为这是在表达式的这些部分的左端开始添加隐式括号:

  1. 2 + ((3 % 4) * 5)

(3 4)3,因此等效于:

  1. 2 + (3 * 5)

(3 5)15,因此等效于:

  1. 2 + 15

此计算得出的最终答案17

有关Swift标准库提供的运算符的信息,包括运算符优先级组和关联性设置的完整列表,请参阅运算符声明

注意

Swift的运算符优先级和关联性规则比C和Objective-C中的运算符更简单且更可预测。但是,这意味着它们与基于C的语言并不完全相同。将现有代码移植到Swift时,请务必确保操作员交互仍然按照您期望的方式运行。

操作员方法

类和结构可以提供它们自己的现有运算符的实现。这称为使现有操作员超负荷

以下示例显示了如何+为自定义结构实现算术加法运算符()。算术加法运算符是二进制运算符,因为它在两个目标上进行操作,并且由于在两个目标之间出现而被称为中

该示例Vector2D为二维位置矢量定义了一个结构,然后定义了将该结构的实例加在一起运算符方法的定义(x, y)Vector2D

  1. struct Vector2D {
  2. var x = 0.0, y = 0.0
  3. }
  4. extension Vector2D {
  5. static func + (left: Vector2D, right: Vector2D) -> Vector2D {
  6. return Vector2D(x: left.x + right.x, y: left.y + right.y)
  7. }
  8. }

运算符方法定义为上的类型方法Vector2D,其方法名称与要重载的运算符(+相匹配由于加法不是向量基本行为的一部分,因此类型方法是在的扩展中定义的,Vector2D而不是在的主结构声明中定义的Vector2D因为算术加法运算符是二进制运算符,所以此运算符方法采用类型为2的两个输入参数,Vector2D并返回类型也是的单个输出值Vector2D

在此实现中,输入参数被命名leftright表示Vector2D将在+运算符左侧和右侧实例该方法返回一个新Vector2D实例,该实例的xy属性用两个实例加在一起xy属性初始化Vector2D

type方法可以用作现有Vector2D实例之间的中缀运算符

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

此示例将向量相加构成向量,如下所示。(3.0, 1.0)(2.0, 4.0)(5.0, 5.0)

../_images/vectorAddition_2x.png

前缀和后缀运算符

上面显示的示例演示了二进制中缀运算符的自定义实现。类和结构也可以提供标准一元运算符的实现一元运算符针对单个目标进行操作。他们是前缀,如果他们先于他们的目标(如-a)和后缀运营商,如果他们遵循自己的目标(如b!)。

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

  1. extension Vector2D {
  2. static prefix func - (vector: Vector2D) -> Vector2D {
  3. return Vector2D(x: -vector.x, y: -vector.y)
  4. }
  5. }

上面的示例-aVector2D实例实现了一元减号(一元减运算符是前缀运算符,因此此方法必须使用prefix修饰符限定

对于简单的数值,一元减运算符将正数转换为负数,反之亦然。Vector2D实例的相应实现对xy属性都执行此操作

  1. let positive = Vector2D(x: 3.0, y: 4.0)
  2. let negative = -positive
  3. // negative is a Vector2D instance with values of (-3.0, -4.0)
  4. let alsoPositive = -negative
  5. // alsoPositive is a Vector2D instance with values of (3.0, 4.0)

复合分配运算符

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

下面的示例为Vector2D实例实现一个附加赋值运算符方法

  1. extension Vector2D {
  2. static func += (left: inout Vector2D, right: Vector2D) {
  3. left = left + right
  4. }
  5. }

由于加法运算符是较早定义的,因此您无需在此处重新实现加法过程。取而代之的是,加法赋值运算符方法利用现有的加法运算符方法,并使用它来将左值设置为左值加上右值:

  1. var original = Vector2D(x: 1.0, y: 2.0)
  2. let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
  3. original += vectorToAdd
  4. // original now has values of (4.0, 6.0)

注意

无法重载默认赋值运算符(=)。仅复合赋值运算符可以重载。同样,三元条件运算符()也不能重载。c

等价运算符

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

==以与实现其他中缀运算符相同的方式提供该运算符的实现:

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

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

现在,您可以使用此运算符检查两个Vector2D实例是否等效:

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

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

  • 仅存储符合Equatable协议属性的结构
  • 仅具有符合Equatable协议的关联类型的枚举
  • 没有关联类型的枚举

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

以下示例Vector3D为三维位置矢量定义了一种结构,类似于该结构。因为性能都是一个的类型,接收合成的等价运营商的实现。(x, y, z)Vector2DxyzEquatableVector3D

  1. struct Vector3D: Equatable {
  2. var x = 0.0, y = 0.0, z = 0.0
  3. }
  4. let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
  5. let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
  6. if twoThreeFour == anotherTwoThreeFour {
  7. print("These two vectors are also equivalent.")
  8. }
  9. // Prints "These two vectors are also equivalent."

定制运营商

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

新的运营商使用的是在全球范围内声明的operator关键字,并且都标有prefixinfixpostfix修饰:

  1. prefix operator +++

上面的示例定义了一个名为的新前缀运算符+++该运算符在Swift中没有现有的含义,因此在下面使用Vector2D实例的特定上下文中为其赋予了自己的自定义含义就本示例而言,+++被视为新的“前缀加倍”运算符。通过使用前面定义的加法赋值运算符将向量添加到自身,它将实例xy加倍Vector2D为了实现+++运营商,你添加一个名为类型的方法+++,以Vector2D如下:

  1. extension Vector2D {
  2. static prefix func +++ (vector: inout Vector2D) -> Vector2D {
  3. vector += vector
  4. return vector
  5. }
  6. }
  7. var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
  8. let afterDoubling = +++toBeDoubled
  9. // toBeDoubled now has values of (2.0, 8.0)
  10. // afterDoubling also has values of (2.0, 8.0)

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

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

未明确放置在优先级组中的自定义中缀运算符将被赋予默认优先级组,其优先级立即高于三元条件运算符的优先级。

以下示例定义了一个名为的新的自定义中缀运算符+-,它属于优先级组AdditionPrecedence

  1. infix operator +-: AdditionPrecedence
  2. extension Vector2D {
  3. static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
  4. return Vector2D(x: left.x + right.x, y: left.y - right.y)
  5. }
  6. }
  7. let firstVector = Vector2D(x: 1.0, y: 2.0)
  8. let secondVector = Vector2D(x: 3.0, y: 4.0)
  9. let plusMinusVector = firstVector +- secondVector
  10. // plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

该运算符将x两个向量相加,然后y从第一个向量中减去第二个向量值。因为它本质上是一个“加法”运算符,所以它被赋予与诸如+和的加法中缀运算符相同的优先级组-有关Swift标准库提供的运算符的信息,包括运算符优先级组和关联性设置的完整列表,请参阅运算符声明有关优先级组的更多信息,以及有关定义自己的运算符和优先级组的语法,请参阅《运算符声明》

注意

在定义前缀或后缀运算符时,您无需指定优先级。但是,如果将前缀和后缀运算符都应用于同一操作数,则将首先应用后缀运算符。

 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!