除了基本运算符之外,Swift还提供了几个执行更复杂值操作的高级运算符。这些包括C和Objective-C中您熟悉的所有按位和位移运算符。
与C中的算术运算符不同,Swift中的算术运算符默认不会溢出,溢出行为被捕获并报告为错误。要选择溢出行为,请使用Swift提供的溢出算术运算符,例如溢出加法运算符(&+
)。所有这些溢出运算符都以&符号开头&
。
定义自己的结构,类和枚举时,为这些自定义类型提供自己的标准Swift运算符实现会很有用。Swift可以轻松地为这些运算符提供定制的实现,并确切地确定它们对创建的每种类型的行为。
不仅限于预定义的运算符,Swift为您提供了自定义自定义中缀,前缀,后缀和赋值运算符的自由,具有自定义优先级和关联性值。这些运算符可以像任何预定义的运算符一样在代码中使用和采用,甚至可以扩展现有类型以支持您定义的自定义运算符。
26.1 位运算符
按位运算符使你可以处理数据结构中的各个原始数据位。它们通常用于低级编程,例如图形编程和设备驱动程序创建。当你使用来自外部来源的原始数据时,按位运算符也很有用,例如编码和解码数据以通过自定义协议进行通信。
Swift支持C中的所有按位运算符,如下所述。
26.1.1 位反运算符
该位反运算符(~
)反转数所有位:
按位NOT运算符是一个前缀运算符,它出现在它操作的值之前,没有任何空格:
1. let initialBits: UInt8 = 0b00001111
2. let invertedBits = ~initialBits // equals 11110000
UInt8
整数有八位,可以存储0
和255
之间的任何值。此示例UInt8
使用二进制值初始化一个整数,该二进制值00001111
的前四位设置为0
,其后四位设置为1
。这相当于十进制值15
。
然后使用位反运算符创建一个新的常量invertedBits
,该常量等于initialBits
,但所有位都被反转。零成为1,零成为零。invertedBits
is 的值,11110000
等于无符号十进制值240
。
26.1.2 位与运算符
该位与运算符(&
)结合了两个数字的位数。
在下面的例子中,值
firstSixBits
和lastSixBits
两个具有四个中间位等于1
。按位AND运算符将它们组合起来以产生数字00111100
,该数字等于无符号十进制值60
:
1. let firstSixBits: UInt8 = 0b11111100
2. let lastSixBits: UInt8 = 0b00111111
3. let middleFourBits = firstSixBits & lastSixBits // equals 00111100
26.1.3 位或运算符
../_images/bitwiseOR_2x.png在下面的例子中,的值
someBits
和moreBits
具有不同的位设置为1
。按位OR运算符将它们组合起来以产生数字11111110
,该数字等于无符号小数254
:
1. let someBits: UInt8 = 0b10110010
2. let moreBits: UInt8 = 0b01011110
3. let combinedbits = someBits | moreBits // equals 11111110
26.1.4 异或运算符
“异或运算符”( ^
),比较两个数的位,其返回一个新数字,输入位不同的位置被设置为1
,输入位相同的为设置为0
。
在下面的示例中,值
firstBits
和otherBits
每个值都设置为1
位置,而另一个不是。按位XOR运算符将这两个位设置1
为其输出值。所有其他位firstBits
和otherBits
匹配并0
在输出值中设置为:
1. let firstBits: UInt8 = 0b00010100
2. let otherBits: UInt8 = 0b00000101
3. let outputBits = firstBits ^ otherBits // equals 00010001
26.1.5 位移运算符
按位左右移位具有将整数乘以或除以因子2的效果。将整数位向左移动一个位置会使其值加倍,而将其向右移动一个位置会使其值减半。
26.1.5.1 无符号整数的移位运算
无符号整数的位移行为如下:
1. 现有位按所请求的位数向左或向右移动。
2. 超出整数存储边界的任何位都将被丢弃。
3. 在原始位向左或向右移动之后,将零插入到留下的空间中。
这种方法被称为逻辑转换。
下图显示了(按位置向左移动)和(按位置向右移动)的结果。移动蓝色数字,丢弃灰色数字,并插入橙色零:
无符号位移运算符
以下是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颜色值#CC6699
写0xCC6699
在Swift的十六进制数字表示中。然后通过按位AND运算符()和按位右移运算符()将此颜色分解为其红色(CC
),绿色(66
)和蓝色(99
)分量。&``>>
通过在数字0xCC6699
和之间执行按位AND来获得红色分量0xFF0000
。零0xFF0000
有效地“掩盖”第二个和第三个字节0xCC6699
,导致6699
被忽略并0xCC0000
作为结果离开。
然后将此数字向右移动16位()。十六进制数中的每对字符使用8位,因此向右移动16位将转换为。这与十进制值相同。>> 16``0xCC0000``0x0000CC``0xCC``204
类似地,绿色成分通过执行按位与数字之间获得0xCC6699
和0x00FF00
,其给出的输出值0x006600
。然后将此输出值向右移动8位,给出一个值0x66
,其值为十进制值102
。
最后,将蓝色成分是通过执行按位与数字之间获得0xCC6699
和0x0000FF
,其给出的输出值0x000099
。没有必要将它向右移动,因为0x000099
已经等于0x99
,其十进制值为153
。
26.1.5.2 有符号整数的移位运算
对于有符号整数而言,因为有符号整数的二进制表示方式,导致其移位行为比无符号整数更复杂。(为简单起见,下面的示例基于8位有符号整数,但相同的原则适用于任何大小的有符号整数。)
有符号整数使用它们的第一位(称为符号位)来指示整数是正还是负。符号位0
表示正数,符号1
表示负数。其余位(称为值位)存储实际值。正数以与无符号整数完全相同的方式存储,首位存储0
。以下是使用Int8
类型存储数字4
:
符号位是0
(意思是“正”),七个值位只是4
用二进制表示法写的数字。
但是,负数以不同方式存储。它们的存储方式是将2
的n
次幂减去它们的绝对值,其中n
是值的位数。一个八比特数有七个值的位,所以这意味着2
到的功率7
,或128
。
以下是Int8
查找数字的位数-4
:
这一次,符号位是1
(意思是“负”),七个值位的二进制值为124
(即):128 - 4
负数的这种编码称为二进制补码表示。这似乎是一种代表负数的不寻常方式,但它有几个优点。
首先,您可以加-1
到-4
,简单地通过执行一个标准二进制加法全部八个位(包括符号位),并丢弃任何不适合在八位一旦你完成:
其次,二进制补码表示还可以让你将负数位向左和向右移动,就像正数一样,并且对于你向左移动的每一个移位最终都会将它们加倍,或者对于你向右移动的每个移位将它们减半。当你有符号整数右移,适用相同的规则为无符号整数,但在剩下的填充任何空位:要做到这一点,当签署整数右移使用了额外的规则符号位,而不是零。
有符号数字的位移此操作可确保有符号整数在向右移动后具有相同的符号,并称为算术移位。
由于存储正数和负数的特殊方式,将它们中的任何一个向右移动都会使它们接近零。在此移位期间保持符号位相同意味着负整数保持为负值,因为它们的值接近零。
26.2 溢出运算符
如果您尝试将数字插入到不能保存该值的整数常量或变量中,则默认情况下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 addition operator(&+
)允许无符号整数向正方向溢出时会发生什么的示例:
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
使用溢出加法运算符(&+
)递增。这推动它的二进制表示只是超出了它UInt8
可以容纳的大小,导致它溢出超出其边界,如下图所示。UInt8
溢出添加后保留在范围内的值00000000
为零。
当允许无符号整数向负方向溢出时会发生类似情况。这是使用overflow减法运算符(&-
)的示例:
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
二进制。如果减去1
从00000000
使用溢出减法运算符(&-
),这个数字将溢出并环绕到11111111
,或255
十进制。
对于有符号整数,也会发生溢出。有符号整数的所有加法和减法以按位方式执行,符号位作为加数或减数的一部分包括在内,如按位左右移位运算符中所述。
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
,Int8
即可容纳的最大正值。
对于有符号和无符号整数,正方向上的溢出从最大有效整数值回绕到最小值,而负方向上的溢出从最小值回绕到最大值。
26.3 优先级与结核性
运算符优先级使某些运算符的优先级高于其 首先应用这些运算符。
运算符结合性定义了相同优先级的运算符如何组合在一起 - 从左侧分组,或从右侧分组。可以把它想象成“他们与左边的表达联系起来”或“他们将表达联系到他们的右边”。
在计算复合表达式的计算顺序时,考虑每个运算符的优先级和关联性非常重要。例如,运算符优先级解释了以下表达式等于的原因17
。
1. 2 + 3 % 4 * 5
2. // this equals 17
如果您从左到右严格阅读,您可能希望表达式计算如下:
* 2
再加上3
等号5
* 5
余数4
等于1
* 1
时间5
等于5
但是,实际答案17
不是5
。优先级较高的运算符在优先级较低的运算符之前进行求值。在Swift中,与在C中一样,余数运算符(%
)和乘法运算符(*
)的优先级高于加法运算符(+
)。结果,在考虑添加之前评估它们。
但是,余数和乘法具有相同的优先级。要计算出要使用的确切评估顺序,您还需要考虑它们的相关性。剩余和乘法都与左边的表达式相关联。可以想象这是在表达式的这些部分周围添加隐式括号,从左边开始:
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时,请务必确保操作员交互的行为方式仍然符合您的预期。
26.4 为自定义数据类型添加运算符支持
类和结构可以提供自己的现有运算符实现。这称为重载现有运算符。
下面的示例显示了如何+
为自定义结构实现算术加法运算符()。算术加法运算符是一个二元运算符,因为它在两个目标上运行,并且被称为中缀,因为它出现在这两个目标之间。
该示例定义了Vector2D
二维位置向量的结构,然后定义了将结构实例添加到一起的运算符方法:(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)
-
}
-
}
operator方法被定义为一个类型方法on Vector2D
,其方法名称与要重载的运算符匹配(+
)。因为加法不是向量的基本行为的一部分,所以类型方法是在扩展Vector2D
而不是在主结构声明中定义的Vector2D
。因为算术加法运算符是二元运算符,所以此运算符方法接受两个类型的输入参数,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
or postfix
修饰符来实现前缀或后缀一元运算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)
复合赋值运算符
复合赋值运算符将assignment(=
)与另一个操作相结合。例如,加法赋值运算符(+=
)将加法和赋值组合到单个操作中。将复合赋值运算符的左输入参数类型标记为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)
注意
无法重载默认赋值运算符(=
)。只有复合赋值运算符才能重载。类似地,三元条件运算符()不能重载。a ? b : c
等价运算符
默认情况下,自定义类和结构没有等价运算符的实现,称为等于 operator(==
)且不等于operator(!=
)。您通常实现==
运算符,并使用标准库的!=
运算符的默认实现来否定运算符的结果==
。有两种方法可以实现==
运算符:您可以自己实现它,或者对于许多类型,您可以让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为以下类型的自定义类型提供了综合实现:
- 仅存储符合
Equatable
协议的属性的结构 - 只包含符合
Equatable
协议的关联类型的枚举 - 没有关联类型的枚举
要接收合成的实现==
,请Equatable
在包含原始声明的文件中声明一致性,而不是==
自己实现操作符。
下面的示例定义了Vector3D
三维位置矢量的结构,类似于结构。因为,和性能都是一个的类型,接收合成的等价运营商的实现。(x, y, z)``Vector2D``x``y``z``Equatable``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."
26.4 自定义运算符
除了Swift提供的标准运算符之外,您还可以声明并实现自己的自定义运算符。有关可用于定义自定义运算符的字符列表,请参阅运算符。
新的运营商使用的是在全球范围内声明的operator
关键字,并且都标有prefix
,infix
或postfix
修饰:
- prefix operator +++
上面的示例定义了一个名为的新前缀运算符+++
。此运算符在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标准库提供的运算符的信息,包括运算符优先级组和关联性设置的完整列表,请参阅运算符声明。有关优先级组的更多信息以及查看定义自己的运算符和优先级组的语法,请参阅“ 运算符声明”。
注意
定义前缀或后缀运算符时,不指定优先级。但是,如果将前缀和后缀运算符同时应用于同一操作数,则首先应用后缀运算符。
网友评论