鍍金池/ 教程/ iOS/ 高級(jí)運(yùn)算符
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運(yùn)算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴(kuò)展
泛型參數(shù)(Generic Parameters and Arguments)
訪問控制和 protected
語句(Statements)
模式(Patterns)
WWDC 里面的那個(gè)“大炮打氣球”
關(guān)于語言參考(About the Language Reference)
語法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(A Swift Tour)
泛型
枚舉(Enumerations)
高級(jí)運(yùn)算符
繼承
析構(gòu)過程
關(guān)于 Swift(About Swift)
訪問控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問題
錯(cuò)誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動(dòng)引用計(jì)數(shù)
Swift 里的值類型與引用類型
表達(dá)式(Expressions)
Swift 文檔修訂歷史
造個(gè)類型不是夢(mèng)-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標(biāo)
方法(Methods)
可選鏈?zhǔn)秸{(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

高級(jí)運(yùn)算符


1.0 翻譯:xielingwang 校對(duì):numbbbbb

2.0 翻譯+校對(duì):buginux

2.1 校對(duì):shanks,2015-11-01

2.2 翻譯+校對(duì):SketchK 2016-05-17

3.0 翻譯+校對(duì):mmoaay 2016-09-20

3.0.1 shanks,2016-11-13

4.1 翻譯+校對(duì):mylittleswift

本頁內(nèi)容包括:

除了在之前介紹過的基本運(yùn)算符,Swift 中還有許多可以對(duì)數(shù)值進(jìn)行復(fù)雜運(yùn)算的高級(jí)運(yùn)算符。這些高級(jí)運(yùn)算符包含了在 C 和 Objective-C 中已經(jīng)被大家所熟知的位運(yùn)算符和移位運(yùn)算符。

與 C 語言中的算術(shù)運(yùn)算符不同,Swift 中的算術(shù)運(yùn)算符默認(rèn)是不會(huì)溢出的。所有溢出行為都會(huì)被捕獲并報(bào)告為錯(cuò)誤。如果想讓系統(tǒng)允許溢出行為,可以選擇使用 Swift 中另一套默認(rèn)支持溢出的運(yùn)算符,比如溢出加法運(yùn)算符(&+)。所有的這些溢出運(yùn)算符都是以 & 開頭的。

自定義結(jié)構(gòu)體、類和枚舉時(shí),如果也為它們提供標(biāo)準(zhǔn) Swift 運(yùn)算符的實(shí)現(xiàn),將會(huì)非常有用。在 Swift 中自定義運(yùn)算符非常簡(jiǎn)單,運(yùn)算符也會(huì)針對(duì)不同類型使用對(duì)應(yīng)實(shí)現(xiàn)。

我們不用被預(yù)定義的運(yùn)算符所限制。在 Swift 中可以自由地定義中綴、前綴、后綴和賦值運(yùn)算符,以及相應(yīng)的優(yōu)先級(jí)與結(jié)合性。這些運(yùn)算符在代碼中可以像預(yù)定義的運(yùn)算符一樣使用,我們甚至可以擴(kuò)展已有的類型以支持自定義的運(yùn)算符。

位運(yùn)算符

位運(yùn)算符可以操作數(shù)據(jù)結(jié)構(gòu)中每個(gè)獨(dú)立的比特位。它們通常被用在底層開發(fā)中,比如圖形編程和創(chuàng)建設(shè)備驅(qū)動(dòng)。位運(yùn)算符在處理外部資源的原始數(shù)據(jù)時(shí)也十分有用,比如對(duì)自定義通信協(xié)議傳輸?shù)臄?shù)據(jù)進(jìn)行編碼和解碼。

Swift 支持 C 語言中的全部位運(yùn)算符,接下來會(huì)一一介紹。

按位取反運(yùn)算符

