本文介绍: 在我之前的博客Swift之深入解析如何自定义操作符介绍了“基本运算符”,Swift 还提供了数种可以数值进行复杂运算的高级运算符,它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。与 C 语言中的算术运算符不同,Swift 中的算术运算默认是不会溢出的,所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(&+),所有的这些溢出运算符都是以 & 开头的。

一、前言

二、位运算符​

① 按位取反运算符(Bitwise NOT Operator)​

在这里插入图片描述

  • 按位取反运算符是一个前缀运算符,直接放在运算数之前,并且它们之间不能添加任何空格
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000

② 按位与运算符(Bitwise AND Operator)​

在这里插入图片描述

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

③ 按位或运算符(Bitwise OR Operator)​

  • 按位或运算符(|)可以对两个数的比特位进行比较,它返回一个新的数,只要两个数的对应位中有任意一个为 1 时,新数的对应位就为 1:

在这里插入图片描述

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

④ 按位异或运算符(Bitwise XOR Operator)​

  • 按位异或运算符,或称“排外的或运算符”(^),可以对两个数的比特位进行比较,它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 1,并且对应位相同时则为 0:

在这里插入图片描述

  • 如下所示firstBits 和 otherBits 都有一个自己为 1,而对方为 0 的位,按位异或运算符将新数的这两个位都设置为 1,在其余的位上 firstBits 和 otherBits 是相同的,所以设置为 0:
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001

⑤ 按位左移、右移运算符(Bitwise Left and Right Shift Operators)​

  • 按位左移运算符(<<)和按位右移运算符(&gt;&gt;)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守定义规则。对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算,将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。
  • 无符号整数的移位运算​:
    • 对无符号整数进行移位的规则如下:
      • 已存在的位按指定的位数进行左移和右移;
      • 用 0 来填充移位后产生的空白位。
    • 无符号整数的移位运算也称为逻辑移位,如下展示了 11111111 << 1(即把 11111111 向左移动 1 位),和 11111111 &gt;&gt; 1(即把 11111111 向右移动 1 位)的结果,蓝色的数字是被移位的,灰色数字是被抛弃的,橙色的 0 则是被填充进来的:

在这里插入图片描述

    • 如下所示,演示了 Swift 中的移位运算:
