Swift 各個擊破——運算符(Operators)

點贊評論,感受有用的朋友能夠關注筆者公衆號 iOS 成長指北,持續更新ios

歡迎與筆者討論,不管是知識點敘述有誤仍是筆者論述問題git

在本文中,你將學習有關 Swift 編程語言中不一樣類型運算符的全部知識、它們的語法以及如何使用它們。github

撰寫本文的想法來自於一個 ~= 引起的認知盲點。objective-c

let n = 30
if 10...100 ~= n {
  print("inside!") //inside!
}
複製代碼

大多數編程語言中不存在 ~= 這樣的數學運算符,固然更不可能在把它當作判斷值是否符合某區間來使用。這就讓咱們有興趣去探究一下 Swift 中的運算符。編程

本文從前人的肩膀和總結中進行梳理,致力於讓你一文讀懂哪些 Swift 中的運算符swift

運算符類型

Swift 閉包詳解 中咱們說明了,在 Swift 中運算符是頂級函數。咱們在 運算符和條件語句 中介紹了常規的運算符的使用。能夠查看 Define Swift 瞭解早期 Swift中運算符的僞代碼。api

通常來講,咱們從兩個方面來分類運算符,一個是語句中相關項的數量,另外一個是運算符的功能。數組

數量

根據語句中相關項數量咱們將運算符分紅安全

  • 一元運算符:該運算符對單個操做數進行操做。markdown

    例如:

    let a = !true
    let num = -5
    複製代碼
  • 二元運算符:該運算符操做兩個操做數。

    例如咱們常見的數學運算符,如加減乘除之類的

    let a = 3 + 4
    let b = 5 / 2
    複製代碼
  • 三元運算符

    該運算符對三個操做數進行運算。三元運算符是一個比較重要的點

    let result = (5 > 10) ? "Value larger" : "Value Smaller"
    複製代碼

    將三元運算符當作單一條件運算來使用,而不是將其做爲嵌套條件運算來使用。

功能

根據運算的功能,咱們將運算符大體分紅

賦值運算符

在 Swift 中賦值運算符用於爲屬性(變量/常量)賦值。

運算符 描述 舉例
= 簡單賦值運算符,將值從右操做數賦給左操做數 C = A + B 就是將 A + B 賦值給 C
+= 加法和賦值運算符,它將右操做數添加到左操做數,並將結果分配給左操做數 C += A 等同於 C = C + A
-= 減法和賦值運算符,它從左操做數中減去右操做數,並將結果分配給左操做數 C -= A 等同於 C = C - A
*= 乘法和賦值運算符,它將右操做數與左操做數相乘,並將結果賦值給左操做數 C *= A 等同於 C = C * A
/= 除法和賦值運算符,它將左操做數與右操做數除並將結果賦值給左操做數 C /= A 等同於 C = C / A
%= 趨於和賦值運算符,它使用兩個操做數取餘並將結果賦值給左操做數 C %= A 等同於 C = C % A
<<= 左移位和賦值運算符 C <<= 2 等同於 C = C << 2
>>= 右移位和賦值運算符 C >>= 2 等同於 C = C >> 2
&= 按位與和賦值運算符 C &= 2 等同於 C = C & 2
^= 按位異或賦值運算符 C ^= 2 等同於 C = C ^ 2
|= 按位或運算符和賦值運算符 C |= 2 等同於 C = C | 2
var aOperator = 20 //20

aOperator += 20 // 40

aOperator -= 20 // 20

aOperator *= 2 // 40

aOperator /= 2 // 20

aOperator %= 2 // 2

aOperator <<= 2 // 8

aOperator >>= 2 // 2

aOperator &= 1 // 0

aOperator ^= -1 // -1

aOperator |= 1 // -1
複製代碼

在賦值運算符中,咱們須要注意的是 Swift 中沒有 ++-- 這兩個自增自減的運算符。

算術運算符

這些運算符用於執行數學運算,包括乘、除、加、減等。這種運算符屬於二元運算符的範疇,它有左右兩個操做數。