按位取反運(yùn)算符(~可以對(duì)一個(gè)數(shù)值的全部比特位進(jìn)行取反:

Art/bitwiseNOT_2x.png

按位取反運(yùn)算符是一個(gè)前綴運(yùn)算符,需要直接放在運(yùn)算的數(shù)之前,并且它們之間不能添加任何空格:

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000

UInt8 類型的整數(shù)有 8 個(gè)比特位,可以存儲(chǔ) 0 ~ 255 之間的任意整數(shù)。這個(gè)例子初始化了一個(gè) UInt8 類型的整數(shù),并賦值為二進(jìn)制的 00001111,它的前 4 位都為 0,后 4 位都為 1。這個(gè)值等價(jià)于十進(jìn)制的 15

接著使用按位取反運(yùn)算符創(chuàng)建了一個(gè)名為 invertedBits 的常量,這個(gè)常量的值與全部位取反后的 initialBits 相等。即所有的 0 都變成了 1,同時(shí)所有的 1 都變成 0。invertedBits 的二進(jìn)制值為 11110000,等價(jià)于無符號(hào)十進(jìn)制數(shù)的 240。

按位與運(yùn)算符

按位與運(yùn)算符(&可以對(duì)兩個(gè)數(shù)的比特位進(jìn)行合并。它返回一個(gè)新的數(shù),只有當(dāng)兩個(gè)數(shù)的對(duì)應(yīng)位都為 1 的時(shí)候,新數(shù)的對(duì)應(yīng)位才為 1

Art/bitwiseAND_2x.png

在下面的示例當(dāng)中,firstSixBitslastSixBits 中間 4 個(gè)位的值都為 1。按位與運(yùn)算符對(duì)它們進(jìn)行了運(yùn)算,得到二進(jìn)制數(shù)值 00111100,等價(jià)于無符號(hào)十進(jìn)制數(shù)的 60

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

按位或運(yùn)算符

按位或運(yùn)算符(|可以對(duì)兩個(gè)數(shù)的比特位進(jìn)行比較。它返回一個(gè)新的數(shù),只要兩個(gè)數(shù)的對(duì)應(yīng)位中有任意一個(gè)為 1 時(shí),新數(shù)的對(duì)應(yīng)位就為 1

Art/bitwiseOR_2x.png

在下面的示例中,someBitsmoreBits 不同的位會(huì)被設(shè)置為 1。接位或運(yùn)算符對(duì)它們進(jìn)行了運(yùn)算,得到二進(jìn)制數(shù)值 11111110,等價(jià)于無符號(hào)十進(jìn)制數(shù)的 254

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

按位異或運(yùn)算符

按位異或運(yùn)算符(^可以對(duì)兩個(gè)數(shù)的比特位進(jìn)行比較。它返回一個(gè)新的數(shù),當(dāng)兩個(gè)數(shù)的對(duì)應(yīng)位不相同時(shí),新數(shù)的對(duì)應(yīng)位就為 1

Art/bitwiseXOR_2x.png

在下面的示例當(dāng)中,firstBitsotherBits 都有一個(gè)自己的位為 1 而對(duì)方的對(duì)應(yīng)位為 0 的位。 按位異或運(yùn)算符將新數(shù)的這兩個(gè)位都設(shè)置為 1,同時(shí)將其它位都設(shè)置為 0

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001

按位左移、右移運(yùn)算符

按位左移運(yùn)算符(<<按位右移運(yùn)算符(>>可以對(duì)一個(gè)數(shù)的所有位進(jìn)行指定位數(shù)的左移和右移,但是需要遵守下面定義的規(guī)則。

對(duì)一個(gè)數(shù)進(jìn)行按位左移或按位右移,相當(dāng)于對(duì)這個(gè)數(shù)進(jìn)行乘以 2 或除以 2 的運(yùn)算。將一個(gè)整數(shù)左移一位,等價(jià)于將這個(gè)數(shù)乘以 2,同樣地,將一個(gè)整數(shù)右移一位,等價(jià)于將這個(gè)數(shù)除以 2。

無符號(hào)整數(shù)的移位運(yùn)算

對(duì)無符號(hào)整數(shù)進(jìn)行移位的規(guī)則如下:

  1. 已經(jīng)存在的位按指定的位數(shù)進(jìn)行左移和右移。
  2. 任何因移動(dòng)而超出整型存儲(chǔ)范圍的位都會(huì)被丟棄。
  3. 0 來填充移位后產(chǎn)生的空白位。

這種方法稱為邏輯移位。

以下這張圖展示了 11111111 << 1(即把 11111111 向左移動(dòng) 1 位),和 11111111 >> 1(即把 11111111 向右移動(dòng) 1 位)的結(jié)果。藍(lán)色的部分是被移位的,灰色的部分是被拋棄的,橙色的部分則是被填充進(jìn)來的:

Art/bitshiftUnsigned_2x.png

下面的代碼演示了 Swift 中的移位運(yùn)算:

let shiftBits: UInt8 = 4 // 即二進(jìn)制的 00000100
shiftBits << 1           // 00001000
shiftBits << 2           // 00010000
shiftBits << 5           // 10000000
shiftBits << 6           // 00000000
shiftBits >> 2           // 00000001

可以使用移位運(yùn)算對(duì)其他的數(shù)據(jù)類型進(jìn)行編碼和解碼:

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

這個(gè)示例使用了一個(gè)命名為 pinkUInt32 型常量來存儲(chǔ) CSS 中粉色的顏色值。該 CSS 的十六進(jìn)制顏色值 #CC6699,在 Swift 中表示為 0xCC6699。然后利用按位與運(yùn)算符(&)和按位右移運(yùn)算符(>>)從這個(gè)顏色值中分解出紅(CC)、綠(66)以及藍(lán)(99)三個(gè)部分。

紅色部分是通過對(duì) 0xCC66990xFF0000 進(jìn)行按位與運(yùn)算后得到的。0xFF0000 中的 0 部分“掩蓋”了 OxCC6699 中的第二、第三個(gè)字節(jié),使得數(shù)值中的 6699 被忽略,只留下 0xCC0000。

然后,再將這個(gè)數(shù)按向右移動(dòng) 16 位(>> 16)。十六進(jìn)制中每?jī)蓚€(gè)字符表示 8 個(gè)比特位,所以移動(dòng) 16 位后 0xCC0000 就變?yōu)?0x0000CC。這個(gè)數(shù)和 0xCC 是等同的,也就是十進(jìn)制數(shù)值的 204。

同樣的,綠色部分通過對(duì) 0xCC66990x00FF00 進(jìn)行按位與運(yùn)算得到 0x006600。然后將這個(gè)數(shù)向右移動(dòng) 8 位,得到 0x66,也就是十進(jìn)制數(shù)值的 102

最后,藍(lán)色部分通過對(duì) 0xCC66990x0000FF 進(jìn)行按位與運(yùn)算得到 0x000099。這里不需要再向右移位,所以結(jié)果為 0x99 ,也就是十進(jìn)制數(shù)值的 153。

有符號(hào)整數(shù)的移位運(yùn)算

對(duì)比無符號(hào)整數(shù),有符號(hào)整數(shù)的移位運(yùn)算相對(duì)復(fù)雜得多,這種復(fù)雜性源于有符號(hào)整數(shù)的二進(jìn)制表現(xiàn)形式。(為了簡(jiǎn)單起見,以下的示例都是基于 8 比特位的有符號(hào)整數(shù)的,但是其中的原理對(duì)任何位數(shù)的有符號(hào)整數(shù)都是通用的。)

有符號(hào)整數(shù)使用第 1 個(gè)比特位(通常被稱為符號(hào)位)來表示這個(gè)數(shù)的正負(fù)。符號(hào)位為 0 代表正數(shù),為 1 代表負(fù)數(shù)。

其余的比特位(通常被稱為數(shù)值位)存儲(chǔ)了實(shí)際的值。有符號(hào)正整數(shù)和無符號(hào)數(shù)的存儲(chǔ)方式是一樣的,都是從 0 開始算起。這是值為 4Int8 型整數(shù)的二進(jìn)制位表現(xiàn)形式:

Art/bitshiftSignedFour_2x.png

符號(hào)位為 0,說明這是一個(gè)正數(shù),另外 7 位則代表了十進(jìn)制數(shù)值 4 的二進(jìn)制表示。

負(fù)數(shù)的存儲(chǔ)方式略有不同。它存儲(chǔ)的值的絕對(duì)值等于 2n 次方減去它的實(shí)際值(也就是數(shù)值位表示的值),這里的 n 為數(shù)值位的比特位數(shù)。一個(gè) 8 比特位的數(shù)有 7 個(gè)比特位是數(shù)值位,所以是 27 次方,即 128

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

Art/bitshiftSignedMinusFour_2x.png

這次的符號(hào)位為 1,說明這是一個(gè)負(fù)數(shù),另外 7 個(gè)位則代表了數(shù)值 124(即 128 - 4)的二進(jìn)制表示:

Art/bitshiftSignedMinusFourValue_2x.png

負(fù)數(shù)的表示通常被稱為二進(jìn)制補(bǔ)碼表示。用這種方法來表示負(fù)數(shù)乍看起來有點(diǎn)奇怪,但它有幾個(gè)優(yōu)點(diǎn)。

首先,如果想對(duì) -1-4 進(jìn)行加法運(yùn)算,我們只需要將這兩個(gè)數(shù)的全部 8 個(gè)比特位進(jìn)行相加,并且將計(jì)算結(jié)果中超出 8 位的數(shù)值丟棄:

Art/bitshiftSignedAddition_2x.png

其次,使用二進(jìn)制補(bǔ)碼可以使負(fù)數(shù)的按位左移和右移運(yùn)算得到跟正數(shù)同樣的效果,即每向左移一位就將自身的數(shù)值乘以 2,每向右一位就將自身的數(shù)值除以 2。要達(dá)到此目的,對(duì)有符號(hào)整數(shù)的右移有一個(gè)額外的規(guī)則:

  • 當(dāng)對(duì)整數(shù)進(jìn)行按位右移運(yùn)算時(shí),遵循與無符號(hào)整數(shù)相同的規(guī)則,但是對(duì)于移位產(chǎn)生的空白位使用符號(hào)位進(jìn)行填充,而不是用 0。

Art/bitshiftSigned_2x.png

這個(gè)行為可以確保有符號(hào)整數(shù)的符號(hào)位不會(huì)因?yàn)橛乙七\(yùn)算而改變,這通常被稱為算術(shù)移位。

由于正數(shù)和負(fù)數(shù)的特殊存儲(chǔ)方式,在對(duì)它們進(jìn)行右移的時(shí)候,會(huì)使它們?cè)絹碓浇咏?0。在移位的過程中保持符號(hào)位不變,意味著負(fù)整數(shù)在接近 0 的過程中會(huì)一直保持為負(fù)。

溢出運(yùn)算符

在默認(rèn)情況下,當(dāng)向一個(gè)整數(shù)賦予超過它容量的值時(shí),Swift 默認(rèn)會(huì)報(bào)錯(cuò),而不是生成一個(gè)無效的數(shù)。這個(gè)行為為我們?cè)谶\(yùn)算過大或著過小的數(shù)的時(shí)候提供了額外的安全性。

例如,Int16 型整數(shù)能容納的有符號(hào)整數(shù)范圍是 -3276832767,當(dāng)為一個(gè) Int16 型變量賦的值超過這個(gè)范圍時(shí),系統(tǒng)就會(huì)報(bào)錯(cuò):

var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767,這是 Int16 能容納的最大整數(shù)
potentialOverflow += 1
// 這里會(huì)報(bào)錯(cuò)

為過大或者過小的數(shù)值提供錯(cuò)誤處理,能讓我們?cè)谔幚磉吔缰禃r(shí)更加靈活。

