Swift復習(二)基本語法和特性(二)

十五、賦值和算術(shù)運算符

運算符基本概念

  • 一元運算符對一個目標進行操作。一元前綴運算符(如!b),一元后綴運算符(b!)
  • 二元運算符對兩個目標進行操作(比如a+b)同時因為它們出現(xiàn)在兩個目標之間,所以是中綴
  • 三元運算符操作三個目標。Swift語言也僅有一個三元運算符,三元條件運算符( a ? b : c)

Swift運算符的改進

  • Swift在支持C中的大多數(shù)標準運算符的同時也增加了一些排除常見代碼錯誤的能力
    • 賦值符號(=)不會返回值,以防它被誤用于等于符號(==)的意圖上
    • 算術(shù)符號(+,-,*,/,%以及其他)可以檢測并阻止值溢出,以避免你在操作比存儲類型允許的范圍更大或者更小的數(shù)字時得到奇奇怪怪的結(jié)果

賦值運算符

  • 賦值運算符將一個值賦給另外一個值
  • 如果賦值符號右側(cè)是擁有多個值的元組,它的元素將會一次性地拆分成常量或者變量
  • Swift的賦值符號自身不會返回值

OC中常常有if( self = [super init] ),如果self初始化成功,則進入方法體內(nèi)部;而Swift中=符號不會返回值

算術(shù)運算符 - 標準運算符

  • 標準運算符+-*/
  • 加法運算符同時也支持String的拼接
  • Swift算術(shù)運算符默認不允許值溢出

算術(shù)運算符 - 余數(shù)運算符

  • 余數(shù)運算符(a % b)可以求出多個b的倍數(shù)能夠剛好放進a中并且返回剩下的值(就是我們所謂的余數(shù))
  • 當a是負數(shù)時也使用相同的方法來進行計算
  • 當b為負數(shù)時它的正負號被忽略掉了。這意味著a%b與a%-b能夠獲得相同的答案,如 9 % 2 為 1,,9 % (-2)結(jié)果也為1

算術(shù)運算符 - 一元

  • 數(shù)字值的正負號可以用前綴 - 來切換,我們稱之為一元減號運算符
  • 一元減號運算符(-)直接在要進行操作的值前邊設置,不加任何空格
  • 一元加好運算符(+)直接返回它操作的值,不會對其進行任何的修改

十六、處理算術(shù)結(jié)果溢出

溢出運算符

  • 在默認情況下,當將一個整數(shù)賦給超過它容量的值時,Swift會報錯而不是生成一個無效的數(shù),給我們操作過大或者過小的數(shù)的時候提供了額外的安全性
  • 同時提供三個算術(shù)溢出運算符來讓系統(tǒng)支持整數(shù)溢出運算
    • 溢出加法(&+)
    • 溢出減法(&-)
    • 溢出乘法(&*)

值溢出

  • 數(shù)值可以出現(xiàn)向上溢出或向下溢出
let a: UInt8 = UInt8.max
print(a) // 255
//let a1 = a + 1 // 直接用+會報錯,提示超出范圍
let a1 = a &+ 1
print(a1) // 結(jié)果為0

let b = UInt8.min
print(b) // 0
//let b1 = b - 1// 直接用-會報錯,提示超出范圍
let b1 = b &- 1
print(b1) // 結(jié)果為255
  • 溢出也會發(fā)生在有符號整形數(shù)值上
  • 對于無符號與有符號整形數(shù)值來說,當出現(xiàn)上溢時,它們會從數(shù)值所能容納的最大數(shù)變成最小的數(shù)。同樣的,當發(fā)生下溢時,它們會從所能容納的最小數(shù)變成最大的數(shù)。
let a: Int8 = Int8.max
print(a) // 127
//let a1 = a + 1 // 直接用+會報錯,提示超出范圍
let a1 = a &+ 1
print(a1) // 結(jié)果為-128

let b = Int8.min
print(b) // -128
//let b1 = b - 1// 直接用-會報錯,提示超出范圍
let b1 = b &- 1
print(b1) // 結(jié)果為127

溢出時的計算,實際會從符號位(負數(shù)為1,正數(shù)為0)借位,比如Int8,它的最大值為127,最小值為-128,127表示后7位全1(2的8次方減1),符號位為0,即011111111,加上1,則變成100000000,剛好是-128,而-128減去1,正好又變成011111111,即127

注意:除非非常清晰明了值溢出會帶來什么樣的結(jié)果,否則,最好盡量使用標準的算符運算符,這樣可以幫助我們發(fā)現(xiàn)錯誤

十七、為了Optional:合并空值運算符

