Swift:高级运算符

中文文档

一、位运算符

位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,使用它可以单独操作数据结构中原始数据的比特位。在使用一个自定义的协议进行通信的时候,运用位运算符来对原始数据进行编码和解码也是非常有效的。

1、按位取反运算符
  • ~: 按位取反运算符, 对一个操作数的每一位都取反
按位取反运算符
  • 这个运算符是前置的,所以请不加任何空格地写着操作数之前。
let initialBits: UInt8 = 0b00001111 
let invertedBits = ~initialBits  // 等于 0b11110000 
2、按位与运算符
  • &: 按位与运算符对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1
按位与运算符
  • 以下代码,firstSixBits和lastSixBits中间4个位都为1。对它俩进行按位与运算后,就得到了00111100,即十进制的60。
let firstSixBits: UInt8 = 0b11111100 
let lastSixBits: UInt8  = 0b00111111 
let middleFourBits = firstSixBits & lastSixBits  // 等于 00111100 
3、按位或运算
  • |: 按位或运算符, 比较两个数,然后返回一个新的数,这个数的每一位设置1的条件是两个输入数的同一位都不为0(即任意一个为1,或都为1)。
按位或运算
  • 如下代码,someBits和moreBits在不同位上有1。按位或运行的结果是11111110,即十进制的254。
let someBits: UInt8 = 0b10110010 
let moreBits: UInt8 = 0b01011110 
let combinedbits = someBits | moreBits  // 等于 11111110 
4、按位异或运算符
  • ^: 按位异或运算符, 比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0。
按位异或运算符
  • 以下代码,firstBits和otherBits都有一个1跟另一个数不同的。所以按位异或的结果是把它这些位置为1,其他都置为0。
let firstBits: UInt8 = 0b00010100 
let otherBits: UInt8 = 0b00000101 
let outputBits = firstBits ^ otherBits  // 等于 00010001 
5、按位左移/右移运算符
  • 左移运算符<<和右移运算符>>会把一个数的所有比特位按以下定义的规则向左或向右移动指定位数。

  • 按位左移和按位右移的效果相当把一个整数乘于或除于一个因子为2的整数。向左移动一个整型的比特位相当于把这个数乘于2,向右移一位就是除于2

6、无符整型的移位操作
  • 对无符整型的移位的效果如下:
已经存在的比特位向左或向右移动指定的位数。
被移出整型存储边界的的位数直接抛弃,移动留下的空白位用零0来填充。这种方法称为逻辑移位。
  • 以下这张把展示了 11111111 << 1(11111111向左移1位),和 11111111 >> 1(11111111向右移1位)。蓝色的是被移位的,灰色是被抛弃的,橙色的0是被填充进来的
无符整型的移位操作
let shiftBits: UInt8 = 4   // 即二进制的00000100 
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 是 0xCC, 即 204 
let greenComponent = (pink & 0x00FF00) >> 8   // greenComponent 是 0x66, 即 102 
let blueComponent = pink & 0x0000FF           // blueComponent 是 0x99, 即 153 
  • 这个例子使用了一个UInt32的命名为pink的常量来存储层叠样式表CSS中粉色的颜色值,CSS颜色#CC6699在Swift用十六进制0xCC6699来表示。然后使用按位与(&)和按位右移就可以从这个颜色值中解析出红(CC),绿(66),蓝(99)三个部分。

  • 对0xCC6699和0xFF0000进行按位与&操作就可以得到红色部分。0xFF0000中的0了遮盖了OxCC6699的第二和第三个字节,这样6699被忽略了,只留下0xCC0000。

  • 然后,按向右移动16位,即 >> 16。十六进制中每两个字符是8比特位,所以移动16位的结果是把0xCC0000变成0x0000CC。这和0xCC是相等的,都是十进制的204。

  • 同样的,绿色部分来自于0xCC6699和0x00FF00的按位操作得到0x006600。然后向右移动8們,得到0x66,即十进制的102。

  • 最后,蓝色部分对0xCC6699和0x0000FF进行按位与运算,得到0x000099,无需向右移位了,所以结果就是0x99,即十进制的153。

7、有符整型的移位操作
  • 有符整型的移位操作相对复杂得多,因为正负号也是用二进制位表示的
  • 有符整型通过第1个比特位(称为符号位)来表达这个整数是正数还是负数。0代表正数,1代表负数。
  • 其余的比特位(称为数值位)存储其实值。有符正整数和无符正整数在计算机里的存储结果是一样的,下来我们来看+4内部的二进制结构。
  • 符号位为0,代表正数,另外7比特位二进制表示的实际值就刚好是4。
  • 负数呢,跟正数不同。负数存储的是2n次方减去它的绝对值,n为数值位的位数。一个8比特的数有7个数值位,所以是27次方,即128
  • 我们来看-4存储的二进制结构。
  • 现在符号位为1,代表负数,7个数值位要表达的二进制值是124,即128 - 4
  • 负数的编码方式称为二进制补码表示。这种表示方式看起来很奇怪,但它有几个优点。

  • 首先,只需要对全部8个比特位(包括符号)做标准的二进制加法就可以完成 -1 + -4 的操作,忽略加法过程产生的超过8个比特位表达的任何信息。

  • 第二,由于使用二进制补码表示,我们可以和正数一样对负数进行按位左移右移的,同样也是左移1位时乘于2,右移1位时除于2。要达到此目的,对有符整型的右移有一个特别的要求:
