Swift5.0 - day3-可选项、结构体、类

简介: Swift5.0 - day3-可选项、结构体、类

一、可选项



  • 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


image.png


  • (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 ?? (optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
  • public func ?? (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)


image.png

可以使用 lldb 命令来查看:frame variable -R 或者 fr v -R 查看区别

frame 内存布局 variable 变量


image.png


多重可选项举例二


var num1:Int? = nil
var num2:Int?? = num1
var num3:Int?? = nil
print(num1 == num3)


image.png

image.png


二、结构体



  • 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()


  • 代码段三



image.png

  • 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()
}


image.png


  • 前 8 个字节指向类型的信息,后8个是指向引用计数 ,最后面的16个是来存储成员变量的


  • 3.4、值类型
  • 值类型赋值给 varlet 或者给函数传参,是直接将所有的内容拷贝一份,类似于对文件进行 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


image.png

  • 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)


image.png

3.7、引用类型(类)

  • 引用赋值给 letvar 或者给函数传参,是将内存地址拷贝一份,类似于一个文件的替身(快捷方式,链接),指向的是同一个文件。属于浅拷贝(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


image.png


  • 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
}


image.png


class Size{
   var width:Int
   var height:Int
   init(width:Int,height:Int) {
          self.width = width
          self.height = height
   }
}

image.png


字符串和数组的使用


image.png

  • 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


目录
相关文章
|
1月前
|
存储 定位技术 Swift
Swift 中的枚举与结构体,包括它们的定义、特性、使用场景及示例
本文深入探讨了 Swift 中的枚举与结构体,包括它们的定义、特性、使用场景及示例。枚举适合表示有限的、离散的状态或选项,结构体则适用于具有特定属性和行为的数据结构。两者在存储方式、继承性和灵活性上有所不同,但在实际开发中常结合使用,以充分发挥各自优势。
42 3
|
1月前
|
存储 Swift 开发者
Swift 是一种现代编程语言,支持面向对象编程(OOP),包括类、对象、继承、多态等核心概念
Swift 是一种现代编程语言,支持面向对象编程(OOP),包括类、对象、继承、多态等核心概念。通过这些特性,Swift 能够帮助开发者构建结构清晰、可维护性强的代码。本文介绍了 Swift 中 OOP 的基本概念、高级特性和实际应用,并通过一个简单的 `Car` 类示例展示了如何在 Swift 中实现面向对象编程。
24 1
|
1月前
|
Swift 索引 容器
Swift 泛型-关联类
Swift 泛型-关联类
23 1
|
2月前
|
Swift
Swift 中 struct(结构体)和 class(类)的区别
【10月更文挑战第10天】理解 struct 和 class 的区别对于正确使用 Swift 语言进行编程非常重要。在实际开发中,需要根据具体的需求和场景来选择合适的数据类型,以充分发挥它们的优势,提高代码的质量和效率。
|
1月前
|
存储 Swift iOS开发
Swift 类
10月更文挑战第29天
14 0
|
1月前
|
Swift iOS开发
Swift 结构体
10月更文挑战第28天
31 0
|
7月前
Swift4.0判断本函数是否在其它类有相同的方法
Swift4.0判断本函数是否在其它类有相同的方法
46 0
|
7月前
|
安全 Swift 开发者
【Swift开发专栏】Swift类的继承与多态
【4月更文挑战第30天】Swift中的OOP聚焦于类继承与多态,提供代码复用和类型安全。继承通过`class`和冒号实现,子类继承父类属性和方法,支持单继承以降低复杂性。多态借助协议和类型兼容实现,允许统一处理不同类型的对象。继承用于构建复杂类,多态则使代码更通用、可扩展。理解并运用这些概念对Swift开发者至关重要。
62 0
|
7月前
|
存储 定位技术 Swift
【Swift 开发专栏】Swift 中的枚举与结构体
【4月更文挑战第30天】本文探讨了Swift中的枚举与结构体,包括它们的定义、用法及差异。枚举用于表示有限状态或选项,如游戏状态;结构体适合表示具有特定属性和行为的数据,如商品信息。两者均可定义属性和方法,作为函数参数。枚举以整数存储,不可继承,结构体按属性存储且可继承,更灵活。理解两者特点有助于提升编程效率。
41 0
|
7月前
|
存储 数据处理 Swift
在Swift中,类(class)和结构体(struct)
在Swift中,类(class)和结构体(struct)
78 1

热门文章

最新文章

相关课程

更多