合并空值運算符(空合運算符)

  • 合并空值運算符(a ?? b)如果可選項a有值則展開,如果沒有值,是nil,則返回默認值b
  • 表達式a必須是一個可選類型。表達式b必須與a的儲存類型相同
  • 實際上是三元運算符作用到Optional上的縮寫(a != nil ? a! : b
  • 如果a 的值是非空的,b的值將不會被考慮,也就是說合并空值運算符是短路的

十八、區(qū)間運算符

閉區(qū)間運算符

  • 閉區(qū)間運算符(a...b)定義從a到b的一組范圍,并且包含a和b。a的值不能大于b。

半開區(qū)間運算符

  • 半開區(qū)間運算符(a..<b)定義了從a到b但不包括b的區(qū)間
  • 如同閉區(qū)間操作符,a的值也不能大于b,如果a與b的值相等,那返回的區(qū)間將是空的

單側(cè)區(qū)間

  • 閉區(qū)間有另外一種形式來讓區(qū)間朝一個方向盡可能的遠,這種區(qū)間叫做單側(cè)區(qū)間,如5......5
  • 半開區(qū)間運算符同樣可以有單側(cè)形式,只需要寫它最終的值,如..<5
  • 比如說,一個包含數(shù)組所有元素的區(qū)間,從索引3到數(shù)組的結(jié)束。在這種情況下,你可以省略區(qū)間運算符一側(cè)的值
let array = [1,2,3,4,5,6,7,8,9]

print("左開區(qū)間")
for value in array[...5] {
    print(value)
}

print("右開區(qū)間")
for value in array[5...] {
    print(value)
}

print("半開區(qū)間")
for value in array[..<5] {
    print(value)
}
/*
左開區(qū)間
1
2
3
4
5
6
右開區(qū)間
6
7
8
9
半開區(qū)間
1
2
3
4
5
*/
  • 單側(cè)區(qū)間可以在其它上下文中使用,不僅僅是下標
  • 不能遍歷省略了第一個值的單側(cè)區(qū)間,因為遍歷根本不知道該從哪里開始。你可以遍歷省略了最終值的單側(cè)區(qū)間
let range = ...5 //PartialRangeThrough<Int>
print(range.contains(7)) //false
print(range.contains(4)) //true
print(range.contains(-1)) //true

字符串索引區(qū)間

  • 字符串范圍也可以使用區(qū)間運算符
var string = "abcdefghij"
let range = string.index(string.endIndex, offsetBy: -6)...
string.removeSubrange(range)
print(string)//abcd

倒序索引

  • 通過reversed()方法,我們可以將一個正序循環(huán)變成逆序循環(huán)
for i in (0..<10).reversed(){
    print(i)
}

Comparable區(qū)間

  • 區(qū)間運算符可以作用在Comparable類型上,返回閉區(qū)間和半閉區(qū)間
var string = "abcDefghij"
let interval = "a"..."z"
for c in string{
    if interval.contains(String(c)) == false{
        print("\(c)不是小寫字母")
    }
}
//D不是小寫字母

十九、位運算符

位取反運算符

  • 位取反運算符(~)是對所有位進行取反操作(1變成0,0變成1)
let a : UInt8 = 0x5// 00000101
let b = ~a // 11111010
print(b) // 轉(zhuǎn)為10進制即250

位與運算符

  • 位與運算符(&)可以對兩個數(shù)的比特位進行合并。它會返回一個新的數(shù),只有當這兩個數(shù)都是1的時候才能返回1
let a : UInt8 = 0x5// 00000101
var b: UInt8 = ~a // 11111010
var c = a & b
print(c) // 0 a和b互為反,所以與的結(jié)果為0
b = 0x11 // 00010001
c = a & b // 00000001
print(c) // 1

位或運算符

  • 位或運算符(|)可以對兩個比特位進行比較,然后返回一個新的數(shù),只要兩個操作位任意一個為1時,那么對應的位數(shù)就為1
let a : UInt8 = 0x5// 00000101
var b: UInt8 = ~a // 11111010
var c = a | b
print(c) // 255 a和b互為反,所以或就是全1
b = 0x11 // 00010001
c = a | b // 00010101
print(c) // 21

位異或運算符

  • 位異或運算符,或者說“互斥或”(^)可以對兩個數(shù)的比特位進行比較。它返回一個新的數(shù),當兩個操作數(shù)的對應位不相同時,該數(shù)的對應位就為1。(相同為0,不同為1)
let a : UInt8 = 0x5// 00000101
var b: UInt8 = ~a // 11111010
var c = a ^ b
print(c) // 255 a和b互為反,所以異或就是全1
b = 0x11 // 00010001
c = a ^ b // 00010100
print(c) // 20

異或也叫半加運算,其運算法則相當于不帶進位的二進制加法

位左移和右移運算符

  • 位左移運算符(<<)和位右移運算符(>>)可以把所有位數(shù)的數(shù)字向左或向右移動一個確定的位數(shù)
  • 位左移和右移具有給整數(shù)乘以或除以二的效果。將一個數(shù)左移一位相當于把這個數(shù)翻倍,將一個數(shù)右移一位相當于把這個數(shù)減半
let a : UInt8 = 0x5// 00000101
let b = a << 2 // 00010100
let c = a >> 2 // 00000001
print(b) // 20
print(c) // 1

無符號整數(shù)的移位操作

  • 已經(jīng)存在的比特位按指定的位數(shù)進行左移和右移
  • 任何移動超出整形存儲邊界的位都會被丟棄
  • 用0來填充向左或向右移動后產(chǎn)生的空白位

有符號整數(shù)的移位操作

  • 有符號整數(shù)使用它的第一位(所謂的符號位)來表示這個整數(shù)是正數(shù)還是負數(shù)。符號位為0表示為正數(shù),1表示為負數(shù)
  • 其余的位數(shù)(所謂的數(shù)值位)存儲了實際的值。有符號正整數(shù)和無符號數(shù)的存儲方式是一樣的,都是從0開始算起
  • 但是負數(shù)的存儲方式略有不同。它存儲的是2的n次方減去它的絕對值,這里的n為數(shù)值位的位數(shù)(補碼)

補碼表示的優(yōu)點

  • 首先,如果想給-4加個-1,只需要將這兩個數(shù)的全部八個比特位相加(包括符號位),并且將計算結(jié)果中超出的部分丟棄
  • 其次,使用二進制補碼可以使負數(shù)的位左移和右移操作得到跟正數(shù)同樣的效果,即每向左移一位就將自身的數(shù)值乘以2,每向右移一位就將自身的數(shù)值除以2。要達到此目的,對有符號整數(shù)的右移有一個額外的規(guī)則:當對整數(shù)進行位右移操作時,遵循與無符號整數(shù)相同的規(guī)則,但是對于移位產(chǎn)生的空白位使用符號位進行填充,而不是0
// 10000000 - 01110101 = 00001011
let a : Int8 = -0x75// 10001011
let b = a << 2 // 00101100
let c = a >> 2 // 11100010,實際存儲的數(shù)值為00011110,加上符號即-30
print(b) // 44
print(c) // -30

二十、位運算經(jīng)典算法

兩個數(shù)字交換

  • 不借助臨時變量,交換兩個變量的值
var a = 10
var b = 20
a = a ^ b
b = a ^ b
a = a ^ b
print(a) // 20 
print(b) // 10

求無符號整數(shù)二進制中1的個數(shù)

  • 給定一個無符號整形(UInt)變量,求其二進制表示中“1”的個數(shù),要求算法的執(zhí)行效率盡可能的高
func countOfOnes(num: UInt) -> UInt{
    var count: UInt = 0
    var temp = num
    while temp != 0{
        count += temp & 1
        temp >>= 1
    }
    return count
}
print(countOfOnes(num: 54))// 00110110,打印為 4
  • 如果整數(shù)的二進制中有較多的0,那么我們每一次右移一位做判斷會很浪費,怎么改進前面的算法呢?有沒有算法讓算法的復雜度只與“1”的個數(shù)有關(guān)?
  • 思路:為了簡化這個問題,我們考慮只有高位又1的情況,例如11000000,如何跳過前面低位的6個0,而直接去判斷第七位的1?我們可以設計11000000和10111111(也就是11000000 - 1)做“與”操作,消去最低位的1.如果得到的結(jié)果為0,說明我們已經(jīng)找到或者消去里面最后一個1。如果得到的結(jié)果為0,說明我們已經(jīng)找到或者消去里面最后一個1。如果不為0,那么說明我們消去了最低位的1,但是二進制中還有其他的1,我們的計數(shù)器需要加1,然后繼續(xù)上面的操作。
func countOfOnes(num: UInt) -> UInt{
    var count: UInt = 0
    var temp = num
    while temp != 0{
        count += 1
        temp &= temp - 1
    }
    return count
}
print(countOfOnes(num: 54))// 4

如何判斷一個整數(shù)為2的整數(shù)次冪

  • 給定一個無符號整形(UInt)變量,判斷是否為2的整數(shù)次冪
  • 思路:一個整數(shù)如果是2的整數(shù)次方,那么它的二進制表示中有且只有一位是1,而其它所有位都是0,根據(jù)前面的分析,把這個整數(shù)減去1后再和它自己做與運算,這個整數(shù)中唯一的1就變成0了,也就是得到的結(jié)果為0
func isPowerOfTwo(num: UInt) -> Bool{
    return (num & (num - 1)) == 0
}
print(isPowerOfTwo(num:512))//true

缺失的數(shù)字

  • 很多成對出現(xiàn)的正整數(shù)保存在磁盤文件中,注意成對的數(shù)字不一定是相鄰的,如2,3,4,3,4,2……,由于意外有一個數(shù)字消失了,如何盡快找到是哪個數(shù)字消失了?
  • 思路:考慮“異或”操作的定義,當兩個操作數(shù)的對應位不相同時,該數(shù)的對應位就為1。也就是如果是相等的兩個數(shù)“異或”,得到的結(jié)果為0,而0與任何數(shù)字“異或”,得到的是那個數(shù)字本身。所以我們考慮將所有的數(shù)字做“異或”操作,因為只有一個數(shù)字消失,那么其他兩兩出現(xiàn)的數(shù)字“異或”后為0,0與僅有的一個數(shù)字做“異或”,我們就得到了消失的數(shù)字是哪個。
func findLostNum(nums: [UInt]) -> UInt{
    var lostNum: UInt = 0
    for num in nums{
        lostNum ^= num
    }
    return lostNum
}

print(findLostNum(nums: [1,2,3,4,3,2,1])) // 4

缺失的數(shù)字2

  • 如果有兩個數(shù)字意外丟失了(丟失的不是相等的數(shù)字),該如何找到丟失的兩個數(shù)字?
  • 思路:設題目中這兩個只出現(xiàn)1次的數(shù)字分別為A和B,如果能將A,B分開到二個數(shù)組中,那顯然符合“異或”解法的關(guān)鍵點了。因此這個題目的關(guān)鍵點就是將A,B分開到二個數(shù)組中。由于A,B肯定是不相等的,因此在二進制上必定有一位是不同的。根據(jù)這一位是0還是1可以將A和B分開到A組和B組。而這個數(shù)組中其它數(shù)字要么就屬于A組,要么就屬于B組。再對A組和B組分別執(zhí)行“異或”解法就可以得到A,B了。而要判斷A,B在哪一位上不同,只要根據(jù)“A異或B”的結(jié)果就可以知道了,這個結(jié)果在二進制上為1的位都說明A,B在這一位上是不相同的。
func findLostNums(nums: [UInt]) -> (UInt,UInt){
    var lostNum1: UInt = 0
    var lostNum2: UInt = 0
    var temp: UInt = 0
    //計算兩個數(shù)的異或結(jié)果
    for num in nums {
        temp ^= num
    }
    //找到第一個為1的位
    var flag: UInt = 1
    while (flag & temp) == 0 {
        flag <<= 1
    }
    //找到兩個丟失的數(shù)字
    for num in nums {
        if (num & flag) == 0{
            lostNum1 ^= num
        } else {
            lostNum2 ^= num
        }
    }
    return (lostNum1,lostNum2)
}

print(findLostNums(nums: [1,2,3,4,5,3,2,1])) // (4, 5)

二十一、運算符的優(yōu)先級和結(jié)合性

基本概念

  • 運算符的優(yōu)先級使得一些運算符優(yōu)先于其它運算符,高優(yōu)先級的運算符會先計算
  • 結(jié)合性定義了具有相同優(yōu)先級的運算符是如何結(jié)合(或關(guān)聯(lián))的--是與左邊結(jié)合為一組,還是與右邊結(jié)合為一組??梢赃@樣理解:“它們是與左邊的表達式結(jié)合的”或者“它們是與右邊的表達式結(jié)合的”

編程規(guī)范

  • 最好使用小括號來顯式的指定優(yōu)先級,這樣代碼易于閱讀
  • Swift語言中邏輯運算符&&和||是左相關(guān)的,這意味著多個邏輯運算符組合的表達式會首先計算最左邊的字表達式

二十二、運算符重載

運算符重載

  • 類和結(jié)構(gòu)體可以為現(xiàn)有的運算符提供自定義的實現(xiàn),稱為運算符重載
struct Vector2D{
    var x = 0.0
    var y = 0.0
    
}
extension Vector2D{
    static func +(left: Vector2D,right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x,y:left.y + right.y)
    }
}

