26 高级运算符

高级运算符

除了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 addition (&+)
  • Overflow subtraction (&-)
  • Overflow multiplication (&*)

值溢出

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

下面是使用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协议的属性的结构
  • 仅具有符合Equatable协议的关联类型的枚举
  • 没有关联类型的枚举

要接收==的合成实现,请在包含原始声明的文件中声明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)

<<返回目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容