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閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容