let v1 = Vector2D(x: 3,y:1)
let v2 = Vector2D(x: 5,y:8)
let combinedVector = v1+v2
print(combinedVector) // Vector2D(x: 8.0, y: 9.0)

一元運算符重載

  • 類與結(jié)構(gòu)體也能提供標準一元運算符的實現(xiàn)
  • 要實現(xiàn)前綴或者后綴運算符,需要在聲明運算符函數(shù)的時候在func關(guān)鍵字之前指定prefix或者postfix限定符
struct Vector2D{
    var x = 0.0
    var y = 0.0
    
}
extension Vector2D{
    static prefix func - (vector: Vector2D) -> Vector2D{
        return Vector2D(x: -vector.x,y: -vector.y)
    }
}

let positive = Vector2D(x: 3,y:1)
let negative = -positive
print(negative) // Vector2D(x: -3.0, y: -1.0)

組合賦值運算符重載

  • 組合賦值運算符將賦值運算符(=)與其它運算符進行結(jié)合
  • 在實現(xiàn)的時候,需要把運算符的左參數(shù)設置成inout類型,因為這個參數(shù)的值
struct Vector2D{
    var x = 0.0
    var y = 0.0
    
}
extension Vector2D{
    static func +(left: Vector2D,right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x,y:left.y + right.y)
    }
    static func +=(left: inout Vector2D,right: Vector2D) {
        left = left + right
    }
    static func -(left: Vector2D,right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x - right.x,y:left.y - right.y)
    }
    static func -=(left: inout Vector2D,right: Vector2D) {
        left = left - right
    }
}

