一、可選項
-
1.1慈格、可選項(Optional)
一般也叫可選類型怠晴,它允許將值設(shè)為 nil
-
在類型名稱后面加個問號 ?來定義一個可選型
var name: String? = "王三" name = nil
提示:如果定義如下
var name:String? 等價于 var name:String? = nil
可選型其實也就是要么
有值
,要么是nil
默認(rèn)的情況下 可選型的值在沒有復(fù)制的情況下使用浴捆,值是 nil ,如上面的提示
-
具體的舉例如下
var array = [12,3,8,20] func get(_ index:Int) -> Int?{ if index < 0 || index >= array.count { return nil } return array[index] } print(get(2)) // Optional(8) print(get(-2)) // nil
-
1.2蒜田、強制解包
-
(1)、可選項是對其他類型的一層包裝选泻,可以理解為一個盒子
如果為
nil
冲粤,那么它是個空盒子-
如果不為
nil
美莫,那么盒子里面裝的是:被包裝類型的數(shù)據(jù)var age:Int? // 空盒子 nil age = 10 // 盒子有內(nèi)容 Optional(10) age = nil // 空盒子 nil
-
(2)、如果想要從可選項里面取出被包裝的數(shù)據(jù)(將盒子里面的東西取出來)梯捕,需要用
感嘆號
?? 進(jìn)行強制解包let age:Int? = 10 let ageInt:Int = age! print(ageInt + 10)
提示:如果age 為nil 進(jìn)行解包會報錯:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
,如下例子let age:Int? = nil let ageInt:Int = age!
-
-
1.3厢呵、判斷可選項是否包含值
-
具體的例子
let number = Int("521") if number != nil{ print("字符串轉(zhuǎn)換整數(shù)成功:\(number!)") }else{ print("字符串轉(zhuǎn)換整數(shù)失敗") }
提示:
Int("521")
可以把里面的字符串轉(zhuǎn)換為整數(shù),但是如果里面是abc
,那么就會轉(zhuǎn)換失敗為:nil
-
-
1.4傀顾、可選項綁定 (Optional Binding)
- 可以用 可選項 綁定來判斷可選項是否包含值
如果包含就自動解包襟铭,把它賦值為一個臨時變量(
let
)或者變量(var
),并返回true
,否則返回false
-
舉例一
if let number = Int("123"){ print("字符串轉(zhuǎn)換整數(shù)成功:\(number)") // number 是強制解包之后的 Int 值 // number 的作用域僅限于這個大括號 }else{ print("字符串轉(zhuǎn)換整數(shù)失敗") }
-
舉例二
enum Season:Int{ case spring = 1,summer,autumn,winter } if let season = Season(rawValue: 8){ switch season { case .spring: print("春天") default: print("其他季節(jié)") } }else{ print("解包失敗") }
結(jié)果是:解包失敗
- 可以用 可選項 綁定來判斷可選項是否包含值
-
1.5、等價寫法
-
寫法一
if let num1 = Int("10") { if let num2 = Int("30") { if num1 < num2 && num2 < 100 { print("\(num1)<\(num2)<100") } } }
打印結(jié)果:10<30<100
-
寫法二
if let num1 = Int("10"), let num2 = Int("30"), num1 < num2 && num2 < 100{ print("\(num1)<\(num2)<100") }
打印結(jié)果:10<30<100
提示:牽涉到可選項的綁定的時候锣笨,不能用
&&
來連接蝌矛,只能用,
來連接
-
-
1.6、while選項中使用可選項綁定
遍歷數(shù)組错英,將遇到的正數(shù)都加起來入撒,如果遇到負(fù)數(shù)或者非數(shù)字,停止遍歷var array = ["1","3","40","-2","abc","29","0"] var index = 0 var sum = 0 while let num = Int(array[index]),num > 0 { sum += num index += 1 } print("不滿足條件的值是:\(array[index])")
-
1.7椭岩、空合并運算符 :
??
(Nil-Coalesing Operator)public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
-
解釋:上面的意思也就是
a 是可選項 b 是可選項 或者 不是可選項 b 跟 a 的存儲類型必須相同 如果 a 不為nil茅逮,就返回 a 如果 a 為nil,就返回 b 如果 b 不是可選項判哥,返回 a 時會自動解包
提示:返回類型其實是由 b 的類型決定的献雅,在返回 a的時候,如果 b 不是可選型塌计,那么返回 a 時,a的盒子會自動解包取出值
-
舉例如下:
-
例一
let a: Int? = 1 let b: Int? = 2 let c = a ?? b // c是Int? , Optional(1)
-
例二
let a: Int? = nil let b: Int? = 2 let c = a ?? b // c是Int? , Optional(2)
-
例三
let a: Int? = nil let b: Int? = nil let c = a ?? b // c是Int? , nil
-
例四:a有值挺身,b不是可選類型,a 解包 后為 b的類型
let a: Int? = 2 let b: Int = 1 let c = a ?? b // c是Int , 2
-
例五:a有值锌仅,b不是可選類型章钾,a 解包 后為 b的類型
let a: Int? = nil let b: Int = 2 let c = a ?? b // c是Int , 2
-
例六:如果不使用空合并運算符,我們需要如下運算
let a: Int? = nil let b: Int = 2 // 如果不使用 ?? 運算符 let c:Int if let tmp = a{ c = tmp }else{ c = b }
提示:上面的 a 類似于在可選項綁定的使用
-
-
1.8热芹、多個
??
一起使用-
例一
let a: Int? = 1 let b: Int = 2 let c = a ?? b ?? 3 // c 是 Int,值為 1
-
例二
let a: Int? = nil let b: Int = 2 let c = a ?? b ?? 3 // c 是 Int,值為 2
-
例三
let a: Int? = nil let b: Int = nil let c = a ?? b ?? 3 // c 是 Int,值為 3
-
-
1.9贱傀、
??
跟if let
配合使用let a: Int? = nil let b: Int = 2 if let c = a ?? b{ print(c) }
提示:上面類似于
if a != nil || b != nil
let a: Int? = nil let b: Int = 2 if let c = a, let d = b{ print(c) }
提示:上面類似于
if a != nil && b != nil
-
1.10、if 語句實現(xiàn)登錄
func login(_ info: [String : String]) { let username: String if let tmp = info["username"] { username = tmp }else{ print("請輸入用戶名") return } let password: String if let tmp = info["password"] { password = tmp } else { print("請輸入密碼") return } print("用戶名:\(username) 密碼:\(password) 登錄") }
測試
// 用戶名:jack 密碼:123456 登陸ing login(["username" : "jack", "password" : "123456"]) // 請輸入用戶名 login(["password" : "123456"]) // 請輸入密碼 login(["username" : "jack"])
-
1.11伊脓、guard 語句
-
語法格式:
guard 條件 else { // do someting ..... // 退出當(dāng)前的作用域 // return府寒、berak、continue报腔、throwerror }
- 當(dāng) guard 語句條件為 false 時株搔,就會執(zhí)行大括號里面的代碼
- 當(dāng) guard 語句條件為 true 時,就會跳過 guard 語句
- guard 語句 特別適合用來 “提前退出”
-
當(dāng)使用 guard 語句 進(jìn)行可選項綁定的時候纯蛾,綁定的常量(let)邪狞、變量(var) 也能在外層作用域使用
func login(_ info: [String : String]) { guard let username = info["username"] else { print("請輸入用戶名") return } guard let password = info["password"] else { print("請輸入密碼") return } print("用戶名:\(username) 密碼:\(password) 登錄") }
調(diào)用
// 用戶名:jack 密碼:123456 登陸ing login(["username" : "jack", "password" : "123456"]) // 請輸入用戶名 login(["password" : "123456"]) // 請輸入密碼 login(["username" : "jack"])
-
-
1.12、隱式解包(Implicitly UnwrappedOptional)
在某些情況下茅撞,可選項一旦被設(shè)定值之后,就會一直擁有值
在這種情況下,可以去掉檢查米丘,也不必每次訪問的時候都進(jìn)行解包剑令,因為它能確定每次訪問的時候都有值
-
可以在類型后面加個
感嘆號
??,定義一個隱式解包的可選項let num1: Int! = 10 if num1 != nil { print(num1 + 6) // 16 } if let num3 = num1 { print(num3) }
提示:如果用
!
要保證有值,因為 用!的變量或者常量在使用的時候是在強制解包拄查,不然會報錯吁津,如下let num1: Int! = nil let num2: Int = num1 print(num2)
報錯:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
-
1.13、字符串插值
-
如下代碼的警告:
String interpolation produces a debug description for an optional value; did you mean to make this explicit?
let heigt:Int? = 29 print("height=\(heigt)")
-
處理警告的報錯方式
-
方式一:強制解包
print("height=\(heigt!)")
-
方式二:
print("height=\(String(describing: heigt))")
-
方式三:
print("height=\(heigt ?? 0)")
-
-
-
1.14堕扶、多重可選項
-
多重可選項舉例一
var num1:Int? = 10 var num2:Int?? = num1 var num3:Int?? = 10 print(num1 == num3)
可以使用 lldb 命令來查看:
frame variable -R
或者fr v -R
查看區(qū)別
frame
內(nèi)存布局variable
變量
-
多重可選項舉例二
var num1:Int? = nil var num2:Int?? = num1 var num3:Int?? = nil print(num1 == num3)
-
二碍脏、結(jié)構(gòu)體
-
2.1、結(jié)構(gòu)體:在 Swift 標(biāo)準(zhǔn)庫庫中稍算,絕大多數(shù)的公開類型都是結(jié)構(gòu)體(值類型)典尾,而枚舉和類(引用類型)只占很小的一部分,比如:Bool糊探、Int钾埂、Double、String科平、 Array褥紫、Dictionary 等常見類型都是結(jié)構(gòu)體,其中可以定義方法和屬性瞪慧,但其不像類一樣具有繼承的特性髓考。
struct Date { var year: Int var month: Int var day: Int } var date = Date(year: 2019, month: 6, day: 24)
所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(initialier 初始化方法、構(gòu)造器弃酌、構(gòu)造方法)氨菇,在2.1、中的調(diào)用
var date = Date(year: 2019, month: 6, day: 24)
代碼 可以傳入所有成員的值矢腻,以初始化所有成員(存儲屬性门驾,英文名:Sored Property) -
2.2、結(jié)構(gòu)體的初始化器
-
編譯器會根據(jù)情況多柑,可能會為結(jié)構(gòu)體生成多個初始化器奶是,宗旨是:保證所有的成員都有初始值,如下
struct Point { var x:Int var y:Int } var p1 = Point(x: 10, y: 10) var p2 = Point(x: 10) // Definition conflicts with previous value var p3 = Point(y: 10) // Definition conflicts with previous value var p4 = Point() // Definition conflicts with previous value
-
舉例二
struct Point { var x:Int = 0 var y:Int } var p1 = Point(x: 10, y: 10) var p1 = Point(x: 10) // Definition conflicts with previous value var p1 = Point(y: 10) // Definition conflicts with previous value var p1 = Point() // Definition conflicts with previous value
-
舉例三
struct Point { var x:Int var y:Int = 0 } var p1 = Point(x: 10, y: 10) var p1 = Point(x: 10) // Definition conflicts with previous value var p1 = Point(y: 10) // Definition conflicts with previous value var p1 = Point() // Definition conflicts with previous value
-
舉例四
struct Point { var x:Int = 0 var y:Int = 0 } var p1 = Point(x: 10, y: 10) var p1 = Point(x: 10) // Definition conflicts with previous value var p1 = Point(y: 10) // Definition conflicts with previous value var p1 = Point() // Definition conflicts with previous value
-
思考下面能編譯通過嗎竣灌?
struct Point { var x: Int? var y: Int? } var p1 = Point(x: 1, y: 2) var p2 = Point(y: 1) var p3 = Point(x: 2) var p4 = Point()
結(jié)論:可以編譯通過聂沙,因為 可選項都有個默認(rèn)值 nil,因此可以編譯通過
-
-
2.3初嘹、自定義初始化器
-
一旦在定義結(jié)構(gòu)體的時候定義了初始化器及汉,編譯器就不會再幫它自動生成其他初始化器
struct Point { var x: Int = 0 var y: Int = 0 init(x:Int,y:Int) { self.x = x self.y = y } } var p1 = Point(x: 1, y: 2) var p2 = Point(y: 1) // 報錯 var p3 = Point(x: 2) // 報錯 var p4 = Point() // 報錯
-
-
2.4、窺探初始化器的本質(zhì)屯烦,下面的兩段代碼等效
-
第 1 段代碼
struct Point { var x: Int = 0 var y: Int = 0 } var p = Point()
-
第 2 段代碼
struct Point { var x: Int = 0 var y: Int = 0 init(x:Int,y:Int) { self.x = x self.y = y } } var p = Point()
-
-
2.5坷随、結(jié)構(gòu)體的內(nèi)存結(jié)構(gòu)
struct Point { var x: Int = 0 var y: Int = 0 var origin: Bool = false } print(MemoryLayout<Point>.size) // 17 print(MemoryLayout<Point>.stride) // 24 print(MemoryLayout<Point>.alignment) // 8
結(jié)構(gòu)體所占用的內(nèi)存空間:所有存儲屬性內(nèi)存空間之和對齊之后的結(jié)果
三房铭、類
-
3.1、類的定義和結(jié)構(gòu)體類似温眉,但編譯體并沒有為類自動生成可以傳入成員值的初始化器缸匪,如下三段代碼
-
代碼段一
class Point { var x: Int = 0 var y: Int = 0 } var p1 = Point(x: 10, y: 10) // 報錯 var p2 = Point(y: 10) // 報錯 var p3 = Point(x: 10) // 報錯 var p4 = Point()
提示:類只有最簡單的初始化器:類名()
-
代碼段二
struct Point { var x: Int = 0 var y: Int = 0 } var p1 = Point(x: 10, y: 10) var p2 = Point(y: 10) var p3 = Point(x: 10) var p4 = Point()
-
代碼段三
-
-
3.2、類的初始化器
如果類的所有成員都在定義的時候指定了初始值类溢,編譯器會為類生成無參數(shù)的初始化器
-
成員的初始化是在這個初始化器中完成的
class Point{ var x:Int = 1 var y:Int = 2 } let p = Point()
等效下面的代碼
class Point{ var x:Int var y:Int init(){ self.x = 1 self.y = 2 } } let p = Point()
-
3.3凌蔬、結(jié)構(gòu)體與類的本質(zhì)區(qū)別
-
結(jié)構(gòu)體是值類型(枚舉也是值類型),類是引用類型(指針類型)
class Size{ var width = 1 var height = 2 } struct Point{ var x = 3 var y = 4 } func test(){ var size = Size() var point = Point() }
- 前 8 個字節(jié)指向類型的信息闯冷,后8個是指向引用計數(shù) 砂心,最后面的16個是來存儲成員變量的
-
-
3.4、值類型
-
值類型賦值給
var
蛇耀、let
或者給函數(shù)傳參辩诞,是直接將所有的內(nèi)容拷貝一份,類似于對文件進(jìn)行 copy蒂窒、paste操作躁倒,產(chǎn)生了全新的文件副本。屬于深拷貝(deep copy)struct Point{ var x:Int var y:Int } func test(){ var p1 = Point(x: 1, y: 2) var p2 = p1 p2.x = 10 p2.y = 20 print("p1.x=\(p1.x) p1.y=\(p1.y)") }
打印結(jié)果是:p1.x=1 p1.y=2
-
-
3.5洒琢、值類型的賦值操作
-
例一:字符串
var s1 = "Tom" var s2 = s1 s2.append("_Jack") print("s1=\(s1)") // s1=Tom print("s2=\(s2)") // s2=Tom_Jack
-
例二:數(shù)組
var a1 = [1,2,3] var a2 = a1 a2.append(4) print("a1=\(a1)") // a1=[1, 2, 3] print("a2=\(a2)") // a2=[1, 2, 3, 4]
-
例三:字典
var d1 = ["max":10,"min":20] var d2 = d1 d1["other"] = 12 d2["max"] = 30 print("d1=\(d1)") // d1=["other": 12, "max": 10, "min": 20] print("d2=\(d2)") // d2=["max": 30, "min": 20]
提示:
- 在 Swift 標(biāo)準(zhǔn)庫中秧秉,為了提升性能,String衰抑、Array象迎、Dictionary、Set采取了Copy On Write 的技術(shù)
- 比如:僅當(dāng)有 “寫” 操作的時候呛踊,才會真正執(zhí)行copy操作砾淌;對于標(biāo)準(zhǔn)庫值類型的賦值操作,Swift能確保最佳性能谭网,所以沒必要為了保證最佳性能來避免賦值
- 建議:沒必要做修改的盡量定義成
let
-
-
3.6汪厨、值類型的賦值操作(結(jié)構(gòu)體、枚舉)
struct Point{ var x:Int var y:Int } var p1 = Point(x: 10, y: 20) print(p1) // Point(x: 10, y: 20) p1 = Point(x: 11, y: 22) print(p1) // Point(x: 11, y: 22)
-
3.7愉择、引用類型(類)
-
引用賦值給
let
劫乱、var
或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份锥涕,類似于一個文件的替身(快捷方式衷戈,鏈接),指向的是同一個文件层坠。屬于淺拷貝(shallow copy)class Size{ var width:Int var height:Int init(width:Int,height:Int) { self.width = width self.height = height } } var s1 = Size(width: 10, height: 20) var s2 = s1 s2.width = 11 s2.height = 22 print("s1.width=\(s1.width)") print("s1.height=\(s1.height)")
打印結(jié)果:
s1.width=11 s1.height=22
-
-
3.8殖妇、引用類型的賦值操作
class Size{ var width:Int var height:Int init(width:Int,height:Int) { self.width = width self.height = height } } var s1 = Size(width: 10, height: 20) s1 = Size(width: 11, height: 22)
-
3.9、值類型和引用類型的
let
struct Point{ var x:Int var y:Int }
class Size{ var width:Int var height:Int init(width:Int,height:Int) { self.width = width self.height = height } }
-
字符串和數(shù)組的使用
-
-
3.10破花、嵌套類型
struct Poker { enum Suit:Character { case spades = "?",hearts = "?",diamonds = "?",clubs = "?" } enum Rank:Int { case two = 2,three,four,five,six,even case jack,queen,king,ace } }
嵌套的使用: 打印是 hearts 的原始值
?
print(Poker.Suit.hearts.rawValue)
更多的使用
var suit = Poker.Suit.spades suit = .diamonds var rank = Poker.Rank.five rank = .king
-
3.11谦趣、枚舉疲吸、結(jié)構(gòu)體、類在其內(nèi)部都可以函數(shù)蔚润,稱其:方法
class Size { var width:Int = 1 var height:Int = 2 func test() { ...... } } struct Size { var width:Int = 1 var height:Int = 2 func test() { ...... } } enum Date{ case digit(year:Int,month:Int,day:Int) case string(String) func test() { ...... } }
提示:方法是不占用實例對象內(nèi)存的
-
3.12磅氨、下面值類型與引用類型 內(nèi)存結(jié)構(gòu)如下
struct Point { var x: Int var b1: Bool var b2: Bool var y: Int } var p = Point(x: 10, b1: true, b2: true, y: 20) MemoryLayout<Point>.stride // 24 分配占用的內(nèi)存空間大小 MemoryLayout<Point>.size // 24 實際用到的空間大小 MemoryLayout<Point>.alignment // 8 對齊參數(shù) class Size { var width: Int var b1: Bool var b2: Bool var height: Int init(width: Int, b1: Bool, b2: Bool, height: Int) { self.width = width self.b1 = b1 self.b2 = b2 self.height = height } } var s = Size(width: 10, b1: true, b2: true, height: 20) MemoryLayout<Size>.stride // 8 分配占用的內(nèi)存空間大小 MemoryLayout<Size>.size // 8 實際用到的空間大小 MemoryLayout<Size>.alignment // 8 對齊參數(shù)
四、結(jié)構(gòu)體和類的區(qū)別
- 4.1嫡纠、結(jié)構(gòu)體通常用來定義一組數(shù)據(jù)類型的組合,而類則是面向?qū)ο蟮难佣模渲谐丝梢远x屬性還可以定義方法除盏。在Swift里面類也可以,區(qū)分不太明顯挫以。
- 4.2者蠕、值類型和引用類型的區(qū)別
它們最大的區(qū)別在于進(jìn)行數(shù)據(jù)傳遞的時候,值類型總是復(fù)制掐松,值類型有數(shù)據(jù)傳遞踱侣,原來的實例會被復(fù)制一份,修改新的實例不能修改原始的實例大磺;引用類型不會被復(fù)制抡句,傳遞是引用,修改是自身杠愧,引用類型是通過引用計數(shù)來管理其生命周期的待榔。 - 4.3、在結(jié)構(gòu)體里面開發(fā)者不需要自己提供構(gòu)造方法流济,結(jié)構(gòu)體會自己生成一些構(gòu)造方法锐锣,而類則要求開發(fā)者自己提供構(gòu)造方法。
- 4.4绳瘟、結(jié)構(gòu)體不可以被繼承雕憔,類可以進(jìn)行繼承。
- 4.5糖声、內(nèi)存所處位置
- 結(jié)構(gòu)體的具體在哪是由創(chuàng)建的位置決定的斤彼,比如在func內(nèi)定義的結(jié)構(gòu)體是在棧空間姨丈,func是在棾┳浚空間,func在哪里是無所謂的
- 類不管在哪里創(chuàng)建蟋恬,對象的內(nèi)存(如:類Ponit())空間一定在堆空間,因為會調(diào)用 alloc/malloc 等翁潘;指針變量(如:point = Ponit())的內(nèi)存可能在其他的地方,
擴展:在對值類型進(jìn)行比較的時候歼争,應(yīng)使用等號運算符
==
拜马;對引用類型進(jìn)行比較操作渗勘,應(yīng)使用等同運算符===
,如下代碼class TextClass { } var text1 = TextClass() var text2 = text1 //比較結(jié)果是 true text1 === text2