美文网首页Swift
Swift基础语法(四)枚举和可选项的认识

Swift基础语法(四)枚举和可选项的认识

作者: iOS之文一 | 来源:发表于2022-02-11 13:36 被阅读0次

    Swift基础语法文章汇总

    本文介绍枚举类型和可选项的认识和使用

    主要内容:

    1. 枚举的简单使用
    2. 枚举的内存计算
    3. 可选项的简单使用

    1、枚举的简单使用

    1.1 基本定义

    代码:

    //1、定义一个枚举类型
    enum Dir1 : String{
        case north
        case south
        case east
        case west
    }
    
    //2、简写
    enum Dir2{
        case north,south,east,west
    }
    
    //3、使用
    let north = Dir1.north.rawValue
    print("wy:\(north)")//wy:north
    

    说明:

    • 注意书写方式,enum表示枚举
    • 每一项可以用case来表示
    • 也可以简写,只写一个case,后面的成员用,隔开

    注意:

    • 可以确定类型,也可以不确定
    • 可以直接获取每个成员的值

    1.2 关联值

    有时将枚举的成员值跟其他类型的值关联存储在一起,需要让成员值来标识关联值是哪一个成员
    代码:

    //关联值
    enum Date {
        case digit(year: Int,month: Int,day: Int)
        case string(String)
    }
    
    //可以给枚举成员赋值
    var date = Date.digit(year: 2022, month: 2, day: 10)
    //此处date的枚举类型已经确定,所以可以直接使用.string(),而不需要加上Date进行说明
    date = .string("2022-02-10")
    
    /*
     1、此处通过传入的date得知枚举类型,所以在判断时可以直接使用.digit()/.string()
     2、在switch判断时,可以获取到其中的关联值进行使用
     */
    switch date {
    case .digit(let year,let month,let day):
        print(year,month,day)
    case let .string(value):
        print(value)
    }
    

    说明:

    • 在定义枚举的成员中有两个成员,一个是digit,一个是string类型,两种表示日期的方式
    • 使用关联值在成员中不仅可以表示它是哪一种表示类型,还可以直接表示这个值的具体数据
    • 比如成员后面加上(),()里面加上数据的数据类型,之后在外界使用这个成员的时候就可以直接赋值了
    • 在使用时,可以直接获取到成员的具体值

    注意:

    • 区别于其他语言的枚举值只能区分不同类型,这里的枚举值可以关联这个成员表示的具体数据
    • 枚举的成员值和枚举值是关联起来的,这样就可以通过成员值来查找枚举值
    • 如果一个枚举变量已经确定了枚举类型,那么就可以直接使用.digit()/.string()来赋值

    1.3 原始值

    枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做原始值,也就是这个成员所对应的值不会被外界赋值。

    代码:

    //1、原始值:定义时赋值
    enum Grade : String {
        case perfect = "A"
        case great = "B"
        case good = "C"
        case bad = "D"
    }
    print(Grade.perfect.rawValue)//A
    
    //如果是数值类型,默认是有顺序的数值
    enum Season : Int {
        case spring = 1 ,summer, autumn = 4, winter
    }
    print(Season.summer.rawValue)//2
    

    说明:

    • 此处是在定义时赋一个原始值,以后在外界使用时就一直是这个原始值,而且外界无法更改
    • 在获取原始值需要使用rawValue
    • 如果是字符串,则原始值为字符串成员本身
    • 如果是数值,则按顺序递增,并且第一个成员为0

    隐式原始值

    //2、原始值:隐式原始值
    //如果是字符串类型,则默认值是其本身
    enum Dir3 : String{
        case north
        case south
        case east
        case west
    }
    
    //等价于
    enum Dir4 : String{
        case north = "north"
        case south = "south"
        case east = "east"
        case west = "west"
    }
    

    说明:

    • 如果没有在定义时赋原始值,也是有默认原始值的
    • 如果值是字符串类型,这个默认原始值就是其成员的字符串本身
    • 如果值是Int类型,这个默认原始值就是数值

    注意:

    • 原始值当不显式赋值时,会有其默认原始值

    1.4 递归枚举

    代码:

    //递归枚举,声明到enum前
    indirect enum ArithExpr {
        case number(Int)
        case sum(ArithExpr,ArithExpr)
        case difference(ArithExpr,ArithExpr)
    }
    
    //递归枚举,声明到case前
    enum ArithExpr {
        case number(Int)
        indirect case sum(ArithExpr,ArithExpr)
        indirect case difference(ArithExpr,ArithExpr)
    }
    
    //定义枚举变量,可以直接通过number来定义,也可以通过sum/difference递归定义
    let ten = ArithExpr.number(10)
    let five = ArithExpr.number(5)
    let sum = ArithExpr.sum(ten, five)
    let difference = ArithExpr.difference(ten, five)
    
    //取出枚举变量中的数据进行加减运算
    //需要注意的是:如果传入sum/difference需要递归取出number值
    func calculate(_ expr: ArithExpr) -> Int {
        switch expr {
        case let .number(value):
            return value
        case let .sum(left, right):
            return calculate(left) + calculate(right)
        case let .difference(left, right):
            return calculate(left) - calculate(right)
        }
    }
    
    calculate(ten)//10
    calculate(five)//5
    calculate(sum)//15
    calculate(difference)//5
    

    说明:

    • 在ArithExpr枚举中的sum和difference中关联值也是ArithExpr枚举,这就是递归枚举
    • 在枚举前加上indirect就表示该枚举可以被成员递归使用
    • 也可以简写成第二种方式,只在需要使用递归枚举的成员前加上indirect
    • 在下面的使用中就可以看到使用sum时直接传入了five和four两个枚举
    • 之后在使用时,可以获取到number中的具体值来计算。
    • 在calculate中也需要递归取出number值才能计算。

    2、枚举内存大小

    原始值和枚举值计算大小的方式是不一样的。

    关于内存对齐可以看我的另一篇博客苹果的内存对齐原理

    2.1 原始值内存计算

    代码:

    enum Dir1 : String{
        case north
        case south
        case east
        case west
    }
    
    MemoryLayout<Dir1>.stride//分配空间大小
    MemoryLayout<Dir1>.size//实际占用空间大小
    

    运行结果:

    原始值内存.png

    说明:

    • 枚举中所有原始值占用的内存大小是1
    • 这是因为原始值都是通过序号来存储的,并没有具体值存储在枚举中,枚举中仅存储有序号

    2.2 关联值内存计算

    代码:

    //关联值
    enum Date {
        case digit(year: Int,month: Int,day: Int)
        case string(String)
    }
    
    //可以给枚举成员赋值
    var date = Date.digit(year: 2022, month: 2, day: 10)
    //此处date的枚举类型已经确定,所以可以直接使用.string(),而不需要加上Date进行说明
    date = .string("2022-02-10")
    
    MemoryLayout.stride(ofValue: date)//分配空间大小
    MemoryLayout.size(ofValue: date)//实际占用空间大小
    

    运行结果:

    关联值内存大小.png

    说明:

    • 枚举中关联值占用的内存大小是数据类型的大小加上一个字节
    • 关联值是直接存储在枚举中的,因此需要加上成员的数据类型的大小
    • 并且还有序号来关联这个关联值。因此还必须加上一个字节
    • 此处digit占有24个字节,string占有8个字节,但因为他们是互斥关系,所以选用最大的24个字节。
    • 枚举必须有一个字节用来存储成员的序号,因此是24+1= 25个字节
    • 而通过内存对齐,需要分配32个字节

    2.3 枚举内存计算

    代码:

    enum Password {
        case number(Int,Int,Int,Int)
        case other
    }
    
    MemoryLayout<Password>.stride//分配空间大小
    MemoryLayout<Password>.size//实际占用空间大小
    MemoryLayout<Password>.alignment//对齐参数
    

    运行结果:

    运行结果.png

    说明:

    • 枚举Password共有两个成员number和other
      • number是关联值,关联值直接存储在枚举中,因此number占用大小为32个字节
      • other是原始值,原始值在枚举中只有序号,不占用实际空间
    • 可以直接计算类型的内存空间,也可以计算变量的数据类型的内存空间
      • 可以通过MemroyLayout<Password>方式来获取数据类型的占用内存空间大小
      • MemroyLayout.size(ofValue:pwd)这种方式可以获取pwd变量的数据类型的占用内存空间大小
    • 有两种空间计算
      • 第一种是stride,这种计算得到的是系统分配的空间大小
      • 第二种是size,这种计算得到的是实际占用的空间大小

    注意:

    • 着重注意原始值和关联值的大小计算是不一样的,关联值存储在枚举中,原始值存储并非存储在枚举中,而是通过序号来索引的
    • 所有原始值占用的大小合起来是1,关联值的大小是数据类型的大小

    2.4 总结

    • 关联值存储在枚举中,会占用枚举变量的内存,原始值不占用枚举变量的内存
    • 枚举必须有一个字节用来存储枚举成员的序号
    • 枚举的关联值占用内存大小选用最大成员的内存大小
    • 分配空间大小是8字节对齐

    3、可选项

    可选项,也叫可选类型,可选项的目的就是允许将值设置为nil,因此如果想要获取或判断的值可能包含nil,那么它就应该用可选项来处理。
    在类型的后面加上?就可以设置为可选类型。

    3.1 基本使用

    代码:

    //1、可选项的认识
    var name: String? = "wy"
    //可选项可以设置为nil
    name = nil
    
    //2、可选项如果没有赋初始值,则说明是nil
    var age: Int?
    age = 10
    age = nil
    

    说明:

    • 可选类型的数据可以设置为nil
    • 正常情况下数据类型是没有默认值的,在使用前必须要赋值
    • 而如果设置为可选项,就有默认值,默认值就是nil

    理解:

    • 可选项可以理解为是一个盒子,如果赋值,里面就装有一个数据,强制解包就是获取盒子内的数据。当然此时盒子内仍然有数据
    • 如果不为nil,那么盒子里装的是:被包装类型的数据
    • 如果为nil,那么它是个空盒子

    3.2 强制解包

    强制解包就是将可选项的变量所包含的数据取出来

    代码:

    //3、强制解包取出盒子中的数据(通过!来强制解包)
    var age: Int? = 10
    var ageInt: Int = age!
    ageInt += 10
    

    说明:

    • 强制解包就是将可选项的变量所包含的数据取出来
    • 通过在可选项变量后加上!就可以将其解包出来

    注意:

    • 如果对值为nil的可选项(空盒子)进行解包,会报错,因为Swift正常类型中是没有nil这个概念的
    • 此时可选项的变量仍然包含有该数据,并未删除

    3.3 可选项绑定

    正常使用方式:

    /*
     正常判断方式
     1、number为可选项,因此可以判断是否为nil
     2、在获取数据时需要强制解包
     */
    let number = Int("123")
    if number != nil {
        print("wy:字符串转换整数成功:\(number!)")
    } else {
        print("wy:字符串转换中整数失败")
    }
    

    说明:

    • 这里可以看到因为Int("123")强行将字符串转换为Int类型,这里有可能转换失败,所以这个操作就有可能返回nil,因此这里返回的应当是可选类型
    • number得到的就是一个可选类型,因此这里可以用nil判断,如果不为nil,就可以解包获取包含的数据

    注意:

    • 除了我们认为的设置为可选项,还有很多情况是系统自动设置为可选项的
    • 比如在这里强转类型后有可能转换失败,返回nil,所以获取到的数据应当是可选项。
    • 后续我们在返回一个数据时也应当这样来做,无法判断是否成功,就可以返回为可选项
    • 可选项在判断语句中使用逗号隔开,不能用&&

    可选项绑定方式:

    //可选项绑定方式
    /*
     1、可以直接在判断条件中获取可选项
     2、特别要注意的是,此时会自动解包,因此直接使用number,而无需强制解包
     */
    if let number = Int("123") {
        print("wy:字符串转换整数成功:\(number)")
    } else {
        print("wy:字符串转换中整数失败")
    }
    

    说明:

    • 也可以直接写到if语句中
    • 使用可选项绑定来判断可选项是否包含值
    • 如果包含就会自动解包,把解包后的值赋给一个变量或常量,并返回True,因此这里的Number就是解包后的数据
    • 而且这个number只能作用在true的作用域,如果是不包含值根本不会自动解包,所以number并不会作用在false中

    判断条件中:

    if let first = Int("4"),let second = Int("42"), first < second && second < 100 {
        print("\(first) < \(second) < 100")
    }
    
    //等价于:
    if let first = Int("4"){
        if let second = Int("42") {
            if first < second && second < 100 {
                print("\(first) < \(second) < 100")
            }
        }
    }
    

    3.4 空合并运算符

    合并方式:
    a ?? b

    • a 是可选项
    • b 是可选项 或者 不是可选项
    • b 跟 a 的存储类型必须相同
    • 如果 a 不为nil,就返回 a
    • 如果 a 为nil,就返回 b
    • 如果 b 不是可选项,返回 a 时会自动解包

    代码:

    //5、空合并运算符
    //a和b都是可选项
    let a: Int? = 1
    let b: Int? = 2
    let c = a ?? b
    print(c)//Optional(1)
    
    //a和b都是可选项,a是nil
    let a: Int? = nil
    let b: Int? = 2
    let c = a ?? b
    print(c)//Optional(2)
    
    //a和b都是可选项,a和b都是nil
    let a: Int? = nil
    let b: Int? = nil
    let c = a ?? b
    print(c)//nil
    
    //a是可选项,b不是可选项
    let a: Int? = 1
    let b: Int = 2
    let c = a ?? b
    print(c)//1
    
    //a是可选项,b不是可选项,且a是nil
    let a: Int? = nil
    let b: Int = 2
    let c = a ?? b
    print(c)//2
    

    说明:

    • 只有当b不是可选项的是,返回a时才会自动解包
    • 如果b是可选项,返回a或b的时候并不会自动解包
    • a和b的存储类型一定要一样

    注意:

    3.5 隐式解包

    可以直接使用!实现隐式解包

    代码:

    //6、隐式解包,num1既可以当做直接数,也可以当做可选项判断是否为nil
    let num1: Int! = 10
    let num2: Int = num1
    if num1 != nil {
        print(num1+6)
    }
    

    说明:

    • 在某些情况下,可选项一旦被设定值之后,就会一直拥有值
    • 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
    • 可以在类型后面加个感叹号 ! 定义一个可以隐式解包的可选项

    3.6 多重可选项

    可选项可以嵌套使用

    代码:

    //7、多重可选项
    var num1: Int? = 10
    var num2: Int?? = num1
    var num3: Int?? = 10
    

    说明:

    • 可选项可以包装数据类型,也可以包装可选项
    • 并且这里num3使用??也是多重可选项,因此先对10进行包装,再将其包装到num3上

    注意:

    • 如果是nil,则不管几层包装都是空盒子

    3.7 字符串插值

    可选项在字符串插值或直接打印时,编译期会发出警告

    警告:

    警告.png

    代码:

    //8、字符串插值
    var wyAge: Int? = 10
    print("My age is \(wyAge!)")
    print("My age is \(String(describing: wyAge))")
    print("My age is \(wyAge ?? 0)")
    

    说明:

    • 可选项只是一个盒子,我们所需要的是盒子里的数据,所以编译期在发现直接打印盒子时就会报错
    • 我们可以取出盒子中的数据再进行打印

    注意:

    • 如果是nil,则不管几层包装都是空盒子

    相关文章

      网友评论

        本文标题:Swift基础语法(四)枚举和可选项的认识

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