美文网首页Swift 专栏
Swift5.0 - day3-可选项、结构体、类

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

作者: IIronMan | 来源:发表于2019-06-25 17:34 被阅读21次

一、可选项

  • 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 变量

      可以使用 lldb 命令来查看:`frame variable -R` 或者 `fr v -R` 查看区别
    • 多重可选项举例二

      var num1:Int? = nil
      var num2:Int?? = num1
      var num3:Int?? = nil
      
      print(num1 == num3)
      
      可以使用 lldb 命令来查看

二、结构体

  • 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) 代码 可以传入所有成员的值,以初始化所有成员

  • 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()
      }
      
      上图是针对 64bit 环境
  • 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

  • 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、引用类型(类)

    • 引用赋值给 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
      
      引用类型
  • 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、下面值类型与引用类型 内存结构如下

    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 对齐参数
    

相关文章

网友评论

    本文标题:Swift5.0 - day3-可选项、结构体、类

    本文链接:https://www.haomeiwen.com/subject/hdeuqctx.html