一、可选项
-
1.1、可选项(Optional)
一般也叫可选类型,它允许将值设为 nil
-
在类型名称后面加个问号 ❓来定义一个可选型
var name: String? = "王三" name = nil
提示:如果定义如下
var name:String? 等价于 var name:String? = nil
可选型其实也就是要么
有值
,要么是nil
默认的情况下 可选型的值在没有复制的情况下使用,值是 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
,那么盒子里面装的是:被包装类型的数据var age:Int? // 空盒子 nil age = 10 // 盒子有内容 Optional(10) age = nil // 空盒子 nil
-
(2)、如果想要从可选项里面取出被包装的数据(将盒子里面的东西取出来),需要用
感叹号
❗️ 进行强制解包let age:Int? = 10 let ageInt:Int = age! print(ageInt + 10)
提示:如果age 为nil 进行解包会报错:
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("字符串转换整数成功:\(number!)") }else{ print("字符串转换整数失败") }
提示:
Int("521")
可以把里面的字符串转换为整数,但是如果里面是abc
,那么就会转换失败为:nil
-
-
1.4、可选项绑定 (Optional Binding)
- 可以用 可选项 绑定来判断可选项是否包含值
如果包含就自动解包,把它赋值为一个临时变量(
let
)或者变量(var
),并返回true
,否则返回false
-
举例一
if let number = Int("123"){ print("字符串转换整数成功:\(number)") // number 是强制解包之后的 Int 值 // number 的作用域仅限于这个大括号 }else{ print("字符串转换整数失败") }
-
举例二
enum Season:Int{ case spring = 1,summer,autumn,winter } if let season = Season(rawValue: 8){ switch season { case .spring: print("春天") default: print("其他季节") } }else{ print("解包失败") }
结果是:解包失败
- 可以用 可选项 绑定来判断可选项是否包含值
-
1.5、等价写法
-
写法一
if let num1 = Int("10") { if let num2 = Int("30") { if num1 < num2 && num2 < 100 { print("\(num1)<\(num2)<100") } } }
打印结果:10<30<100
-
写法二
if let num1 = Int("10"), let num2 = Int("30"), num1 < num2 && num2 < 100{ print("\(num1)<\(num2)<100") }
打印结果:10<30<100
提示:牵涉到可选项的绑定的时候,不能用
&&
来连接,只能用,
来连接
-
-
1.6、while选项中使用可选项绑定
遍历数组,将遇到的正数都加起来,如果遇到负数或者非数字,停止遍历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 语句实现登录
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 ..... // 退出当前的作用域 // return、berak、continue、throwerror }
- 当 guard 语句条件为 false 时,就会执行大括号里面的代码
- 当 guard 语句条件为 true 时,就会跳过 guard 语句
- guard 语句 特别适合用来 “提前退出”
-
当使用 guard 语句 进行可选项绑定的时候,绑定的常量(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) 登录") }
调用
// 用户名:jack 密码:123456 登陆ing login(["username" : "jack", "password" : "123456"]) // 请输入用户名 login(["password" : "123456"]) // 请输入密码 login(["username" : "jack"])
-
-
1.12、隐式解包(Implicitly UnwrappedOptional)
在某些情况下,可选项一旦被设定值之后,就会一直拥有值
在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
-
可以在类型后面加个
感叹号
❗️,定义一个隐式解包的可选项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
查看区别
frame
内存布局variable
变量
-
多重可选项举例二
var num1:Int? = nil var num2:Int?? = num1 var num3:Int?? = nil print(num1 == num3)
-
二、结构体
-
2.1、结构体:在 Swift 标准库库中,绝大多数的公开类型都是结构体(值类型),而枚举和类(引用类型)只占很小的一部分,比如:Bool、Int、Double、String、 Array、Dictionary 等常见类型都是结构体,其中可以定义方法和属性,但其不像类一样具有继承的特性。
struct Date { var year: Int var month: Int var day: Int } var date = Date(year: 2019, month: 6, day: 24)
所有的结构体都有一个编译器自动生成的初始化器(initialier 初始化方法、构造器、构造方法),在2.1、中的调用
var date = Date(year: 2019, month: 6, day: 24)
代码 可以传入所有成员的值,以初始化所有成员(存储属性,英文名:Sored Property) -
2.2、结构体的初始化器
-
编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有的成员都有初始值,如下
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()
结论:可以编译通过,因为 可选项都有个默认值 nil,因此可以编译通过
-
-
2.3、自定义初始化器
-
一旦在定义结构体的时候定义了初始化器,编译器就不会再帮它自动生成其他初始化器
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、窥探初始化器的本质,下面的两段代码等效
-
第 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、结构体的内存结构
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
结构体所占用的内存空间:所有存储属性内存空间之和对齐之后的结果
三、类
-
3.1、类的定义和结构体类似,但编译体并没有为类自动生成可以传入成员值的初始化器,如下三段代码
-
代码段一
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、类的初始化器
如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参数的初始化器
-
成员的初始化是在这个初始化器中完成的
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、结构体与类的本质区别
-
结构体是值类型(枚举也是值类型),类是引用类型(指针类型)
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 个字节指向类型的信息,后8个是指向引用计数 ,最后面的16个是来存储成员变量的
-
-
3.4、值类型
-
值类型赋值给
var
、let
或者给函数传参,是直接将所有的内容拷贝一份,类似于对文件进行 copy、paste操作,产生了全新的文件副本。属于深拷贝(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)") }
打印结果是: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
-
例二:数组
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 标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write 的技术
- 比如:仅当有 “写” 操作的时候,才会真正执行copy操作;对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没必要为了保证最佳性能来避免赋值
- 建议:没必要做修改的尽量定义成
let
-
-
3.6、值类型的赋值操作(结构体、枚举)
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
或者给函数传参,是将内存地址拷贝一份,类似于一个文件的替身(快捷方式,链接),指向的是同一个文件。属于浅拷贝(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)")
打印结果:
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 } }
-
字符串和数组的使用
-
-
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、枚举、结构体、类在其内部都可以函数,称其:方法
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() { ...... } }
提示:方法是不占用实例对象内存的
-
3.12、下面值类型与引用类型 内存结构如下
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 分配占用的内存空间大小 MemoryLayout<Point>.size // 24 实际用到的空间大小 MemoryLayout<Point>.alignment // 8 对齐参数 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 分配占用的内存空间大小 MemoryLayout<Size>.size // 8 实际用到的空间大小 MemoryLayout<Size>.alignment // 8 对齐参数
四、结构体和类的区别
- 4.1、结构体通常用来定义一组数据类型的组合,而类则是面向对象的,其中除了可以定义属性还可以定义方法。在Swift里面类也可以,区分不太明显。
- 4.2、值类型和引用类型的区别
它们最大的区别在于进行数据传递的时候,值类型总是复制,值类型有数据传递,原来的实例会被复制一份,修改新的实例不能修改原始的实例;引用类型不会被复制,传递是引用,修改是自身,引用类型是通过引用计数来管理其生命周期的。 - 4.3、在结构体里面开发者不需要自己提供构造方法,结构体会自己生成一些构造方法,而类则要求开发者自己提供构造方法。
- 4.4、结构体不可以被继承,类可以进行继承。
- 4.5、内存所处位置
- 结构体的具体在哪是由创建的位置决定的,比如在func内定义的结构体是在栈空间,func是在栈空间,func在哪里是无所谓的
- 类不管在哪里创建,对象的内存(如:类Ponit())空间一定在堆空间,因为会调用 alloc/malloc 等;指针变量(如:point = Ponit())的内存可能在其他的地方,
扩展:在对值类型进行比较的时候,应使用等号运算符
==
;对引用类型进行比较操作,应使用等同运算符===
,如下代码class TextClass { } var text1 = TextClass() var text2 = text1 //比较结果是 true text1 === text2