★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ➤微信公众号:山青咏芝(shanqingyongzhi) ➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/) ➤GitHub地址:https://github.com/strengthen/LeetCode ➤原文地址:https://www.cnblogs.com/strengthen/p/10973030.html ➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。 ➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创! ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
热烈欢迎,请直接点击!!!
进入博主App Store主页,下载使用各个作品!!!
注:博主将坚持每月上线一个新app!!!
除了“基本运算符”中描述的运算符外,Swift还提供了一些高级运算符,它们可以执行更复杂的值操作。这些包括您将在C和Objective-C中熟悉的所有按位和移位运算符。
与C中的算术运算符不同,Swift中的算术运算符默认情况下不会溢出。溢出行为被捕获并报告为错误。要选择溢出行为,请使用默认情况下会溢出的Swift第二组算术运算符,例如溢出加法运算符(&+ )。所有这些溢出运算符都以“&”号开头& 。
当定义自己的结构,类和枚举时,为这些自定义类型提供标准Swift运算符的实现可能会很有用。使用Swift,可以轻松地为这些运算符提供量身定制的实现,并轻松确定您创建的每种类型的行为。
您不仅限于预定义的运算符。使用Swift,您可以自由定义自己的自定义中缀,前缀,后缀和赋值运算符,并具有自定义优先级和关联性值。这些运算符可以像任何预定义的运算符一样在您的代码中使用和采用,甚至可以扩展现有类型以支持您定义的自定义运算符。
按位运算符
按位运算符使您可以操纵数据结构中的各个原始数据位。它们通常用于低级编程中,例如图形编程和设备驱动程序创建。当您使用来自外部源的原始数据(例如,对数据进行编码和解码以通过自定义协议进行通信)时,按位运算符也很有用。
Swift支持C中找到的所有按位运算符,如下所述。
按位NOT运算符
该位NOT运算符(~ )反转数所有位:
按位NOT运算符是一个前缀运算符,它紧接在其运算的值之前出现,没有任何空格:
- let initialBits: UInt8 = 0b00001111
- let invertedBits = ~initialBits // equals 11110000
UInt8 整数有8位,可以存储0 和之间的任何值255 。本示例UInt8 使用二进制值初始化一个整数,该二进制值00001111 的前四位设置为0 ,后四位设置为1 。等效于的十进制值15 。
然后使用按位NOT运算符创建一个名为的新常量invertedBits ,该常量等于initialBits ,但所有位都取反。零变成1,而1变成零。invertedBits is的值11110000 ,它等于的无符号十进制值240 。
按位与运算符
该位AND运算符(& )结合了两个数字的位数。它返回一个新的号码,其位被设置为1 仅当位是等于1 在两个输入数字:
在下面的例子中,值firstSixBits 和lastSixBits 两个具有四个中间位等于1 。按位AND运算符将它们组合成数字00111100 ,该数字等于无符号十进制值60 :
- let firstSixBits: UInt8 = 0b11111100
- let lastSixBits: UInt8 = 0b00111111
- let middleFourBits = firstSixBits & lastSixBits // equals 00111100
按位或运算符
的位或运算符(| )两个数的比特进行比较。操作者返回一个新的数字,其比特被设置为1 如果所述比特等于1 在任一输入号码:
在下面的例子中,的值someBits 和moreBits 具有不同的位设置为1 。按位OR运算符将它们组合起来以生成数字11111110 ,该数字等于的无符号十进制数254 :
- let someBits: UInt8 = 0b10110010
- let moreBits: UInt8 = 0b01011110
- let combinedbits = someBits | moreBits // equals 11111110
按位XOR运算符
的按位XOR运算符,或“异或运算符”( ^ ),比较两个数的位。运算符返回一个新数字,该数字的位设置为1 输入位不同的地方,并设置为0 输入位相同的地方:
在下面的例子中,的值firstBits 和otherBits 各自具有位设定为1 在该其他没有一个位置。按位XOR运算符将这两个位都设置1 为其输出值。和中的所有其他位都firstBits 与otherBits 匹配,并0 在输出值中设置为:
- let firstBits: UInt8 = 0b00010100
- let otherBits: UInt8 = 0b00000101
- let outputBits = firstBits ^ otherBits // equals 00010001
按位左移和右移运算符
在按位左移位运算符(<< )和逐位向右移位运算符(>> )中的数向左或由特定数量的地方向右移动的所有位,根据下面定义的规则。
按位左移和右移具有将整数乘以或除以2的作用。将整数的位向左移一位将其值加倍,而将其向右移一位将其值减半。
无符号整数的移位行为
无符号整数的位移行为如下:
- 现有位向左或向右移动所请求的位数。
- 任何超出整数存储范围的位都将被丢弃。
- 在将原始位向左或向右移动后,将零插入到留在后面的空间中。
这种方法称为逻辑转换。
下图显示的结果(位置向左移动)和的结果(位置向右移动)。蓝色数字将移位,灰色数字将被丢弃,而橙色零将被插入: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 常量pink 来存储粉红色的级联样式表颜色值。CSS颜色值以Swift的十六进制数字表示形式#CC6699 编写0xCC6699 。然后,按位AND运算符()和按位右移运算符()将该颜色分解为红色(CC ),绿色(66 )和蓝色(99 )分量。& >>
红色分量是通过在数字0xCC6699 和之间执行按位与运算而获得的0xFF0000 。中的零0xFF0000 有效地“屏蔽”的第二个和第三个字节0xCC6699 ,从而导致6699 忽略并留下0xCC0000 结果。
然后,此数字向右()移16位。十六进制数中的每对字符使用8位,因此向右移16位将转换为。这与相同,其十进制值为。>> 16 0xCC0000 0x0000CC 0xCC 204
类似地,绿色分量是通过在数字0xCC6699 和之间执行按位与运算而获得的0x00FF00 ,其输出值为0x006600 。然后,此输出值向右移动八位,给出一个值为0x66 ,其十进制值为102 。
最后,通过在数字0xCC6699 和之间执行按位与运算,可以得到蓝色分量。蓝色分量0x0000FF 的输出值为0x000099 。由于0x000099 早已等于0x99 ,其十进制值为153 ,因此使用该值时不会将其向右移动,
有符号整数的移位行为
有符号整数的移位行为比无符号整数的移位行为更为复杂,因为有符号整数以二进制形式表示。(为简单起见,下面的示例基于8位带符号整数,但相同的原理适用于任何大小的带符号整数。)
有符号整数使用其第一位(称为符号位)来指示该整数是正数还是负数。符号位0 表示正,符号位1 表示负。
其余位(称为值位)存储实际值。从无符号整数开始,正数的存储方式与无符号整数完全相同0 。这是Int8 查找数字中的位的方式4 :
符号位是0 (表示“正”),而七个值位只是数字4 ,用二进制表示法书写。
但是,负数的存储方式不同。通过将的绝对值减去2 的幂来存储它们n ,其中n 是值位数。一个八位数字具有七个值位,因此这意味着或2 的幂。7 128
这是Int8 查找数字中的位的方式-4 :
这次,符号位为1 (表示“负”),而七个值位的二进制值为124 (即):128 - 4
负数的这种编码称为二进制补码表示。表示负数似乎是一种不寻常的方式,但是它有几个优点。
首先,您可以添加-1 到-4 ,简单地通过执行一个标准二进制加法全部八个位(包括符号位),并丢弃任何不适合在八位一旦你完成:
其次,二进制补码表示法还使您可以像正数一样向左和向右移动负数的位,并且仍然会在每次向左移时将它们加倍,或者将向右移的每一位减半。为此,将有符号整数向右移动时会使用一条额外的规则:当您将有符号整数向右移动时,应用与无符号整数相同的规则,但是用符号bit填充左侧的任何空位,而不是而不是零。
此操作可确保有符号整数向右移位后具有相同的符号,这称为算术移位。
由于存储正负数的特殊方式,将它们中的任意一个向右移动都会使它们更接近于零。在此移位期间将符号位保持不变意味着负整数在其值接近零时仍为负。
溢出运算符
如果您尝试将数字插入不能容纳该值的整数常量或变量中,则默认情况下,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提供了三个算术溢出运算符,它们选择采用溢出行为进行整数计算。这些运算符都以“&”号开头& :
-
溢流加法(
&+ )
-
溢出减法(
&- )
-
溢出乘法(
&* )
价值溢出
数字可以在正方向和负方向上溢出。
这是使用溢出加法运算符(&+ )允许无符号整数沿正方向溢出的情况的示例:
- 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 使用aUInt8 可以保持的最大值(255 ,或11111111 二进制)进行初始化。然后1 使用溢出加法运算符(&+ )将其递增。这将其二进制表示形式推到aUInt8 可以容纳的大小之上,从而导致其溢出超出其范围,如下图所示。UInt8 溢出相加后保留在的范围内的值为00000000 ,或为零。
当允许无符号整数沿负方向溢出时,也会发生类似的情况。以下是使用溢出减法运算符(&- )的示例:
- 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
aUInt8 可以保持的最小值为零或00000000 二进制。如果减去1 从00000000 使用溢出减法运算符(&- ),这个数字将溢出并环绕到11111111 ,或255 十进制。
有符号整数也会发生溢出。对带符号整数的所有加法和减法都是按位方式执行的,其中符号位作为要添加或减去的数字的一部分包括在内,如按位左移和右移运算符中所述。
- 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 ,即anInt8 可以保持的最大正值。
对于有符号和无符号整数,正方向上的溢出会从最大有效整数值回绕到最小值,负方向上的溢出会从最小值到最大值回绕。
优先性和关联性
运算符优先级给予某些运算符比其他运算符更高的优先级;这些运算符首先被应用。
运算符关联性定义了如何将具有相同优先级的运算符组合在一起-从左侧分组还是从右侧分组。可以将其理解为“它们与他们左侧的表情相关联”或“它们与他们右侧的表情相关联”的含义。
在计算复合表达式的计算顺序时,必须考虑每个运算符的优先级和关联性,这一点很重要。例如,运算符优先级解释了为什么以下表达式等于17 。
- 2 + 3 % 4 * 5
- // this equals 17
如果严格从左到右阅读,则可能希望表达式的计算如下:
但是,实际答案17 不是5 。较高优先级的运算符先于较低优先级的运算符进行评估。与C语言一样,在Swift中,余数运算符(% )和乘法运算符(* )的优先级高于加法运算符(+ )。结果,在考虑添加之前都对它们都进行了评估。
但是,余数和乘法的优先级彼此相同。要确定要使用的确切评估顺序,您还需要考虑它们的关联性。余数和乘法都与左侧的表达式相关联。可以认为这是在表达式的这些部分的左侧添加隐式括号,从它们的左侧开始:
(3 % 4) 是3 ,因此等效于:
(3 * 5) 是15 ,因此等效于:
此计算得出的最终答案17 。
有关Swift标准库提供的运算符的信息,包括运算符优先级组和关联性设置的完整列表,请参阅运算符声明。
笔记
Swift的运算符优先级和关联性规则比C和Objective-C中的运算符更简单且更可预测。但是,这意味着它们与基于C的语言并不完全相同。将现有代码移植到Swift时,请务必确保操作员交互仍然按照您期望的方式运行。
操作员方法
类和结构可以提供它们自己的现有运算符的实现。这称为使现有运算符超载。
下例显示了如何+ 为自定义结构实现算术加法运算符()。算术加法运算符是二进制运算符,因为它在两个目标上进行操作,并且由于在两个目标之间出现而被称为中缀。
该示例Vector2D 为二维位置矢量定义了一个结构,然后定义了将该结构的实例加在一起的operator方法的定义:(x, y) 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 。因为算术加法运算符是二进制运算符,所以此运算符方法采用类型为2的两个输入参数,Vector2D 并返回类型也是的单个输出值Vector2D 。
在此实现中,输入参数被命名为left 并right 表示Vector2D 将位于+ 运算符左侧和右侧的实例。该方法返回一个新Vector2D 实例,该实例的x 和y 属性用两个实例加在一起的x 和y 属性之和初始化Vector2D 。
type方法可以用作现有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 )和后缀运营商,如果他们遵循自己的目标(如b! )。
通过在声明operator方法时在关键字之前编写prefix orpostfix 修饰符,可以实现前缀或后缀一元运算func 符:
- extension Vector2D {
- static prefix func - (vector: Vector2D) -> Vector2D {
- return Vector2D(x: -vector.x, y: -vector.y)
- }
- }
上面的示例-a 为Vector2D 实例实现了一元减运算符()。一元减运算符是前缀运算符,因此此方法必须使用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)
复合赋值运算符
复合赋值运算符将赋值(= )与另一个操作结合在一起。例如,加法赋值运算符(+= )将加法和赋值组合到一个操作中。您将复合赋值运算符的左输入参数类型标记为inout ,因为该参数的值将直接从operator方法内部进行修改。
下面的示例为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)
笔记
无法重载默认的赋值运算符(= )。仅复合赋值运算符可以重载。同样,三元条件运算符()也不能重载。a ? b : c
等价运算符
默认情况下,自定义类和结构没有实现等效运算符,即等于运算符(== )和不等于运算符(!= )的实现。通常== ,您可以实现运算符,并使用标准库的默认!= 运算符实现来抵消运算符的结果== 。有两种实现== 操作符的方法:您可以自己实现,或者对于许多类型,可以要求Swift为您综合实现。在这两种情况下,您都将对标准库的Equatable 协议添加一致性。
您== 以与实现其他中缀运算符相同的方式提供该运算符的实现:
- extension Vector2D: Equatable {
- static func == (left: Vector2D, right: Vector2D) -> Bool {
- return (left.x == right.x) && (left.y == right.y)
- }
- }
上面的示例实现了一个== 运算符,用于检查两个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 {
- print("These two vectors are equivalent.")
- }
- // Prints "These two vectors are equivalent."
在许多简单的情况下,您可以要求Swift为您提供等效运算符的综合实现,如使用综合实现采用协议中所述。
定制运营商
除了Swift提供的标准运算符外,您还可以声明和实现自己的自定义运算符。有关可用于定义自定义运算符的字符列表,请参见Operators。
新的运营商使用的是在全球范围内声明的operator 关键字,并且都标有prefix ,infix 或postfix 修饰:
上面的示例定义了一个名为的新前缀运算符+++ 。该运算符在Swift中没有现有的含义,因此在下面使用Vector2D 实例的特定上下文中为其赋予了自定义的含义。就本示例而言,+++ 被视为新的“前缀加倍”运算符。通过使用前面定义的加法赋值运算符将向量加到自身上,它将实例的x 和y 值加倍Vector2D 。为了实现+++ 运营商,你添加一个名为类型的方法+++ ,以Vector2D 如下:
- 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)
自定义中缀运算符的优先级
自定义中缀运算符每个都属于一个优先级组。优先级组指定一个运算符相对于其他中缀运算符的优先级,以及该运算符的关联性。有关这些特征如何影响中缀运算符与其他中缀运算符的交互的说明,请参见优先级和关联性。
未明确放置在优先级组中的自定义中缀运算符将被赋予默认优先级组,该优先级组的优先级立即高于三元条件运算符的优先级。
以下示例定义了一个名为的新的自定义中缀运算符+- ,该运算符属于优先级组AdditionPrecedence :
- 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)
该运算符将x 两个向量的值相加,然后y 从第一个向量中减去第二个向量的值。因为它本质上是一个“加法”运算符,所以它被赋予与诸如+ 和的加法中缀运算符相同的优先级组- 。有关Swift标准库提供的运算符的信息,包括运算符优先级组和关联性设置的完整列表,请参阅运算符声明。有关优先级组的更多信息,以及有关定义自己的运算符和优先级组的语法,请参阅“运算符声明”。
笔记
在定义前缀或后缀运算符时,您无需指定优先级。但是,如果将前缀和后缀运算符都应用于同一操作数,则将首先应用后缀运算符。
结果生成器
一个结果建设者是你定义一个类型,对于创建嵌套数据,例如列表或树,以自然,声明的方式增加了语法。使用结果构建器的代码可以包含普通的Swift语法(如if 和for )来处理条件数据或重复数据。
下面的代码定义了几种使用星号和文本在单行上绘制的类型。
- protocol Drawable {
- func draw() -> String
- }
- struct Line: Drawable {
- var elements: [Drawable]
- func draw() -> String {
- return elements.map { $0.draw() }.joined(separator: "")
- }
- }
- struct Text: Drawable {
- var content: String
- init(_ content: String) { self.content = content }
- func draw() -> String { return content }
- }
- struct Space: Drawable {
- func draw() -> String { return " " }
- }
- struct Stars: Drawable {
- var length: Int
- func draw() -> String { return String(repeating: "*", count: length) }
- }
- struct AllCaps: Drawable {
- var content: Drawable
- func draw() -> String { return content.draw().uppercased() }
- }
该Drawable 协议定义了对可以绘制的东西(例如线条或形状)的要求:类型必须实现一个draw() 方法。该Line 结构表示单线工程图,并且它是大多数工程图的顶层容器。要绘制a Line ,该结构调用draw() 该行的每个组件,然后将 |
请发表评论