操做符 描述 例子(A = 10, B = 20)
+ 兩數相加 A + B = 30
兩數相減 A − B = -10
* 兩數相乘 A * B = 200
/ 兩數相除 B / A = 2
% 取模運算符,整數或浮點相除後所得的餘數 B % A = 0

在這裏咱們以運算符 + 爲例,如下面的代碼爲例,說明 Swift 中二元運算符的函數實現方式

//字符串
func +<T : Strideable>(lhs: T, rhs: T.Stride) -> T
// 集合
func +<C : ExtensibleCollectionType, S : SequenceType where S.Generator.Element == C.Generator.Element>(lhs: C, rhs: S) -> C

複製代碼

咱們能夠理解二元運算符分紅 左算子右算子,因此二元運算符的函數實現兩步操做,合併 左算子右算子 而後返回一個新值。

這主要是爲了後面咱們講解如何在 Swift 中自定義運算符作準備。

比較運算符

Swift 閉包詳解 中咱們說明舉例關於 < 符號的實現,就是一種比較運算符。

操做符 描述 例子(A = 10, B = 20)
== 檢查兩個操做數的值是否相等;若是相等,則條件變爲 true。 (A == B) 值爲 false
!= 檢查兩個操做數的值是否不相等;若是相等,則條件變爲 true。 (A != B) 值爲 true
> 檢查左算子是否大於右算子;若是大於,則條件變爲 true。 (A > B) 值爲 false
< 檢查左算子是否小於右算子;若是小於,則條件變爲 true。 (A < B) 值爲 true
>= 檢查左算子是否大於等於右算子;若是大於等於,則條件變爲 true。 (A >= B) 值爲 false
<= 檢查左算子是否小於等於右算子;若是小於等於,則條件變爲 true。 (A <= B) 值爲 true

比較運算符通常用於條件語句中——條件語句處理的是 Bool 值。

=====

Swift 給了咱們兩個等式運算符,=====,它們作的事情略有不一樣。

== 是用來判斷做用兩個操做數是否相等,不管他們使用什麼 相等 的基準——這爲咱們自定義結構體的相等提供了理論基礎。

=== 是一個 identity operator,它是用來判斷一個類的兩個實例是否指向相同的內存。只用引用類型的實例才須要進行這個判斷。

一個簡單的例子,以下所示

class Person: Equatable {
    var name: String = "iOS成長指北"
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.name == rhs.name
    }
}

let person1 = Person()
let person2 = Person()
let person3 = person1

if person1 == person2 {
    print("person1 == person2") // person1 == person2
}
if person1 === person2 {
    print("person1 === person2")
}
if person1 === person3 {
    print("person1","===","person3") // person1 === person3
}
複製代碼

對於基於同一個引用類型對象 Person 生成的兩個實例,person1person2,咱們定義了一個相等 基準——名字相等就認爲這兩個對象是相等的。因此person1person2 雖然值相等可是指向兩塊不一樣的內存,並不 ===

從某種概念上,將 === 引伸意義爲恆等,有必定的道理。

===== 在代碼實現上,實際上是基於兩種不一樣的泛型

func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool

func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool
複製代碼

須要注意的是, === 只能比較 AnyObject 類型。固然,你確定知道這是由於什麼。

邏輯運算符

咱們常常要處理一些包含多個條件的條件語句,只有知足這些條件的時候,才進行某些操做。

邏輯運算符與布爾(邏輯)值一塊兒使用,並返回布爾值。

操做符 描述 例子(A 爲true, B 爲 false)
&& 邏輯且,若是兩個操做數都不爲 false,則條件爲 true (A && B) 值爲 false
|| 邏輯或,若是兩個條件只要有一個不爲 false,則條件爲 true (A || B) 值爲 true
! 邏輯非,用於反轉其操做數的邏輯狀態,若是值爲 true 則反轉爲 false !(A && B) 值爲 true

位運算符

位運算符用於操做數據結構中每一個獨立的比特位。這是一種偏底層的使用,從語法上來講有些抽象。

它們一般被用在底層開發中,好比圖形編程和建立設備驅動。位運算符在處理外部資源的原始數據時也十分有用,好比對自定義通訊協議傳輸的數據進行編碼和解碼。