let shiftBits: UInt8 = 4 // 即二进制的 00000100
shiftBits << 1           // 00001000
shiftBits << 2           // 00010000
shiftBits << 5           // 10000000
shiftBits << 6           // 00000000
shiftBits &gt;&gt; 2           // 00000001
let pink: UInt32 = 0xCC6699
let redComponent = (pink &amp; 0xFF0000) &gt;&gt; 16  // redComponent 是 0xCC,即 204
let greenComponent = (pink &amp; 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102
let blueComponent = pink &amp; 0x0000FF         // blueComponent 是 0x99,即 153
    • 示例使用了一个命名为 pink 的 UInt32 型常量存储 Cascading Style Sheets(CSS)中粉色的颜色值,该 CSS 的颜色值 #CC6699,在 Swift 中表示为十六进制的 0xCC6699,然后利用按位与运算符(&amp;)和按位右移运算符(>>)从这个颜色值中分解出红(CC)、绿(66)以及蓝(99)三个部分。
    • 红色部分是通过对 0xCC6699 和 0xFF0000 进行按位与运算后得到的,0xFF0000 中的 0 部分“掩盖”了 OxCC6699 中的第二、第三个字节,使得数值中的 6699 被忽略,只留下 0xCC0000。
    • 同样的,绿色部分通过对 0xCC6699 和 0x00FF00 进行按位与运算得到 0x006600,然后将这个数向右移动 8 位,得到 0x66,也就是十进制数值的 102。
    • 最后,蓝色部分通过对 0xCC6699 和 0x0000FF 进行按位与运算得到 0x000099,因为 0x000099 已经等于 0x99 ,也就是十进制数值的 153,所以这个值就不需要再向右移位。
  • 有符号整数的移位运算​
    • 对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式(为了简单起见,以下的示例都是基于 8 比特的有符号整数,但是其中的原理对任何位数的有符号整数都是通用的)。
    • 有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负,符号位为 0 代表正数,为 1 代表负数,其余的比特位(通常被称为数值位)存储了实际的值,有符号正整数和无符号数的存储方式是一样的,都是从 0 开始算起,这是值为 4 的 Int8 型整数的二进制位表现形式:

在这里插入图片描述

    • 符号位为 0(代表这是一个“正数”),另外 7 位则代表了十进制数值 4 的二进制表示。负数的存储方式略有不同,它存储 2 的 n 次方减去其实际值的绝对值,这里的 n 是数值位的位数,一个 8 比特位的数有 7 个比特位是数值位,所以是 2 的 7 次方,即 128。这是值为 -4 的 Int8 型整数的二进制表现形式:

在这里插入图片描述

    • 这次的符号位为 1,说明这是一个负数,另外 7 个位则代表了数值 124(即 128 – 4)的二进制表示:

在这里插入图片描述

    • 负数的表示通常被称为二进制补码,用这种方法来表示负数乍看起来有点奇怪,但它有几个优点:
      • 首先,如果想对 -1 和 -4 进行加法运算,只需要对这两个数的全部 8 个比特位执行标准的二进制相加(包括符号位),并且将计算结果超出 8 位的数值丢弃:

在这里插入图片描述

      • 其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右移一位就将自身的数值除以 2,要达到此目的,对有符号整数的右移有一个额外的规则:当对有符号整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是用 0。

在这里插入图片描述

    • 这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为算术移位。由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 0。在移位的过程中保持符号位不变,意味着负整数在接近 0 的过程中会一直保持为负。

二、溢出运算符​

var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767,这是 Int16 能容纳的最大整数
potentialOverflow += 1
// 这里报错
  • 赋值时为过大或者过小的情况提供错误处理,能在处理边界值时更加灵活。然而,当你希望的时候也可以选择系统在数值溢出的时候采取截断处理,而非报错。Swift 提供的三个溢出运算符来让系统支持整数溢出运算,这些运算符都是以 &amp; 开头的:
    • 溢出加法 &+;
    • 溢出减法 &-;
    • 溢出乘法 &*。
  • 数值有可能出现上溢或者下溢,如下所示演示了当对一个无符号整数使用溢出加法(&+)进行上溢运算时会发生什么:
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
unsignedOverflow = unsignedOverflow &+ 1
// 此时 unsignedOverflow 等于 0
  • unsignedOverflow初始化为 UInt8 所能容纳的最大整数(255,以二进制表示即 11111111),然后使用溢出加法运算符(&+)对其进行加 1 运算,这使得它的二进制表示正好超出 UInt8 所能容纳的位数,也就导致了数值的溢出。
  • 如下图所示,数值溢出后,仍然留在 UInt8 边界内的值是 00000000,也就是十进制数值的 0:

在这里插入图片描述

  • 当允许对一个无符号整数进行下溢运算时也会产生类似的情况,如下有一个使用溢出减法运算符(&-)的例子
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
unsignedOverflow = unsignedOverflow &- 1
// 此时 unsignedOverflow 等于 255
  • UInt8 型整数能容纳的最小值是 0,以二进制表示即 00000000,当使用溢出减法运算符对其进行减 1 运算时,数值会产生下溢并被截断为 11111111, 也就是十进制数值的 255:

在这里插入图片描述

  • 溢出也会发生在有符号整型上,针对有符号整型的所有溢出加法或者减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如上文中的“按位左移、右移运算符”一样的:
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容纳的最小整数 -128
signedOverflow = signedOverflow &- 1
// 此时 signedOverflow 等于 127
  • Int8 型整数能容纳的最小值是 -128,以二进制表示即 10000000。当使用溢出减法运算符对其进行减 1 运算时,符号位被翻转,得到二进制数值 01111111,也就是十进制数值的 127,这个值也是 Int8 型整数所能容纳的最大值

在这里插入图片描述

  • 对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大数。

三、优先级结合性​

  • 运算符的优先级使得一些运算符优先于其他运算符;它们会先被执行。结合性定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组,可以将其理解为“它们是与左边的表达式结合的”,或者“它们是与右边的表达式结合的”。
  • 当考虑一个复合表达式计算顺序时,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 17:
2 + 3 % 4 * 5
// 结果是 17
  • 如果直接从左到右进行运算,可能运算的过程是这样的:
2 + 3 = 5
5 % 4 = 1
1 * 5 = 5
  • 但是正确答案是 17 而不是 5,优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(*)与取余运算符(%)的优先级高于加法运算符(+),因此它们的计算顺序要先于加法运算。
  • 而乘法运算与取余运算的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性,乘法运算与取余运算都是左结合的,可以将这考虑成,从它们的左边开始为这两部表达式都隐式地加上括号
 2 + ((3 % 4) * 5)
  • (3 % 4) 等于 3,所以表达式相当于:
2 + (3 * 5)
  • 3 * 5 等于 15,所以表达式相当于:
2 + 15
  • 因此计算结果为 17。
  • 有关 Swift 标准库提供的操作信息,包括操作符优先级组和结合性设置的完整列表,可以参见 操作符声明
  • 相对 C 语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合预期。

四、运算符函数

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 类型返回值。
  • 在这个实现中,输入参数分别被命名leftright,代表在 + 运算符左边和右边的两个 Vector2D 实例,函数返回了一个新的 Vector2D 实例,x 和 y 分别等于作为参数的两个实例的 x 和 y 的值之和。这个类方法可以在任意两个 Vector2D 实例中间作为中缀运算符来使用:
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
  • 如下所示,实现两个向量 (3.0,1.0) 和 (2.0,4.0) 的相加,并得到新的向量 (5.0,5.0):

在这里插入图片描述

前缀和后缀运算符​

  • 如上演示了一个二元中缀运算符的自定义实现,类与结构体也能提供标准一元运算符的实现,一元运算符只运算一个值,当运算符出现在值之前时,它就是前缀的(例如 -a),而当它出现在值之后时,它就是后缀的(例如 b!)。
  • 要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 修饰符
extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}
  • 这段代码为 Vector2D 类型实现了一元运算符(-a),由于该运算符是前缀运算符,因此这个函数需要加上 prefix 修饰符
  • 对于简单数值,一元负号运算符可以对它们的正负性进行改变。对于 Vector2D 来说,该运算将其 x 和 y 属性的正负性都进行了改变:
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
let alsoPositive = -negative
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例