然而,也可以選擇讓系統(tǒng)在數(shù)值溢出的時(shí)候采取截?cái)嗵幚?,而非?bào)錯(cuò)??梢允褂?Swift 提供的三個(gè)溢出運(yùn)算符來讓系統(tǒng)支持整數(shù)溢出運(yùn)算。這些運(yùn)算符都是以 & 開頭的:

  • 溢出加法 &+
  • 溢出減法 &-
  • 溢出乘法 &*

數(shù)值溢出

數(shù)值有可能出現(xiàn)上溢或者下溢。

這個(gè)示例演示了當(dāng)我們對(duì)一個(gè)無符號(hào)整數(shù)使用溢出加法(&+)進(jìn)行上溢運(yùn)算時(shí)會(huì)發(fā)生什么:

var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容納的最大整數(shù) 255
unsignedOverflow = unsignedOverflow &+ 1
// 此時(shí) unsignedOverflow 等于 0

unsignedOverflow 被初始化為 UInt8 所能容納的最大整數(shù)(255,以二進(jìn)制表示即 11111111)。然后使用了溢出加法運(yùn)算符(&+)對(duì)其進(jìn)行加 1 運(yùn)算。這使得它的二進(jìn)制表示正好超出 UInt8 所能容納的位數(shù),也就導(dǎo)致了數(shù)值的溢出,如下圖所示。數(shù)值溢出后,留在 UInt8 邊界內(nèi)的值是 00000000,也就是十進(jìn)制數(shù)值的 0

