27让歼、【Swift】高級運算符 - Advanced Operators

  • Swift 運算符
    • 基本運算符
    • 高級運算符(包括 C 或 Objective-C 所有按位和移位運算符是掰。)
  • 與 C 的算術(shù)運算符不同,Swift 算術(shù)運算符默認是不會溢出的耍休。
    • 所有溢出行為都會被捕獲并報告為錯誤刃永。
    • 如果想讓系統(tǒng)允許溢出行為,可用 Swift 支持溢出的運算符
    • 如溢出加法運算符(&+)羊精。所有溢出運算符都以 & 開頭的斯够。
  • 中綴囚玫、前綴、后綴和賦值運算符读规,它們具有自定義的優(yōu)先級與關(guān)聯(lián)值抓督。
  • 可擴展已有的類型以支持自定義運算符。

位運算符

  • 操作數(shù)據(jù)結(jié)構(gòu)中束亏,每個獨立的比特位
  • 使用場景:
    • 底層開發(fā)中铃在,比如圖形編程和創(chuàng)建設(shè)備驅(qū)動
    • 處理外部資源的原始數(shù)據(jù),對自定義通信協(xié)議傳輸?shù)臄?shù)據(jù)進行編碼和解碼
  • Swift 支持 C 語言中的全部位運算符