对有符整型按位右移时,使用符号位(正数为0,负数为1)填充空白位。
  • 这就确保了在右移的过程中,有符整型的符号不会发生变化。这称为算术移位。

  • 正因为正数和负数特殊的存储方式,向右移位使它接近于0。移位过程中保持符号会不变,负数在接近0的过程中一直是负数。

二、溢出运算符

  • 默认情况下,当你往一个整型常量或变量赋于一个它不能承载的大数时,Swift不会让你这么干的,它会报错。这样,在操作过大或过小的数的时候就很安全了。
var potentialOverflow = Int16.max 
// potentialOverflow 等于 32767, 这是 Int16 能承载的最大整数 
potentialOverflow += 1 
// 噢, 出错了 
  • 当然,你有意在溢出时对有效位进行截断,你可采用溢出运算,而非错误处理。Swfit为整型计算提供了5个&符号开头的溢出运算符。
溢出加法 &+
溢出减法 &-
溢出乘法 &*
溢出除法 &/
溢出求余 &%
1、值的上溢出
  • 下面例子使用了溢出加法&+来解剖的无符整数的上溢出
var willOverflow = UInt8.max 
// willOverflow 等于UInt8的最大整数 255 
willOverflow = willOverflow &+ 1 
// 这时候 willOverflow 等于 0 
  • willOverflow用Int8所能承载的最大值255(二进制11111111),然后用&+加1。然后UInt8就无法表达这个新值的二进制了,也就导致了这个新值上溢出了,大家可以看下图。溢出后,新值在UInt8的承载范围内的那部分是00000000,也就是0。
2、值的下溢出
  • 数值也有可能因为太小而越界。举个例子:
  • UInt8的最小值是0(二进制为00000000)。使用&-进行溢出减1,就会得到二进制的11111111即十进制的255。
  • Swift代码是这样的:
var willUnderflow = UInt8.min 
// willUnderflow 等于UInt8的最小值0 
willUnderflow = willUnderflow &- 1 
// 此时 willUnderflow 等于 255 
  • 有符整型也有类似的下溢出,有符整型所有的减法也都是对包括在符号位在内的二进制数进行二进制减法的,这在 "按位左移/右移运算符" 一节提到过。最小的有符整数是-128,即二进制的10000000。用溢出减法减去去1后,变成了01111111,即UInt8所能承载的最大整数127。
  • 对应Swift代码
var signedUnderflow = Int8.min 
// signedUnderflow 等于最小的有符整数 -128 
signedUnderflow = signedUnderflow &- 1 
// 如今 signedUnderflow 等于 127 
3、除零溢出
  • 一个数除于0 i / 0,或者对0求余数 i % 0,就会产生一个错误。
let x = 1 
let y = x / 0 
  • 使用它们对应的可溢出的版本的运算符&/&%进行除0操作时就会得到0值。
let x = 1 
let y = x &/ 0 
// y 等于 0 

三、优先级和结合性

  • 与数学中的优先级和结合性相同

四、运算符函数

  • 让已有的运算符也可以对自定义的类和结构进行运算,这称为运算符重载。

  • 例子中定义了一个名为Vector2D的二维坐标向量 (x,y) 的结构,然后定义了让两个Vector2D的对象相加的运算符函数。

struct Vector2D { 
    var x = 0.0, y = 0.0 
} 
func + (left: Vector2D, right: Vector2D) -> Vector2D { 
    return Vector2D(x: left.x + right.x, y: left.y + right.y) 
} 
  • 在这个代码实现中,参数被命名为了left和right,代表+左边和右边的两个Vector2D对象。函数返回了一个新的Vector2D的对象,这个对象的x和y分别等于两个参数对象的x和y的和。

  • 这个函数是全局的,而不是Vector2D结构的成员方法,所以任意两个Vector2D对象都可以使用这个中置运算符。

let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
print(v1 + v2)        // 打印: Vector2D(x: 4.0, y: 6.0)
2、前置和后置运算符
  • 上个例子演示了一个双目中置运算符的自定义实现,同样我们也可以玩标准单目运算符的实现。单目运算符只有一个操作数,在操作数之前就是前置的,如-a; 在操作数之后就是后置的,如i++。

  • 实现一个前置或后置运算符时,在定义该运算符的时候于关键字func之前标注 prefixpostfix 属性。

  • 下面实现-取反运算符

prefix func - (vector: Vector2D) -> Vector2D { 
    return Vector2D(x: -vector.x, y: -vector.y) 
} 
  • 调用