p q p&q p|q p^q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1
操做符 描述 例子(A 爲0011 1100, B 爲 0000 1101)
& Bitwise AND Operator(按位與運算符) (A & B) 結果爲 0000 1100
| Bitwise OR Operator(按位或運算符) (A | B) 結果爲 0011 1101
^ Bitwise XOR Operator(按位異或運算符) (A ^ B) 結果爲 0011 0001
~ Bitwise NOT Operator(按位取反運算符)是一元的,具備 翻轉 位的效果。 (~A ) 結果爲 1100 0011
<< Bitwise Left Shift Operators(按位左移運算符) A << 2 結果爲 1111 0000
>> Bitwise RightShift Operators(按位右移運算符) A >> 2 結果爲 0000 1111

具體代碼以下:打印的結果值爲 Int,這裏我用二進制表示結果

let A = 0b00111100
let B = 0b00001100

var C = A & B // 0000 1100

C = A | B // 0011 1101

C = A ^ B //0011 0001

C = ~A // 1100 0011 

C = A << 2 //1111 0000

C = A >> 2 //0000 1111
複製代碼
枚舉和位移操做

關於二級制的問題,咱們能夠用一個常見的毒藥和老鼠的問題來講明咱們怎麼使用二進制來涵蓋絕大多數的場景。

先簡述一下這個問題:

有100瓶子的水,只有一瓶是毒藥,沒法直觀上進行辨別,小白鼠服用後當即死亡,問最少須要多少隻小白鼠才能夠找到毒藥?

答案是 7 只小老鼠,由於27 大於 100。

感興趣的小夥伴們能夠在網上查找這道二進制毒藥的問題詳細解答。這裏咱們不在贅述。

在 Objective-C 中,大量定義了這種枚舉,例如咱們常見的 UIView 動畫的中的 options 選項。

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(7.0));

typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
    UIViewAnimationOptionLayoutSubviews            = 1 <<  0,
    UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, // turn on user interaction while animating
    UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, // start all views from current value, not initial value
    
    ...

    UIViewAnimationOptionPreferredFramesPerSecondDefault     = 0 << 24,
    UIViewAnimationOptionPreferredFramesPerSecond60          = 3 << 24,
    UIViewAnimationOptionPreferredFramesPerSecond30          = 7 << 24,
    
} API_AVAILABLE(ios(4.0));
複製代碼

可是在 Swift 中咱們沒法定義這樣的枚舉,這是由於

fatal error: Raw value for enum case must be a literal 複製代碼

Swift 中定義枚舉值的RawValue 須要一個肯定的值,而不是通過計算出來的。咱們可使用下述代碼實現一個二進制的枚舉值

enum Bitwise: UInt8 {
    case one = 0b00000001
    case two = 0b00000010
    case three = 0b00000100
    case four = 0b00001000
    case five = 0b00010000
}
複製代碼

這樣子定義雖然可使用到二進制用來組合條件的優點,可是定義的時候過於麻煩。

open class func animate(withDuration duration: TimeInterval, delay: TimeInterval, options: UIView.AnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) 複製代碼

爲了支持組件條件,Swift 中支持組合條件的實現方式就是實現一個數組。

區間運算符

Swift 提供了兩種生成範圍的方法:..<... 運算符。半開範圍運算符 ..< 建立的範圍爲包含第一個值而不包括最後的值,而閉合範圍運算符 ... 建立的範圍包括第一個值和最後一個值。

閉合範圍(lowerBound...upperBound)
for value in 1...3 {
	print(value) // 1,2,3
}
複製代碼
半開範圍(lowerBound..<upperBound)
for value in 1..<3 {
	print(value) // 1,2
}
複製代碼
單邊範圍(lowerBound... 或者 ...upperBound 或 ..<upperBound)

單邊範圍是指在一個方向上儘量連續的範圍。可使用半開範圍算子和閉合範圍算子來建立它,可是該運算符只能在一側具備一個值。

單邊範圍沒法使用 for 循環進行處理,咱們應該用此來判斷是否包含

let range = ..<2