Art/overflowAddition_2x.png

同樣地,當(dāng)我們對(duì)一個(gè)無符號(hào)整數(shù)使用溢出減法(&-)進(jìn)行下溢運(yùn)算時(shí)也會(huì)產(chǎn)生類似的現(xiàn)象:

var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容納的最小整數(shù) 0
unsignedOverflow = unsignedOverflow &- 1
// 此時(shí) unsignedOverflow 等于 255

UInt8 型整數(shù)能容納的最小值是 0,以二進(jìn)制表示即 00000000。當(dāng)使用溢出減法運(yùn)算符對(duì)其進(jìn)行減 1 運(yùn)算時(shí),數(shù)值會(huì)產(chǎn)生下溢并被截?cái)酁?11111111, 也就是十進(jìn)制數(shù)值的 255。

Art/overflowUnsignedSubtraction_2x.png

溢出也會(huì)發(fā)生在有符號(hào)整型數(shù)值上。在對(duì)有符號(hào)整型數(shù)值進(jìn)行溢出加法或溢出減法運(yùn)算時(shí),符號(hào)位也需要參與計(jì)算,正如按位左移、右移運(yùn)算符所描述的。

var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容納的最小整數(shù) -128
signedOverflow = signedOverflow &- 1
// 此時(shí) signedOverflow 等于 127