var original = Vector2D(x: 3,y:1)
let vectorToAdd = Vector2D(x: 13,y:15)
original += vectorToAdd
print(original) // Vector2D(x: 16.0, y: 16.0)

等價運算符重載

  • 自定義類和結(jié)構(gòu)體不接受等價運算符的默認實現(xiàn),也就是所謂的“等于”運算符(==)和“不等于”運算符(!=)
  • 要使用等價運算符來檢查你自己類型的等價,需要和其他中綴運算符一樣提供一個“等于”運算符重載,并且遵循標準庫的Equatable協(xié)議
struct Vector2D{
    var x = 0.0
    var y = 0.0
    
}
extension Vector2D: Equatable{
    static func ==(left: Vector2D,right: Vector2D) -> Bool{
        return left.x == right.x && left.y == right.y;
    }
}

  • Swift為以下自定義類型提供等價運算符默認實現(xiàn)實現(xiàn)
    • 只擁有遵循Equatable協(xié)議存儲屬性的結(jié)構(gòu)體
    • 只擁有遵循Equatable協(xié)議存儲屬性的枚舉
    • 沒有關(guān)聯(lián)類型的枚舉
struct Vector3D: Equatable{
    var x = 0.0
    var y = 0.0
    var z = 0.0
    
}
let v1 = Vector3D(x:2,y: 3,z: 4)
let v2 = Vector3D(x:2,y: 3,z: 4)
if (v1 == v2){
    print("這兩個向量是相等的")//這兩個向量是相等的
}