Bitwise NOT Operator(按位取反運算符)

  • 按位取反運算符(~
    • 對一個數(shù)值的全部比特位進行取反:
image
  • 前綴運算符碍遍,直接放在運算數(shù)之前定铜,并且它們之間不能添加任何空格:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000

Bitwise AND Operator(按位與運算符)

  • 按位與運算符(& 對兩個數(shù)的比特位進行合并
  • 返回一個新的數(shù),只有當(dāng)兩個數(shù)的對應(yīng)位1 的時候怕敬,新數(shù)的對應(yīng)位才為 1
image
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100

Bitwise OR Operator(按位或運算符)

  • 按位或運算符(|可以對兩個數(shù)的比特位進行比較
  • 只要兩個數(shù)的對應(yīng)位中有任意一個1 時揣炕,新數(shù)的對應(yīng)位就為 1
image
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110

Bitwise XOR Operator(按位異或運算符)

  • 按位異或運算符,或稱“排外的或運算符”东跪、“互斥或”(^
  • 當(dāng)兩個數(shù)的對應(yīng)位不相同時畸陡,新數(shù)的對應(yīng)位就為 1,并且對應(yīng)位相同時則為 0
image
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001

Bitwise Left and Right Shift Operators(按位左移虽填、右移運算符)

  • 按位左移運算符(<<按位右移運算符(>>
  • 對一個數(shù)的所有位進行指定位數(shù)的左移和右移
  • 本質(zhì):相當(dāng)于對這個數(shù)進行乘以 2 或除以 2 的運算
    • 左移一位罩锐,等價于將這個數(shù)乘以 2
    • 右移一位,等價于將這個數(shù)除以 2卤唉。

無符號整數(shù)的移位運算

  • 無符號整數(shù)進行移位的規(guī)則:
  1. 已存在的位按指定的位數(shù)進行左移和右移涩惑。
  2. 任何因移動而超出整型存儲范圍的位都會被丟棄。
  3. 0 來填充移位后產(chǎn)生的空白位桑驱。
  • 藍色的數(shù)字是被移位的竭恬,灰色的數(shù)字是被拋棄的,橙色的 0 則是被填充進來的:

[圖片上傳失敗...(image-8779b-1609951379835)]

  • 演示了 Swift 中的移位運算:
let shiftBits: UInt8 = 4 // 即二進制的 00000100
shiftBits << 1           // 00001000
shiftBits << 2           // 00010000
shiftBits << 5           // 10000000
shiftBits << 6           // 00000000
shiftBits >> 2           // 00000001
  • 用移位運算對其他的數(shù)據(jù)類型進行編碼和解碼:
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

有符號整數(shù)的移位運算

  • 以下的示例基于 8 比特的有符號整數(shù)押框,但原理對任何位數(shù)的有符號整數(shù)都是通用的岔绸。

  • 有符號整數(shù)用第 1 個比特位(通常被稱為符號位)表示正負。

    • 符號位為 0 代表正數(shù)橡伞,為 1 代表負數(shù)盒揉。
  • 其余的比特位(通常被稱為數(shù)值位)存儲了實際的值。

  • 有符號正整數(shù)和無符號數(shù)的存儲方式是一樣的兑徘,都是從 0開始算起刚盈。

  • 這是值為 4Int8 型整數(shù)的二進制位表現(xiàn)形式:

    image

  • 值為 -4Int8 型整數(shù)的二進制表現(xiàn)形式:

img
  • 符號位為 1,說明這是一個負數(shù)挂脑,另外 7 個位則代表了數(shù)值 124(即 128 - 4)的二進制表示:
img
  • 負數(shù)的表示通常被稱為二進制補碼
    • 存儲最大值: 2n 次方減去其實際值的絕對值
    • 一個 8 比特位的數(shù)有 7 個比特位是數(shù)值位藕漱,所以是 27 次方欲侮,即 128
  • -1-4 進行加法運算
  • 只需要對這兩個數(shù)的全部 8 個比特位執(zhí)行標準的二進制相加(包括符號位)
  • 將計算結(jié)果中超出 8 位的數(shù)值丟棄:
img
  • 二進制補碼可以使負數(shù)的按位左移和右移運算得到跟正數(shù)同樣的效果
  • 要達到此目的肋联,對有符號整數(shù)的右移有一個額外的規(guī)則:
    • 當(dāng)對有符號整數(shù)進行按位右移運算時威蕉,遵循與無符號整數(shù)相同的規(guī)則
    • 但是對于移位產(chǎn)生的空白位使符號位進行填充,而不是用 0橄仍。**
img
  • 這通常被稱為算術(shù)移位忘伞。
  • 移位的過程中保持符號位不變,意味著負整數(shù)在接近零的過程中會一直保持為負沙兰。

溢出運算符 - Overflow Operators

  • Swift 溢出會直接報錯

  • Int16 有符號整數(shù)范圍是 -32768 到 32767 氓奈,當(dāng)為一個Int16 型變量賦的值超過這個范圍時,系統(tǒng)就會報錯:

var potentialOverflow = Int16.max
// potentialOverflow equals 32767, which is the maximum value an Int16 can hold
potentialOverflow += 1
// this causes an error
  • 故意想要溢出來截斷可用位的數(shù)字時鼎天,也可以選擇這么做而非報錯
  • Swift 提供三個算數(shù)溢出運算符來讓系統(tǒng)支持整數(shù)溢出運算舀奶。這些運算符都是以 & 開頭的:
    • 溢出加法 ( &+ )
    • 溢出減法 ( &- )
    • 溢出乘法 ( &* )

數(shù)值溢出

  • 上溢或者下溢。

  • 對一個無符號整數(shù)使用溢出加法(&+)進行上溢運算時會發(fā)生什么:

var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容納的最大整數(shù) 255
unsignedOverflow = unsignedOverflow &+ 1
// 此時 unsignedOverflow 等于 0
  • 如下圖所示斋射。數(shù)值溢出后育勺,仍然留在 UInt8 邊界內(nèi)的值是 00000000,也就是十進制數(shù)值的 0罗岖。
img
  • 使用溢出減法運算符(&-)的例子:
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容納的最小整數(shù) 0
unsignedOverflow = unsignedOverflow &- 1
// 此時 unsignedOverflow 等于 255
  • 對其進行減 1 運算時涧至,數(shù)值會產(chǎn)生下溢并被截斷為 11111111, 也就是十進制數(shù)值的 255
img
  • 溢出也會發(fā)生在有符號整型上桑包。
    • 針對有符號整型的所有溢出加法或者減法運算都是按位運算的方式執(zhí)行的
    • 符號位也需要參與計算南蓬,正如 按位左移、右移運算符 所描述的哑了。
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容納的最小整數(shù) -128
signedOverflow = signedOverflow &- 1
// 此時 signedOverflow 等于 127

- `Int8` 型整數(shù)能容納的最小值是 `-128`赘方,以二進制表示即 `10000000`。當(dāng)使用溢出減法運算符對其進行減 `1` 運算時弱左,符號位被翻轉(zhuǎn)窄陡,得到二進制數(shù)值 `01111111`,也就是十進制數(shù)值的 `127`拆火,這個值也是 `Int8` 型整所能容納的最大值跳夭。

![](https://docs.swift.org/swift-book/_images/overflowSignedSubtraction_2x.png)

- 對于無符號與有符號整型數(shù)值來說,當(dāng)出現(xiàn)上溢時们镜,它們會從數(shù)值所能容納的最大數(shù)變成最小數(shù)币叹。
- 當(dāng)發(fā)生下溢時,它們會從所能容納的最小數(shù)變成最大數(shù)憎账。

## 優(yōu)先級和結(jié)合性

- 場景:計算順序

- 高優(yōu)先級的運算符會先被計算
- *結(jié)合性*定義了**相同優(yōu)先級的運算符**是如何結(jié)合的套硼,是與左邊結(jié)合為一組卡辰,還是與右邊結(jié)合為一組胞皱。

```swift
2 + 3 % 4 * 5
// 結(jié)果是 17
  • 從左到右進行運算

    • 2 + 3 = 5
    • 5 % 4 = 1
    • 1 * 5 = 5
  • 與 C 語言類似邪意,在 Swift 中,乘法運算符(*)與取余運算符(%)的優(yōu)先級高于加法運算符(+

  • 乘法運算與取余運算的優(yōu)先級相同

  • 為這兩部分表達式都隱式地加上括號:

2 + ((3 % 4) * 5)
  • (3 % 4) 等于 3反砌,所以表達式相當(dāng)于:
2 + (3 * 5)
  • 3 * 5 等于 15雾鬼,所以表達式相當(dāng)于:
2 + 15
  • 因此計算結(jié)果為 17

運算符函數(shù)

  • 運算符重載:類和結(jié)構(gòu)體宴树,為現(xiàn)有運算符提供自定義的實現(xiàn)

  • 讓自定義的結(jié)構(gòu)體支持加法運算符(+

  • 中綴運算符:算術(shù)加法運算符是一個二元運算符策菜,因為它是對兩個值進行運算,出現(xiàn)在兩個值中間酒贬。

  • 定義了一個名為 Vector2D 的結(jié)構(gòu)體用來表示二維坐標向量 (x, y)

  • 定義了一個可以將兩個 Vector2D 結(jié)構(gòu)體實例進行相加的運算符函數(shù)

struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D {
  // 接收兩個類型為 Vector2D 的參數(shù)又憨,同時有一個 Vector2D 類型的返回值
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}
  • 因為加法運算并不是一個向量必需的功能,所以這個類方法被定義在 Vector2D 的一個擴展中
  • 函數(shù)返回一個新的 Vector2D 實例锭吨,這個實例的 xy 分別等于作為參數(shù)的兩個實例的 xy 的值之和
  • 可以在任意兩個 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)
  • 例子實現(xiàn)兩個向量 (3.0,1.0)(2.0零如,4.0) 的相加躏将,并得到新的向量 (5.0,5.0)考蕾。這個過程如下圖示:

前綴和后綴運算符

  • 一元運算符只運算一個值

    • 運算符出現(xiàn)在值之前時祸憋,它就是前綴的(例如 -a
    • 出現(xiàn)在值之后時,它就是后綴的(例如 b!
  • 語法:在聲明運算符函數(shù)的時候在 func 關(guān)鍵字之前指定 prefix 或者 postfix 修飾符:

extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.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 實例

復(fù)合賦值運算符

  • 復(fù)合賦值運算符:賦值運算符(=)與其它運算符進行結(jié)合
    • 如肖卧,將加法與賦值結(jié)合成加法賦值運算符(+=
  • 實現(xiàn)的時候蚯窥,需要把運算符的左參數(shù)設(shè)置成 inout 類型,因為這個參數(shù)的值會在運算符函數(shù)內(nèi)直接被修改
extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}
  • 加法運算在之前已經(jīng)定義過了塞帐,所以在這里無需重新定義
  • 直接利用現(xiàn)有的加法運算符函數(shù)沟沙,用它來對左值和右值進行相加,并再次賦值給左值
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 的值現(xiàn)在為 (4.0, 6.0)

不能對默認的賦值運算符(=)進行重載

只有復(fù)合賦值運算符可以被重載

也無法對三元條件運算符 (a ? b : c) 進行重載

等價運算符

  • 等價運算符通常被稱為相等運算符(==)與不等運算符(!=
  • 場景:自定義的類和結(jié)構(gòu)體對等價運算符進行默認實現(xiàn)
  • 實現(xiàn)的方法與其它中綴運算符一樣, 并且增加對標準庫 Equatable 協(xié)議的遵循:
extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}
  • 標準庫 Equatable 協(xié)議對于“不等”運算符有默認的實現(xiàn)壁榕,它簡單地將“相等”運算符的結(jié)果進行取反后返回

  • 使用

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.”

自定義運算符

  • 場景:除了重載運算符矛紫,還可以聲明和實現(xiàn)自定義運算符
    • 用來自定義運算符的字符列表請參考 運算符
  • 語法:
    • operator 關(guān)鍵字:在全局作用域內(nèi)進行定義
    • 指定 prefixinfix 或者 postfix 修飾符
prefix operator +++
  • +++ 被實現(xiàn)為“前綴雙自增”運算符
    • 使用了前面定義的復(fù)合加法運算符來讓矩陣與自身進行相加
    • 從而讓 Vector2D 實例的 x 屬性和 y 屬性值翻倍
  • 實現(xiàn):
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 現(xiàn)在的值為 (2.0, 8.0)
// afterDoubling 現(xiàn)在的值也為 (2.0, 8.0)

自定義中綴運算符的優(yōu)先級

  • 場景:相對于前綴牌里、后綴這種單目運算符颊咬,中綴為雙目運算符
  • 定義了一個新的自定義中綴運算符 +-,此運算符屬于 AdditionPrecedence (“相加型”)優(yōu)先組:
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
  • 因為它本質(zhì)上是屬于“相加型”運算符态辛,所以將它放置在 +- 等默認中綴“相加型”運算符相同的優(yōu)先級組中麸澜。
  • 關(guān)于 Swift 標準庫提供的運算符,以及完整的運算符優(yōu)先級組和結(jié)合性設(shè)置奏黑,請參考 運算符聲明炊邦。
  • 而更多關(guān)于優(yōu)先級組以及自定義操作符和優(yōu)先級組的語法编矾,請參考 運算符聲明

當(dāng)定義前綴與后綴運算符的時候馁害,我們并沒有指定優(yōu)先級窄俏。

然而,如果對同一個值同時使用前綴與后綴運算符碘菜,則后綴運算符會先參與運算凹蜈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市忍啸,隨后出現(xiàn)的幾起案子仰坦,更是在濱河造成了極大的恐慌,老刑警劉巖计雌,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缎岗,死亡現(xiàn)場離奇詭異,居然都是意外死亡白粉,警方通過查閱死者的電腦和手機传泊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸭巴,“玉大人眷细,你說我怎么就攤上這事【樽妫” “怎么了溪椎?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恬口。 經(jīng)常有香客問我校读,道長,這世上最難降的妖魔是什么祖能? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任歉秫,我火速辦了婚禮,結(jié)果婚禮上养铸,老公的妹妹穿的比我還像新娘雁芙。我一直安慰自己,他們只是感情好钞螟,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布兔甘。 她就那樣靜靜地躺著,像睡著了一般鳞滨。 火紅的嫁衣襯著肌膚如雪洞焙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音澡匪,去河邊找鬼熔任。 笑死,一個胖子當(dāng)著我的面吹牛仙蛉,可吹牛的內(nèi)容都是我干的笋敞。 我是一名探鬼主播碱蒙,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼荠瘪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赛惩?” 一聲冷哼從身側(cè)響起哀墓,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喷兼,沒想到半個月后篮绰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡季惯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年吠各,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勉抓。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡贾漏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出藕筋,到底是詐尸還是另有隱情纵散,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布隐圾,位于F島的核電站伍掀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏暇藏。R本人自食惡果不足惜蜜笤,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盐碱。 院中可真熱鬧瘩例,春花似錦、人聲如沸甸各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趣倾。三九已至聘惦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背善绎。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工黔漂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人禀酱。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓炬守,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容