Int8 型整數(shù)能容納的最小值是 -128,以二進(jìn)制表示即 10000000。當(dāng)使用溢出減法運(yùn)算符對(duì)其進(jìn)行減 1 運(yùn)算時(shí),符號(hào)位被翻轉(zhuǎn),得到二進(jìn)制數(shù)值 01111111,也就是十進(jìn)制數(shù)值的 127,這個(gè)值也是 Int8 型整數(shù)所能容納的最大值。

Art/overflowSignedSubtraction_2x.png

對(duì)于無符號(hào)與有符號(hào)整型數(shù)值來說,當(dāng)出現(xiàn)上溢時(shí),它們會(huì)從數(shù)值所能容納的最大數(shù)變成最小的數(shù)。同樣地,當(dāng)發(fā)生下溢時(shí),它們會(huì)從所能容納的最小數(shù)變成最大的數(shù)。

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

運(yùn)算符的優(yōu)先級(jí)使得一些運(yùn)算符優(yōu)先于其他運(yùn)算符,高優(yōu)先級(jí)的運(yùn)算符會(huì)先被計(jì)算。

結(jié)合性定義了相同優(yōu)先級(jí)的運(yùn)算符是如何結(jié)合的,也就是說,是與左邊結(jié)合為一組,還是與右邊結(jié)合為一組??梢詫⑦@意思理解為“它們是與左邊的表達(dá)式結(jié)合的”或者“它們是與右邊的表達(dá)式結(jié)合的”。

在復(fù)合表達(dá)式的運(yùn)算順序中,運(yùn)算符的優(yōu)先級(jí)和結(jié)合性是非常重要的。舉例來說,運(yùn)算符優(yōu)先級(jí)解釋了為什么下面這個(gè)表達(dá)式的運(yùn)算結(jié)果會(huì)是 17。

2 + 3 % 4 * 5
// 結(jié)果是 17

如果完全從左到右進(jìn)行運(yùn)算,則運(yùn)算的過程是這樣的:

  • 2 + 3 = 5
  • 5 % 4 = 1
  • 1 * 5 = 5

但是正確答案是 17 而不是 5。優(yōu)先級(jí)高的運(yùn)算符要先于優(yōu)先級(jí)低的運(yùn)算符進(jìn)行計(jì)算。與 C 語言類似,在 Swift 中,乘法運(yùn)算符(*)與取余運(yùn)算符(%)的優(yōu)先級(jí)高于加法運(yùn)算符(+)。因此,它們的計(jì)算順序要先于加法運(yùn)算。