if range.contains(-1) {
    print("iOS 成長指北") //iOS 成長指北
}
複製代碼

nil 合併運算符(Nil-coalescing)

可選 章節中,咱們介紹瞭如何使用 nil 合併運算符 來對可選項進行解包。

在 Objective-C 中,咱們常用三元運算符來進行值得 nil 處理,若是前值不爲nil,返回前值

NSString *result = _result ?:@"哨值"
複製代碼

可是在Swift 中並不能這樣子操做,若是使用三元運算符來實現的話,咱們須要實現以下

var option: String? = nil
let reslut = option != nil ? option : ""
複製代碼

可是使用nil 合併運算符,能夠簡化這步操做

let reslut = option ?? "iOS成長指北"
複製代碼

運算符優先級

運算符優先級是用於計算給定數學表達式的規則集合。當在一個表達式中使用多個運算符時,每一個部分都將按稱爲運算符優先級的特定順序求值。某些運算符的優先級高於其餘運算符,這會影響表達式的計算方式。例如,乘法運算符的優先級高於加法運算符。

下面操做符優先級從高到低,只列舉部分操做符

運算符 Examples 結合性
位移操做符優先級 >> &<< &>> >>
乘法優先級 &* % & * / left
加減優先級 ` &+ &- + - ^`
區間運算符 ..< ...
nil 合併運算符 ?? right
比較運算符優先級 !== > < >= <= === ==
邏輯且 && left
邏輯或 ` `
默認優先級 ~>
三元運算符優先級 ?: right
賦值運算符優先級 ` = %= /= &<<= &>>= &= *= >>= <<= ^=`

在這些裏面,比較引人注目的可能就是 &<<&>>&*&+&- 這些,這些被稱爲溢出運算符

溢出運算符

當向一個整數類型的常量或者變量賦予超過它容量的值時,Swift 默認會報錯,而不是容許生成一個無效的數。這個行爲爲咱們在運算過大或者太小的數時提供了額外的安全性。

然而,當你但願的時候也能夠選擇讓系統在數值溢出的時候採起截斷處理,而非報錯。Swift 提供了 溢出運算符來讓系統支持整數溢出運算。這些運算符都是以 & 開頭的。

//UInt8.max 0b11111111
var unsignedOverflow = UInt8.max &+ 1 // 0b00000000
var unsignedOverflow1 = UInt8.max &>> 8 //0b11111111
var unsignedOverflow2 = UInt8.max &<< 8 //0b11111111
var unsignedOverflow3 = UInt8.max >> 8 //0b00000000
var unsignedOverflow4 = UInt8.max << 8 //0b00000000

//UInt8.min 0b00000000
unsignedOverflow = UInt8.min &- 1 //0b11111111
unsignedOverflow1 = 0b00000001 &>> 8 //0b00000001
unsignedOverflow2 = 0b00000001 &<< 8 //0b00000001
unsignedOverflow3 = 0b00000001 >> 8 //0b00000000
unsignedOverflow4 = 0b00000001 << 8 //0b00000000
複製代碼

當超過位數是,&<<&>><<>>都可以保護溢出,可是其便宜大於等於最大位數時,其表現結果是不同的。

溢出也會發生在有符號整型上。針對有符號整型的全部溢出加法或者減法運算都是按位運算的方式執行的,符號位也須要參與計算

var signedOverflow = Int8.min
signedOverflow = signedOverflow &- 1 // 127
複製代碼

自定義運算符

使運算符如此強大的緣由在於,它們能夠自動捕獲它們兩側的上下文。

除了實現標準運算符,在 Swift 中還能夠聲明和實現 自定義運算符

咱們這裏將自定義運算符分紅兩類,一類是運算符重載,另外一類是自定義系統沒有的運算符。

運算符重載

繼續以咱們的購物車爲例

struct Customer {
    var shoppingList : [Product] = []
  // 購買
    mutating func buy(_ product: Product) {
        self.shoppingList.append(product)
    }
}

struct Product {

}
複製代碼

咱們能夠將添加的產品代碼重載一個運算符,該運算符容許咱們經過使用 += 運算符直接向購物車裏添加產品。