二十三、自定義運算符

  • 除了實現(xiàn)標準運算符,在Swift中還可以聲明和實現(xiàn)自定義運算符(custom operators)
  • 新的運算符要在全局作用域內(nèi),使用operator關(guān)鍵字進行聲明,同時還要指定prefix、infix或者postfix限定符
struct Vector2D{
    var x = 0.0
    var y = 0.0
    
}
// 必須先聲明在全局作用域
prefix operator +++
prefix operator ---
extension Vector2D{
    static func +(left: Vector2D,right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x,y:left.y + right.y)
    }
    static func +=(left: inout Vector2D,right: Vector2D) {
        left = left + right
    }
    static func -(left: Vector2D,right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x - right.x,y:left.y - right.y)
    }
    static func -=(left: inout Vector2D,right: Vector2D) {
        left = left - right
    }
    // 實現(xiàn)自增,每個元素加1
    static prefix func +++ (vector: inout Vector2D) -> Vector2D{
        vector += Vector2D(x: 1,y: 1)
        return vector;
    }
    // 實現(xiàn)自減,實現(xiàn)中每個元素減1
    static prefix func --- (vector: inout Vector2D) -> Vector2D{
        vector -= Vector2D(x: 1,y: 1)
        return vector
    }
}

自定義中綴運算符的優(yōu)先級和結(jié)合性

  • 自定義中綴(infix)運算符也可以指定優(yōu)先級和結(jié)合性
  • 每一個自定義的中綴運算符都屬于一個優(yōu)先級組
  • 優(yōu)先級組指定了中綴運算符和其他中綴運算符的關(guān)系

優(yōu)先級的定義如下:

/*
 定義自己的優(yōu)先級組
 
 associativity表示結(jié)合性
 lowerThan表示優(yōu)先級在哪個優(yōu)先級之下
 higherThan表示優(yōu)先級在哪個優(yōu)先級之上
 */
precedencegroup MyPrecedence{
    associativity: left
    lowerThan: MultiplicationPrecedence
    higherThan: 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)
    }
}
// 定義一個乘平方運算符,優(yōu)先級是乘法組  乘法組優(yōu)先級高于加法組
infix operator *^: MultiplicationPrecedence

extension Vector2D{
    static func *^(left: Vector2D,right: Vector2D) -> Vector2D{
        return Vector2D(x: left.x * right.x,y: left.y * left.y + right.y * right.y)
    }
}

let firstVector = Vector2D(x: 1,y: 2)
let secondVector = Vector2D(x: 3,y: 7)
let plusMinusVector = firstVector +- secondVector
let thirdVector = Vector2D(x: 2,y: 2)
let vector = firstVector +- secondVector *^ thirdVector
print(plusMinusVector)//Vector2D(x: 4.0, y: -5.0)
print(vector)//Vector2D(x: 7.0, y: -51.0)

若使用一個自定義的優(yōu)先級(低于加法優(yōu)先級),代碼如下:

precedencegroup MyPrecedence{
    associativity: left
    lowerThan: 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)
    }
}
// 定義一個乘平方運算符,優(yōu)先級是自定義的低于加法組的優(yōu)先級
infix operator *^: MyPrecedence

extension Vector2D{
    static func *^(left: Vector2D,right: Vector2D) -> Vector2D{
        return Vector2D(x: left.x * right.x,y: left.y * left.y + right.y * right.y)
    }
}

let firstVector = Vector2D(x: 1,y: 2)
let secondVector = Vector2D(x: 3,y: 7)
let plusMinusVector = firstVector +- secondVector
let thirdVector = Vector2D(x: 2,y: 2)
let vector = firstVector +- secondVector *^ thirdVector
print(plusMinusVector)//Vector2D(x: 4.0, y: -5.0)
print(vector)//Vector2D(x: 8.0, y: 29.0)

二十四、流程控制-循環(huán)

for-in循環(huán)

  • 使用for-in循環(huán)來遍歷序列,比如一個范圍的數(shù)字,數(shù)組中的元素或者字符串中的字符
for i in 0...3{
    print(i)
}
for c in "你好啊"{
    print(c)
}
let set = Set([1,3,5])
for i in set{
    print(i)
}
let array = [1,3,5]
for i in array{
    print(i)
}
  • 當字典遍歷時,每一個元素都返回一個(key,value)元組,你可以在for-in循環(huán)體中使用顯式命令常量來分解(key,value)元組成員
let dictionay = ["a":1,"b":2,"c":3]
for tuple in dictionay{
    print(tuple.0)
    print(tuple.1)
}
for (key,value) in dictionay{
    print(key)
    print(value)
}
for (_, value) in dictionay{
    print(value)
}

  • 如果不需要序列的每一個值,你可以使用下劃線來取代遍歷名以忽略值
let dictionay = ["a":1,"b":2,"c":3]
for (_, value) in dictionay{
    print(value)
}

var addition = 0
for _ in 1...10{
    addition += 1
}

for-in 分段區(qū)間

  • 使用stride(from:to:by:)函數(shù)來跳過不想要的標記(開區(qū)間)
  • 閉區(qū)間也同樣適用,使用stride(from:through:by:)即可