而乘法與取余的優(yōu)先級(jí)相同。這時(shí)為了得到正確的運(yùn)算順序,還需要考慮結(jié)合性。乘法與取余運(yùn)算都是左結(jié)合的??梢詫⑦@考慮成為這兩部分表達(dá)式都隱式地加上了括號(hào):

2 + ((3 % 4) * 5)

(3 % 4) 等于 3,所以表達(dá)式相當(dāng)于:

2 + (3 * 5)

3 * 5 等于 15,所以表達(dá)式相當(dāng)于:

2 + 15

因此計(jì)算結(jié)果為 17

如果想查看完整的 Swift 運(yùn)算符優(yōu)先級(jí)和結(jié)合性規(guī)則,請(qǐng)參考表達(dá)式。如果想查看 Swift 標(biāo)準(zhǔn)庫提供所有的運(yùn)算符,請(qǐng)查看 Swift Standard Library Operators Reference。

注意

相對(duì) C 語言和 Objective-C 來說,Swift 的運(yùn)算符優(yōu)先級(jí)和結(jié)合性規(guī)則更加簡(jiǎn)潔和可預(yù)測(cè)。但是,這也意味著它們相較于 C 語言及其衍生語言并不是完全一致的。在對(duì)現(xiàn)有的代碼進(jìn)行移植的時(shí)候,要注意確保運(yùn)算符的行為仍然符合你的預(yù)期。

運(yùn)算符函數(shù)

類和結(jié)構(gòu)體可以為現(xiàn)有的運(yùn)算符提供自定義的實(shí)現(xiàn),這通常被稱為運(yùn)算符重載。

下面的例子展示了如何為自定義的結(jié)構(gòu)體實(shí)現(xiàn)加法運(yùn)算符(+)。算術(shù)加法運(yùn)算符是一個(gè)雙目運(yùn)算符,因?yàn)樗梢詫?duì)兩個(gè)值進(jìn)行運(yùn)算,同時(shí)它還是中綴運(yùn)算符,因?yàn)樗霈F(xiàn)在兩個(gè)值中間。

例子中定義了一個(gè)名為 Vector2D 的結(jié)構(gòu)體用來表示二維坐標(biāo)向量 (x, y),緊接著定義了一個(gè)可以對(duì)兩個(gè) Vector2D 結(jié)構(gòu)體進(jìn)行相加的運(yùn)算符函數(shù):

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)
    }
}

該運(yùn)算符函數(shù)被定義為 Vector2D 上的一個(gè)類方法,并且函數(shù)的名字與它要進(jìn)行重載的 + 名字一致。因?yàn)榧臃ㄟ\(yùn)算并不是一個(gè)向量必需的功能,所以這個(gè)類方法被定義在 Vector2D 的一個(gè)擴(kuò)展中,而不是 Vector2D 結(jié)構(gòu)體聲明內(nèi)。而算術(shù)加法運(yùn)算符是雙目運(yùn)算符,所以這個(gè)運(yùn)算符函數(shù)接收兩個(gè)類型為 Vector2D 的參數(shù),同時(shí)有一個(gè) Vector2D 類型的返回值。

在這個(gè)實(shí)現(xiàn)中,輸入?yún)?shù)分別被命名為 leftright,代表在 + 運(yùn)算符左邊和右邊的兩個(gè) Vector2D 實(shí)例。函數(shù)返回了一個(gè)新的 Vector2D 實(shí)例,這個(gè)實(shí)例的 xy 分別等于作為參數(shù)的兩個(gè)實(shí)例的 xy 的值之和。

這個(gè)類方法可以在任意兩個(gè) Vector2D 實(shí)例中間作為中綴運(yùn)算符來使用:

let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一個(gè)新的 Vector2D 實(shí)例,值為 (5.0, 5.0)

這個(gè)例子實(shí)現(xiàn)兩個(gè)向量 (3.0,1.0)(2.0,4.0) 的相加,并得到新的向量 (5.0,5.0)。這個(gè)過程如下圖示:

Art/vectorAddition_2x.png

前綴和后綴運(yùn)算符