extension Customer {
    static func += (lhs: inout Customer, rhs: Product) {
        lhs.add(rhs)
    }
}
customer += product
print(customer.shoppingList)
customer += product1
print(customer.shoppingList)
複製代碼

重載自定義運算符必定注意命名空間的問題,不要全局重載 Swift 已有的操做符。儘可能不要影響

不能對默認的賦值運算符 = 進行重載。只有複合賦值運算符能夠被重載。一樣地,也沒法對三元條件運算符 (a ? b : c) 進行重載。

運算符重載的另外一個定義就是前面咱們說的遵照 Equatable 協議的 == 運算符。

自定義運算符

除了重載一些系統有的運算符之外,咱們還能夠自定義運算符。

根據運算符出現的位置,咱們將運算符分紅前綴,中綴和後綴三種運算符。一元運算符存在前綴和後綴之分,對兩元運算符來講,你只能定義中綴運算符

自定義前綴/後綴運算符

繼續以咱們商場的例子來講,咱們須要給咱們外露一個商品標價,咱們能夠定義一個運算符來實現這個功能

prefix operator ~>

prefix func ~>(value: NSNumber) -> String {
    let currencyFormatter = NumberFormatter()
    currencyFormatter.numberStyle = .currency
    currencyFormatter.locale = Locale.current

    return currencyFormatter.string(from: value)!
}

let decimalInputPrice: String = ~>843.32 // $843.32

let intInputPrice: String = ~>300 // $300.00
複製代碼

同理,咱們定義一個後綴運算符來實現一樣的功能

postfix operator ~<

postfix func ~<(value: NSNumber) -> String {
    let currencyFormatter = NumberFormatter()
    currencyFormatter.numberStyle = .currency
    currencyFormatter.locale = Locale.current

    return currencyFormatter.string(from: value)!
}

let decimalInputPrice: String =  843.32~< // $843.32

let intInputPrice: String = 300~< // $300.00
複製代碼
自定義中綴運算符

下面咱們用自定義中綴運算符實現一個並集的功能

let firstNumbers:  Set<Int> = [1, 4, 5]
let secondNumbers: Set<Int> = [1, 4, 6]

infix operator +-: AdditionPrecedence
extension Set {
    static func +- (lhs: Set, rhs: Set) -> Set {
        return lhs.union(rhs)
    }
}

let uniqueNumbers = firstNumbers +- secondNumbers
print(uniqueNumbers) // Prints: [1, 4, 5, 6]
複製代碼

你注意到咱們定義運算符時加的: AdditionPrecedence 嗎?下面咱們好好說說這個

運算符優先級

每一個自定義運算符都屬於某個優先級組。優先級組指定了這個運算符相對於其餘中綴運算符的優先級和結合性。

在早期的的 Swift 語言中,咱們經過

infix operator =~ { associativity left precedence 130 }
複製代碼

可是在新的 Swift 中,這部分已經廢棄了。下面列幾個官網的代碼

infix operator == : Equal
precedencegroup Equal {
  associativity: left
  higherThan: FatArrow
}

infix operator & : BitAnd
precedencegroup BitAnd {
  associativity: left
  higherThan: Equal
}

infix operator => : FatArrow
precedencegroup FatArrow {
  associativity: right
  higherThan: AssignmentPrecedence
}
複製代碼

咱們能夠定義咱們自定義運算符的優先級以及其左關聯和有關聯

並非全部的符號都可以自定義字符串的,你能夠經過 運算符 來了解到底哪些能夠

彩蛋

在 github 找到了一個有趣的自定義 Emoj 的 + 號運算符的例子

print("🛹" + "❄️") //🏂
print("😬" + "❄️") //🥶
print("😢" + "🔥") //🥵
print("🥕" + "🥬" + "🥒" + "🍅") //🥗
print("🧔" + "💈") //👶🏻
print("🦏" + "🌈") //🦄
print("🔨" + "🔧") //🛠
複製代碼

能夠嘗試一下這個例子,實現 + 號運算符的重載

感謝你閱讀本文! 🚀

相關文章
相關標籤/搜索