let interval = 5
for mark in stride(from: 0, to: 50, by: interval){
    print(mark)
}
print("--------------")
for mark in stride(from: 0, through: 50, by: interval){
    print(mark)
}
/*
0
5
10
15
20
25
30
35
40
45
--------------
0
5
10
15
20
25
30
35
40
45
50
*/

while循環(huán)

  • 直接用while循環(huán)
  • repeat-while循環(huán)(和OC中的do-while一樣)
var count = 0
repeat {
    print(count)
    count += 1
} while count < 5

二十五、流程控制-(switch-case)

switch

  • switch語句會將一個值與多個可能的模式匹配。然后基于第一個成功匹配的模式來執(zhí)行合適的代碼塊
  • switch語句一定得是全面的。就是說,給定類型里每一個值都得被考慮到并且匹配到一個switch的case。如果無法提供一個switch-case所有可能的值,你可以定義一個默認匹配所有的case來匹配所有未明確出來的值。這個匹配所有的情況用關(guān)鍵字default標記,并且必須在所有case的最后出現(xiàn)。

在OC中,switch語句如果不全面,仍然可以運行

沒有隱式貫穿

  • 相比C和OC里的switch語句來說,Swift中的switch語句不會默認從匹配case的末尾貫穿到下一個case里
  • 相反,整個switch語句會在匹配到第一個switch的case執(zhí)行完畢之后退出,不再需要顯式的break語句
  • 如果想貫穿到下一個case,在此case語句的末尾使用fallthrough

執(zhí)行體

  • 每一個case的函數(shù)體必須包含至少一個可執(zhí)行的語句
  • 在一個switch的case中匹配多個值可以用逗號分割,并且可以寫成多行
let c: Character = "a"
switch c{
case "a":
    fallthrough
case "A":
    print("the letter is A")
default:
    print("the letter is not A")
}
print("-----------")
switch c{
case "a","A":
    print("the letter is A")
default:
    print("the letter is not A")
}

/*
 the letter is A
 -----------
 the letter is A
 
 */

區(qū)間匹配

  • switch的case的值可以在一個區(qū)間中匹配
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var naturalCount: String
switch approximateCount{
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
    
default:
    naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
//There are dozens of moons orbiting Saturn.

元組匹配

  • 可以使用元組來在一個switch語句中測試多個值
  • 使用下劃線(_)來表明匹配所有可能的值
let point = (1,1)
switch point{
case (0,0):
    print("origin")
case (_,0):
    print("x-axis")
case (0,_):
    print("y-axis")
case (-2...2,-2...2):
    print("inside a 2 side box")
default:
    print("outside of the box")
}
//inside a 2 side box

值綁定

  • switch的case可以將匹配到的值臨時綁定為一個常量或者變量,來給case的函數(shù)體使用
  • 如果使用var關(guān)鍵字,臨時的變量就會以合適的值來創(chuàng)建并初始化。對這個變量的任何改變都只會在case的函數(shù)體內(nèi)有效
let point = (1,1)
switch point{
case (let x,0):
    print("on the x-axis with an x value of \(x)")
case (0,let y):
    print("on the y-axis with an y value of \(y)")
case (let x,let y):
    print("point is {\(x),\(y)}")
}
//point is {1,1}

where子句

  • switch-case可以使用where子句來檢查是否符合特定的約束
let point = (1,-1)
switch point{
case let (x,y) where x == y:
    print("on the line y=x")
case let (x, y) where x == -y:
    print("on the line y=-x")
case let (x, y):
    print("point is {\(x),\(y)}")
}
//on the line y=-x

符合匹配

  • 多種情況共享同一個函數(shù)體的多個情況可以在case后寫多個模式來復合,在每個模式之間用逗號分割。
  • 如果任何一個模式匹配了,那么這個情況都會被認為是匹配的。
  • 如果模式太長,可以把它們寫成多行
let c = Character("m")
switch c{
case "a","e","i","o","u":
    print("元音字母")
case let cc where "bcdfghjklmnpqrstvwxyz".contains(cc):
    print("輔音字母")
default:
    print("既不是元音,也不是輔音")
}

二十六、流程控制-控制轉(zhuǎn)移

  • continue
  • break
  • fallthrough
  • return
  • throw

continue

  • continue語句告訴循環(huán)停止正在做的事情并且再次從頭開始循環(huán)的下一次遍歷。它是說“我不再繼續(xù)當前的循環(huán)遍歷了”而不是離開整個的循環(huán)。

break

  • break語句會立即結(jié)束整個控制流語句。當你想要提前結(jié)束switch或者循環(huán)語句或者其他情況時可以在switch語句或者循環(huán)語句中使用break語句。
  • 當在循環(huán)語句中使用時,break會立即結(jié)束循環(huán)的執(zhí)行,并且轉(zhuǎn)移控制到循環(huán)結(jié)束花括號(})后的第一行代碼上。當前遍歷循環(huán)里的其他代碼都不會被執(zhí)行,并且余下的遍歷循環(huán)也不會開始了。
  • 當在switch語句里使用時,break導致switch語句立即結(jié)束它的執(zhí)行,并且轉(zhuǎn)移控制到switch語句結(jié)束花括號(})之后的第一行代碼上

fallthough

  • 如果確實需要C或者OC風格的貫穿行為,可以選擇在switch中case的末尾加上fallthough關(guān)鍵字