print(-v1)        // 打印: Vector2D(x: -1.0, y: -2.0)
3、组合赋值运算符
  • 组合赋值是其他运算符和赋值运算符一起执行的运算。如+=把加运算和赋值运算组合成一个操作
func += (inout left: Vector2D, right: Vector2D) { 
    left = left + right 
} 
  • 因为加法运算在之前定义过了,这里无需重新定义。所以,加赋运算符函数使用已经存在的高级加法运算符函数来执行左值加右值的运算。
var a = Vector2D(x: 1.0, y: 2.0) 
let b = Vector2D(x: 3.0, y: 4.0) 
a += b 
// a 现在为 (4.0, 6.0) 

注意:默认的赋值符(=)是不可重载的。只有组合赋值符可以重载。三目条件运算符 (a ? b : c) 也是不可重载。

4、等价运算符
  • 定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为“相等”运算符(==)与“不等”运算符(!=)。对于自定义类型,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.")
}
// 打印 “These two vectors are equivalent.”
  • Swift 为以下自定义类型提等价运算符供合成实现:
只拥有遵循 Equatable 协议存储属性的结构体;
只拥有遵循 Equatable 协议关联类型的枚举;
没有关联类型的枚举。
  • 在类型原本的声明中声明遵循 Equatable 来接收这些默认实现。

  • 下面为三维位置向量 (x, y, z) 定义的 Vector3D 结构体,与 Vector2D 类似,由于 xyz 属性都是 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."

五、自定义运算符

  • Swift 中还可以声明和实现自定义运算符。

注意
以下这些标记 =->///**/.<(前缀运算符)、&??(中缀运算符)、>(后缀运算符)、!? 是被系统保留的。这些符号不能被重载,也不能用于自定义运算符。

  • 新的运算符要使用 operator 关键字在全局作用域内进行定义,同时还要指定 prefixinfix 或者 postfix 修饰符:
prefix:    前置运算符
infix:     中置运算符
postfix:   后置运算符
prefix operator +++
  • 上面的代码定义了一个新的名为 +++ 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因此我们针对 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)
1、自定义中缀运算符的优先级
  • 每个自定义中缀运算符都属于某个优先级组。这个优先级组指定了这个运算符和其他中缀运算符的优先级和结合性。

  • 而没有明确放入优先级组的自定义中缀运算符会放到一个默认的优先级组内,其优先级高于三元运算符。

  • 以下例子定义了一个新的自定义中缀运算符 +-,此运算符属于 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)
2、定义优先级别名
  • 可以使用下面的方式, 自定义操作符的优先级, 这依赖于已有优先级, 比如下面的AdditionPrecedenceMultiplicationPrecedence
// >>>操作符, 优先级别名
infix operator >>> : ATPrecedence
precedencegroup ATPrecedence { //定义运算符优先级ATPrecedence
    associativity: left 
    higherThan: AdditionPrecedence 
    lowerThan: MultiplicationPrecedence
}
  • 直接指定操作符的类型,对这个类型进行定义
associativity: left 表示左结合
higherThan 优先级高于 AdditionPrecedence 这个是加法的类型
lowerThan 优先级低于 MultiplicationPrecedence 乘除
  • 这里给出常用类型对应的group
infix operator || : LogicalDisjunctionPrecedence
infix operator && : LogicalConjunctionPrecedence
infix operator < : ComparisonPrecedence
infix operator <= : ComparisonPrecedence
infix operator > : ComparisonPrecedence
infix operator >= : ComparisonPrecedence
infix operator == : ComparisonPrecedence
infix operator != : ComparisonPrecedence
infix operator === : ComparisonPrecedence
infix operator !== : ComparisonPrecedence
infix operator ~= : ComparisonPrecedence
infix operator ?? : NilCoalescingPrecedence
infix operator + : AdditionPrecedence
infix operator - : AdditionPrecedence
infix operator &+ : AdditionPrecedence
infix operator &- : AdditionPrecedence
infix operator | : AdditionPrecedence
infix operator ^ : AdditionPrecedence
infix operator * : MultiplicationPrecedence
infix operator / : MultiplicationPrecedence
infix operator % : MultiplicationPrecedence
infix operator &* : MultiplicationPrecedence
infix operator & : MultiplicationPrecedence
infix operator << : BitwiseShiftPrecedence
infix operator >> : BitwiseShiftPrecedence
infix operator ..< : RangeFormationPrecedence
infix operator ... : RangeFormationPrecedence
infix operator *= : AssignmentPrecedence
infix operator /= : AssignmentPrecedence
infix operator %= : AssignmentPrecedence
infix operator += : AssignmentPrecedence
infix operator -= : AssignmentPrecedence
infix operator <<= : AssignmentPrecedence
infix operator >>= : AssignmentPrecedence
infix operator &= : AssignmentPrecedence
infix operator ^= : AssignmentPrecedence
infix operator |= : AssignmentPrecedence
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容