上個(gè)例子演示了一個(gè)雙目中綴運(yùn)算符的自定義實(shí)現(xiàn)。類與結(jié)構(gòu)體也能提供標(biāo)準(zhǔn)單目運(yùn)算符的實(shí)現(xiàn)。單目運(yùn)算符只運(yùn)算一個(gè)值。當(dāng)運(yùn)算符出現(xiàn)在值之前時(shí),它就是前綴的(例如 -a),而當(dāng)它出現(xiàn)在值之后時(shí),它就是后綴的(例如 b!)。

要實(shí)現(xiàn)前綴或者后綴運(yùn)算符,需要在聲明運(yùn)算符函數(shù)的時(shí)候在 func 關(guān)鍵字之前指定 prefix 或者 postfix 修飾符:

extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}

這段代碼為 Vector2D 類型實(shí)現(xiàn)了單目負(fù)號(hào)運(yùn)算符。由于該運(yùn)算符是前綴運(yùn)算符,所以這個(gè)函數(shù)需要加上 prefix 修飾符。

對(duì)于簡(jiǎn)單數(shù)值,單目負(fù)號(hào)運(yùn)算符可以對(duì)它們的正負(fù)性進(jìn)行改變。對(duì)于 Vector2D 來說,該運(yùn)算將其 xy 屬性的正負(fù)性都進(jìn)行了改變:

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一個(gè)值為 (-3.0, -4.0) 的 Vector2D 實(shí)例
let alsoPositive = -negative
// alsoPositive 是一個(gè)值為 (3.0, 4.0) 的 Vector2D 實(shí)例

復(fù)合賦值運(yùn)算符

復(fù)合賦值運(yùn)算符將賦值運(yùn)算符(=)與其它運(yùn)算符進(jìn)行結(jié)合。例如,將加法與賦值結(jié)合成加法賦值運(yùn)算符(+=)。在實(shí)現(xiàn)的時(shí)候,需要把運(yùn)算符的左參數(shù)設(shè)置成 inout 類型,因?yàn)檫@個(gè)參數(shù)的值會(huì)在運(yùn)算符函數(shù)內(nèi)直接被修改。

extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}

因?yàn)榧臃ㄟ\(yùn)算在之前已經(jīng)定義過了,所以在這里無需重新定義。在這里可以直接利用現(xiàn)有的加法運(yùn)算符函數(shù),用它來對(duì)左值和右值進(jìn)行相加,并再次賦值給左值:

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)

注意

不能對(duì)默認(rèn)的賦值運(yùn)算符(=)進(jìn)行重載。只有組合賦值運(yùn)算符可以被重載。同樣地,也無法對(duì)三目條件運(yùn)算符 (a ? b : c) 進(jìn)行重載。

等價(jià)運(yùn)算符

自定義的類和結(jié)構(gòu)體沒有對(duì)等價(jià)運(yùn)算符進(jìn)行默認(rèn)實(shí)現(xiàn),等價(jià)運(yùn)算符通常被稱為“相等”運(yùn)算符(==)與“不等”運(yùn)算符(!=)。對(duì)于自定義類型,Swift 無法判斷其是否“相等”,因?yàn)椤跋嗟取钡暮x取決于這些自定義類型在你的代碼中所扮演的角色。

為了使用等價(jià)運(yùn)算符能對(duì)自定義的類型進(jìn)行判等運(yùn)算,需要為其提供自定義實(shí)現(xiàn),實(shí)現(xiàn)的方法與其它中綴運(yùn)算符一樣, 并且增加對(duì)標(biāo)準(zhǔn)庫 Equatable 協(xié)議的遵循:

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

上述代碼實(shí)現(xiàn)了“相等”運(yùn)算符(==)來判斷兩個(gè) Vector2D 實(shí)例是否相等。對(duì)于 Vector2D 類型來說,“相等”意味著“兩個(gè)實(shí)例的 x 屬性和 y 屬性都相等”,這也是代碼中用來進(jìn)行判等的邏輯。示例里同時(shí)也實(shí)現(xiàn)了“不等”運(yùn)算符(!=),它簡(jiǎn)單地將“相等”運(yùn)算符的結(jié)果進(jìn)行取反后返回。