② 复合赋值运算符​

  • 复合赋值运算符将赋值运算符(=)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(+=),在实现的时候,需要把运算符的左参数设置成 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 的值现在为 (4.0, 6.0)
  • 注意:不能对默认的赋值运算符(=)进行重载,只有复合赋值运算符可以被重载。同样地,也无法对三元条件运算符 (a ? b : c) 进行重载。

③ 等价运算符​

  • 通常情况下,自定义的类和结构没有对等价运算符进行默认实现,等价运算符通常被称为相等运算符(==)与不等运算符(!=)。
  • 为了使用等价运算符对自定义的类型进行判等运算,需要为“相等”运算符提供自定义实现,实现的方法与其它中缀运算符一样,并且增加对标准库 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.")
}
// 打印“These two vectors are equivalent.”

五、自定义运算符​

prefix operator +++
  • 如上的代码定义了一个新的名为 +++ 的前缀运算符,对于这个运算符,在 Swift 中并没有已知的意义,因此在针对 Vector2D 实例的特定上下文中,给予了它自定义的意义。对这个示例来讲,+++ 被实现为“前缀自增”运算符,它使用了前面定义的复合加法运算符来让矩阵与自身进行相加,从而让 Vector2D 实例的 x 属性和 y 属性值翻倍。
  • 可以通过对 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 现在的值为 (2.0, 8.0)
// afterDoubling 现在的值也为 (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 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
  • 这个运算符把两个向量的 x 值相加,同时从第一个向量的 y 中减去第二个向量的 y,因为它本质上是属于“相加型”运算符,所以将它放置在 + 和 – 等默认中缀“相加型”运算符相同的优先级组中。
  • 注意:当定义前缀与后缀运算符的时候,并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。

六、结果构造器​

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() }
}
let name: String? = "Ravi Patel"
let manualDrawing = Line(elements: [
    Stars(length: 3),
    Text("Hello"),
    Space(),
    AllCaps(content: Text((name ?? "World") + "!")),
    Stars(length: 2),
    ])
print(manualDrawing.draw())
// 打印 "***Hello RAVI PATEL!**"
  • 代码没问题,但是不够优雅,AllCaps 后面的括号嵌套太深,可读性不佳。namenil 时使用 “World” 的兜底逻辑必须要依赖 ?? 操作符,这在逻辑复杂的时候会更难以阅读。如果还需要 switch 或者 for 循环构建绘制的一部分,就更难以编写了,使用结果构造器可以将这样的代码重构得更像普通的 Swift 代码。
  • 在类型的定义上加上 @resultBuilder 特性来定义一个结果构造器,如下定义了允许使用声明式语法描述绘制的结果构造器 DrawingBuilder:
@resultBuilder
struct DrawingBuilder {
    static func buildBlock(_ components: Drawable...) -> Drawable {
        return Line(elements: components)
    }
    static func buildEither(first: Drawable) -> Drawable {
        return first
    }
    static func buildEither(second: Drawable) -> Drawable {
        return second
    }
}
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return content()
}
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return AllCaps(content: content())
}

func makeGreeting(for name: String? = nil) -> Drawable {
    let greeting = draw {
        Stars(length: 3)
        Text("Hello")
        Space()
        caps {
            if let name = name {
                Text(name + "!")
            } else {
                Text("World!")
            }
        }
        Stars(length: 2)
    }
    return greeting
}
let genericGreeting = makeGreeting()
print(genericGreeting.draw())
// 打印 "***Hello WORLD!**"

let personalGreeting = makeGreeting(for: "Ravi Patel")
print(personalGreeting.draw())
// 打印 "***Hello RAVI PATEL!**"
  • makeGreeting(for:) 函数将传入的 name 形参用于绘制个性化问候,draw(_:) 和 caps(_:) 函数都传入应用 @DrawingBuilder 特性的单一闭包实参。当调用这些函数时,要使用 DrawingBuilder 定义的特殊语法,Swift 将绘制的声明式描述转换为一系列 DrawingBuilder 的方法调用,构造成最终传递进函数的实参值。例如,Swift 将例子中的 caps(_:) 的调用转换为下面的代码:
let capsDrawing = caps {
    let partialDrawing: Drawable
    if let name = name {
        let text = Text(name + "!")
        partialDrawing = DrawingBuilder.buildEither(first: text)
    } else {
        let text = Text("World!")
        partialDrawing = DrawingBuilder.buildEither(second: text)
    }
    return partialDrawing
}
extension DrawingBuilder {
    static func buildArray(_ components: [Drawable]) -> Drawable {
        return Line(elements: components)
    }
}
let manyStars = draw {
    Text("Stars:")
    for length in 1...3 {
        Space()
        Stars(length: length)
    }
}

原文地址:https://blog.csdn.net/Forever_wj/article/details/130270753

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_10187.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注