語句標簽

  • 可以用語句標簽來給循環(huán)語句或者條件語句作標記。在一個條件語句中,你可以使用一個語句標簽配合break語句來結(jié)束被標記的語句。在循環(huán)語句中,你可以使用語句標簽來配合break或者continue語句來結(jié)束或者繼續(xù)執(zhí)行被標記的語句。
var number = 10
whileLoop: while number > 0 {
    switch number{
    case 9:
        print("9")
    case 10:
        var sum = 0
        for index in 0...10{
            sum += index
            if index == 9 {
                print(sum)
                // 從這里直接退出了外層的循環(huán)
                break whileLoop
            }
        }
    default:
        break
    }
    number -= 1
    
}

二十七、使用guard來改善條件判斷

guard

  • guard語句,類似于if語句,基于布爾值表達式來執(zhí)行語句。使用guard語句來要求一個條件必須是真才能執(zhí)行g(shù)uard之后的語句。與if語句不同,guard語句綜上有一個else子句--else子句中的代碼會在條件不為真的時候執(zhí)行。

  • 黃金大道原則:當編寫條件語句的時候,左邊的代碼間距應該是一個“黃金”或者“快樂”的大道。這是說,不要嵌套if語句。多個return語句是OK的。這樣可以避免圈復雜度(Cyclomatic Complexity),并且讓代碼更加容易閱讀。因為你的方法的重要部分沒有嵌套在分支上,你可以很清楚地找到相關(guān)的代碼。

  • Early Exit原則

func isIPAddress(ipAddr: String) -> (Int,String){
    let compoments = ipAddr.split(separator: ".")
    guard compoments.count == 4 else {
        return (100,"ip只能有4部分")
    }
    guard let first = Int(compoments[0]),first >= 0 && first <= 255 else {
        return (1,"第一個數(shù)不對")
    }
    guard let second = Int(compoments[1]),second >= 0 && second <= 255 else {
        return (1,"第二個數(shù)不對")
    }
    guard let third = Int(compoments[2]),third >= 0 && third <= 255 else {
        return (1,"第三個數(shù)不對")
    }
    guard let fourth = Int(compoments[3]),fourth >= 0 && fourth <= 255 else {
        return (1,"第四個數(shù)不對")
    }
    return (0,"")
}

檢查API的可用性

  • Swift擁有內(nèi)置的對API可用性的檢查功能,它能夠確保你不會悲劇地使用了對部屬目標不可用的API
  • 你可以在if或者guard語句中使用一個可用性條件來由條件地執(zhí)行代碼,基于在運行時你想用的哪個API是可用的
    if #available(platform name version, *)
if #available(iOS 13.0,macOS 12.1, *){
    // 使用iOS13.0,macOS 12.1后的API
} else {
    // fall back到早期的iOS、macOS的API
}

二十八、模式和模式匹配

模式

  • 模式代表單個值或者復合值的結(jié)構(gòu)

  • 例如,元組(1,2)的結(jié)構(gòu)是由逗號分割的,包含兩個元素的列表。因為模式代表一種值的結(jié)構(gòu),而不是特定的某個值,你可以利用模式來匹配各種各樣的值。比如,(x,y)可以匹配元組(1,2),以及任何含兩個元素的元組。除了利用模式匹配一個值以外,你可以從復合值中提取出部分或全部值,然后分別把各個部分的值和一個常量或變量綁定起來。

  • Swift中的模式分為兩類:一種能成功匹配任何類型的值,另一種在運行時匹配某個特定值時可能會失敗。

    • 第一類模式用于解構(gòu)簡單變量、常量和可選綁定中的值。此類模式包括通配符模式、標識符模式,以及包含前兩種模式的值綁定模式和元組模式。你可以為這類模式指定一個類型標注,從而限制它們只能匹配某種特定類型的值。
    • 第二類模式用于全模式匹配,這種情況下你試圖匹配的值在運行時可能不存在。此類模式包括枚舉用例模式、可選模式、表達式模式和類型轉(zhuǎn)換模式。你在switch語句的case標簽中,do語句的catch子句中,或者在if、while、guard和for-in語句的case條件句中使用此類模式。

模式分類

  • 通配符模式(Wildcard Pattern)
  • 標識符模式(Identifier Pattern)
  • 值綁定模式(Value-Binding Pattern)
  • 元組模式(Tuple Pattern)
  • 枚舉用例模式(Enumeration Case Pattern)
  • 可選項模式(Optional Pattern)
  • 類型轉(zhuǎn)換模式(Type-Casting Pattern)
  • 表達式模式(Expression Pattern)

通配符模式(Wildcard Pattern)

  • 通配符模式由一個下劃線(_)構(gòu)成,用于匹配并忽略任何值。當你想忽略被匹配的值時可使用該模式
for _ in 1...3{
    
}

標識符模式(Identifier Pattern)

  • 標識符模式匹配任何值,并將匹配的值和一個變量或常量綁定起來
let someValue = 42

值綁定模式(Value-Binding Pattern)

  • 值綁定模式把匹配到的值綁定給一個變量或者常量。把匹配到的值綁定給常量時,用關(guān)鍵字let,綁定給變量時,用關(guān)鍵字var。
let point = (3,2)
switch point{
    // 將point中的元素綁定到x和y
case let (x,y):
    print("The point is at (\(x),\(y))")//The point is at (3,2)
}