現(xiàn)在我們可以使用這兩個(gè)運(yùn)算符來判斷兩個(gè) Vector2D 實(shí)例是否相等:

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 為以下自定義類型提等價(jià)運(yùn)算符供合成實(shí)現(xiàn):

  • 只擁有遵循 Equatable 協(xié)議存儲(chǔ)屬性的結(jié)構(gòu)體;
  • 只擁有遵循 Equatable 協(xié)議關(guān)聯(lián)類型的枚舉;
  • 沒有關(guān)聯(lián)類型的枚舉。

在類型原本的聲明中聲明遵循 Equatable 來接收這些默認(rèn)實(shí)現(xiàn)。

下面為三維位置向量 (x, y, z) 定義的 Vector3D 結(jié)構(gòu)體,與 Vector2D 類似,由于 x,yz 屬性都是 Equatable 類型,Vector3D 就收到默認(rèn)的等價(jià)運(yùn)算符實(shí)現(xiàn)了。

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

自定義運(yùn)算符

除了實(shí)現(xiàn)標(biāo)準(zhǔn)運(yùn)算符,在 Swift 中還可以聲明和實(shí)現(xiàn)自定義運(yùn)算符??梢杂脕碜远x運(yùn)算符的字符列表請(qǐng)參考運(yùn)算符。

新的運(yùn)算符要使用 operator 關(guān)鍵字在全局作用域內(nèi)進(jìn)行定義,同時(shí)還要指定 prefix、infix 或者 postfix 修飾符:

prefix operator +++

上面的代碼定義了一個(gè)新的名為 +++ 的前綴運(yùn)算符。對(duì)于這個(gè)運(yùn)算符,在 Swift 中并沒有意義,因此我們針對(duì) Vector2D 的實(shí)例來定義它的意義。對(duì)這個(gè)示例來講,+++ 被實(shí)現(xiàn)為“前綴雙自增”運(yùn)算符。它使用了前面定義的復(fù)合加法運(yùn)算符來讓矩陣對(duì)自身進(jìn)行相加,從而讓 Vector2D 實(shí)例的 x 屬性和 y 屬性的值翻倍。實(shí)現(xiàn) +++ 運(yù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ùn)算符的優(yōu)先級(jí)

每個(gè)自定義中綴運(yùn)算符都屬于某個(gè)優(yōu)先級(jí)組。這個(gè)優(yōu)先級(jí)組指定了這個(gè)運(yùn)算符和其他中綴運(yùn)算符的優(yōu)先級(jí)和結(jié)合性。優(yōu)先級(jí)和結(jié)合性中詳細(xì)闡述了這兩個(gè)特性是如何對(duì)中綴運(yùn)算符的運(yùn)算產(chǎn)生影響的。

而沒有明確放入優(yōu)先級(jí)組的自定義中綴運(yùn)算符會(huì)放到一個(gè)默認(rèn)的優(yōu)先級(jí)組內(nèi),其優(yōu)先級(jí)高于三元運(yùn)算符。

以下例子定義了一個(gè)新的自定義中綴運(yùn)算符 +-,此運(yùn)算符屬于 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 是一個(gè) Vector2D 實(shí)例,并且它的值為 (4.0, -2.0)

這個(gè)運(yùn)算符把兩個(gè)向量的 x 值相加,同時(shí)用第一個(gè)向量的 y 值減去第二個(gè)向量的 y 值。因?yàn)樗举|(zhì)上是屬于“相加型”運(yùn)算符,所以將它放置 +- 等默認(rèn)的中綴“相加型”運(yùn)算符相同的優(yōu)先級(jí)組中。關(guān)于 Swift 標(biāo)準(zhǔn)庫提供的運(yùn)算符,以及完整的運(yùn)算符優(yōu)先級(jí)組和結(jié)合性設(shè)置,請(qǐng)參考 Swift Standard Library Operators Reference。而更多關(guān)于優(yōu)先級(jí)組以及自定義操作符和優(yōu)先級(jí)組的語法,請(qǐng)參考運(yùn)算符聲明

注意

當(dāng)定義前綴與后綴運(yùn)算符的時(shí)候,我們并沒有指定優(yōu)先級(jí)。然而,如果對(duì)同一個(gè)值同時(shí)使用前綴與后綴運(yùn)算符,則后綴運(yùn)算符會(huì)先參與運(yùn)算。