var optionalValue: String? = "abc"
guard let optionalValue else {
    print("value is nil")
    throw NSError(domain: "ttt", code: 10034, userInfo: nil)
}
print("value is not nil")

元組模式(Tuple Pattern)

  • 元組模式是由逗號分割的,具有零個或多個模式的列表,并由一對圓括號括起來。元組模式匹配相應元組類型的值。
  • 可以使用類型標注去限制一個元組模式能匹配哪種元組類型。例如,在常量聲明let (x,y): (Int, Int) = (1,2)中的元組模式(x,y): (Int, Int)只能匹配兩個元素都是Int類型的元組
  • 當元組模式被用于for-in語句或者變量和常量聲明時,它僅可以包含通配符模式、標識符模式、可選模式或者其他包含這些模式的元組模式
let points = [(0,0),(1,0),(1,1),(2,0),(2,1)]
for (x,y) in points where y == 0{
    print("\(x) and \(y)")
}
/*
0 and 0
1 and 0
2 and 0
*/

枚舉用例模式(Enumeration Case Pattern)

  • 枚舉用例模式匹配現(xiàn)有的某個枚舉類型的某個用例。枚舉用例模式出現(xiàn)在switch語句中的case標簽中,以及if、while、guard和for-in語句的case條件中。

可選項模式(Optional Pattern)

  • 可選項模式匹配Optional<Wrapped>枚舉在some(Wrapped)中包裝的值。
  • 可選項模式為for-in語句提供了一種迭代數(shù)組的簡便方式,只為數(shù)組中非nil的元素執(zhí)行循環(huán)體
let intOptional: Int? = 42
// 用可選項模式的枚舉來匹配
if case .some(let x) = intOptional {
    print(x) // 42
}

// 用可選項模式匹配 只會匹配到非nil的值
if case let x? = intOptional{
    print(x) // 42
}

let arrayOfOptionalInts: [Int?] = [nil,2,3,nil,5]
// 只會匹配到非nil的值
for case let number? in arrayOfOptionalInts{
    print("found a \(number)")
}

/*
 42
 42
 found a 2
 found a 3
 found a 5
 */

類型轉(zhuǎn)換模式(Type-Casting Pattern)

  • 有兩種類型轉(zhuǎn)換模式,is模式和as模式。is模式只出現(xiàn)在switch語句中的case標簽中。is模式和as模式形式如下:
    • is 類型
    • 模式 as 類型
  • is模式僅當一個值的類型在運行時和is模式右邊的指定類型一致,或者是其子類的情況下,才會匹配這個值。is模式和is運算符有相似表現(xiàn),它們都進行類型轉(zhuǎn)換,但是is模式?jīng)]有返回類型。
  • as模式僅當一個值的類型在運行時和as模式右邊的指定類型一致,或者是其子類的情況下,才會匹配這個值。如果匹配成功,被匹配的值的類型被轉(zhuǎn)換成as模式右邊指定的類型
protocol Animal{
    var name: String {get}
}

struct Dog: Animal{
    var name: String{
        "dog"
    }
    var runSpeed: Int
}
struct Bird: Animal{
    var name: String{
        "bird"
    }
    var flightHeight: Int
}
struct Fish: Animal{
    var name: String{
        "fish"
    }
    var depth: Int
}

let animals: [Any] = [Dog(runSpeed: 55), Bird(flightHeight: 2000), Fish(depth: 100)]

for animal in animals{
    switch animal{
    case let dog as Dog:
        print("\(dog.name) can run \(dog.runSpeed)")
    case let fish as Fish:
        print("\(fish.name) can dive depth \(fish.depth)")
    case is Bird:
        print("bird can fly")
    default:
        print("unknown animal")
    }
}
/*
 dog can run 55
 bird can fly
 fish can dive depth 100
 */

表達式模式(Expression Pattern)

  • 表達式模式代表表達式的值。表達式模式只出現(xiàn)在switch語句中的case標簽中。
  • 表達式模式代表的表達式會使用Swift標準庫中的~=運算符與輸入表達式的值進行比較。如果~=運算符返回true,則匹配成功。默認情況下,~=運算符使用==來比較兩個相同類型的值。它也可以將一個整形數(shù)值與一個Range實例中的一段整數(shù)區(qū)間做匹配。
let point = (1,2)
switch point{
case (0,0):
    print("origin")
case (-2...2,-2...2):
    print("in the square")//in the square
default:
    print("The point is at (\(point.0),\(point.1)).")
}
  • 可以重載~=運算符來提供自定義的表達式匹配行為
func ~= (pattern: String,value: Int) -> Bool{
    pattern == "\(value)"
}
let point = (0,0)
switch point{
case ("0","0"):
    print("origin") // origin
default:
    print("The point is at (\(point.0),\(point.1)).")
}
  • 自定義類型默認也是無法進行表達式模式匹配的,也需要重載~=運算符
struct Employee{
    var salary: Float
}
let e = Employee(salary: 33000)
func ~=(lhs: Range<Float>,rhs: Employee) -> Bool{
     lhs.contains(rhs.salary)
}
switch e {
case 0.0..<1000:
    print("艱難度日")
case 1000..<5000:
    print("勉強溫飽")
case 5000..<10000:
    print("日子不錯了")
case 10000..<50000:
    print("中產(chǎn)階級") // 中產(chǎn)階級
default:
    print("不敢想象")
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容