枚举

103 阅读9分钟

枚举

OC中的枚举本质就是整型,Swift中的枚举就不太一样了,

//enum Direction{
//    case north
//    case south
//    case east
//    case west
//}
enum Direction{
    case north, south, east, west
}
var dir = Direction.north
dir = Direction.east
dir = .north
print(dir)
switch dir {
case .north:
    print("north")
case .south:
    print("south")
case .east:
    print("east")
case .west:
    print("west")
}

以上是enum的写法,switch必须保证处理了所有情况否则就会报错

有时会将枚举的成员值跟其他类型的关联存储在一起,会非常有用。如果该类型有固定的取值类型会非常适合用枚举

枚举的原始值

枚举成员可以使用相同类型的默认值先关联,这个默认值叫做原始值

//关联的是Character类型
enum PokerSuit: Character{
    case heart = "♥️"
    case spade = "♠️"
    case diamond = "♦️"
    case club = "♣️"
}

如果枚举值的类型是Int,String,Swift会自动分配原始值,按照顺序累加如果在某个枚举指定具体的数值在该数值的基础上累加(针对Int)

枚举递归:

indirect enum AirthExpr {
    case number(Int)
    case sum(AirthExpr, AirthExpr)
    case difference(AirthExpr, AirthExpr)
}

在没枚举中自己引用自己的类型成为递归枚举必须使用indirect这个关键字否则会报错,以上代码也可以写成下方这种,在需要枚举的时候使用indirect关键字

enum AirthExpr{
    case number(Int)
    indirect case sum(AirthExpr,AirthExpr)
    indirect case diffeeence(AirthExpr,AirthExpr)
}

一般情况下为了方便会直接使用上方的代码

enum AirthExpr{
    case number(Int)
    indirect case sum(AirthExpr,AirthExpr)
    indirect case diffeeence(AirthExpr,AirthExpr)
}
let five = AirthExpr.number(5)
let four = AirthExpr.number(4)
let two = AirthExpr.number(2)
let sum = AirthExpr.sum(five, four)
let difference = AirthExpr.diffeeence(sum, two)
​
func calculate(_ expr: AirthExpr) -> Int{
    switch expr {
    case .number(let value):
        return value
    case .sum(let value1, let value2):
       return calculate(value1) + calculate(value2)
    case .diffeeence(let value1, let value2):
        return calculate(value1) - calculate(value2)
    }
}
print(calculate(difference))
print(calculate(sum))
​

image-20220107231627604

该例子比较生动的描绘递归枚举的用处,仅作参考!

MemoryLayout

可以使用MemoryLayout获取数据类型占据的大小

我们使用int来尝试一下

var age = 10
print(MemoryLayout<Int>.size)
print(MemoryLayout<Int>.stride)
print(MemoryLayout<Int>.alignment)
​

image-20220107232200089

以上效果并不是很明显,我们找个特殊的结构来看MemoryLayout

//MemoryLayout
enum Passord{
    case number(Int,Int,Int,Int)
    case other
}
var paw = Passord.number(1, 2, 3, 4)
//var age = 10
print("num =" , MemoryLayout.size(ofValue: paw))
print("num =" , MemoryLayout.stride(ofValue: paw))
print("num =" , MemoryLayout.alignment(ofValue: paw))
paw = .other
print("other =" , MemoryLayout<Passord>.size)
print("other =" , MemoryLayout<Passord>.stride)
print("other =" , MemoryLayout<Passord>.alignment)

image-20220107233211025

同一个实例变量虽然不会动态改变内存大小,所以无论是取值是number还是other内存并不会发生变化。

关联值: number中存储的值叫做关联值,关联值的特点就是将外部传入的关联值直接存储在枚举变量里面,关联值是有外部传入的,每次传入都不一样,每一个枚举变量需要有自己的内存空间来存储外部传入的关联值。

原始值: 原始值是固定死的,会跟成员永远绑定在一起

enum Season{
    case spring, summer, autumn, winter
}
print("Season =" , MemoryLayout<Season>.size)
print("Season =" , MemoryLayout<Season>.stride)
print("Season =" , MemoryLayout<Season>.alignment)
​

image-20220110233505499

单纯的枚举只要一个字节就能表示清楚所有的枚举值.

enum Season: Int{
    case spring = 255, summer, autumn, winter
}
var s = Season.spring
s = .summer
s = .autumn

image-20220111000951443

Int类型占8个字节,但是打印出来的枚举大小是1个字节,说明原始值并不存储在枚举变量中,而且原始值是跟枚举值1对一绑定的,并且一开始就固定死的,没有必要每一个枚举变量中都存储一次原始值造成内存浪费。如果枚举没有指定类型的时候是不允许拥有原始值的

或许有人会有疑问那么原始值会存在哪里呢,这个不需要太过于纠结,因为不存储也是可以的,可以尝试以下伪代码

enum Season: Int{
    case spring = 255, summer, autumn, winter
    func rawValue() ->Int{
        if self == 0 return 255
        if self == 1 return 256
    }
}

可选项

可选项,一般也叫可选类型,他允许将值设置为nil

image-20220111003651006

平时我们一般类型中不允许给空值,如果你声明为可选类型,那么你就可以将值设置为nil

在类型后面加个?号,来定义一个可选项

var age:Int? //默认就是nil
age = 10
age = nil

以上代码是不会报错的,可选类型的默认值就是nil

var array = [1,2,5,7]
func get(_ index: Int) -> Int?{
    if index < 0 || index >= array.count{
        return nil
    }
    return array[index]
}
print(get(1))
print(get(-1))
print(get(4))

image-20220111004442335

返回回来的值是Optional类型

强制解包

可选类型是对其他类型的一层封装,可以将它理解为一个盒子

如果不为nil,盒子里装的是:被包装的数据类型

如果要从可选类型中取出被包装的数据(将盒子里的东西取出来),需要使用 感叹号! 进行强制解包

var age: Int? = 10 //默认就是nil
var ageInt = age!
ageInt += 10
print(ageInt)

image-20220111005137910

image-20220111005118343

如果直接用age进行计算,那么会报错,因为age不是Int类型而是一个盒子。

image-20220111005438142

如果对值为空的的可选项(空盒子)进行强制解包,将会产生运行时错误。所以我们需要做一件事情,判断可选项是否包含值(也就是是不是空值)可以查看下面这个例子

let number = Int("123")
if number != nil{
    print("字符串转换整数成功:(number!)")
}else {
    print("字符串转换整数失败:")
}

可选项绑定

可以使用可选项绑定来判断可选项是否包含值。

如果包含就自动解包,把值赋给一个临时的变量(let)或者变量(var),并返回true,否则返回false

if let number = Int("123"){
    print("字符串转换整数成功:(number)")
//    number的作用域仅限于这个大括号
//    number是强制解包之后Int值
}else {
    print("字符串转换整数失败:")
}
enum Season: Int{
    case spring = 1, summer, autumn, winter
}
if let season = Season(rawValue: 6){
    switch season {
    case .spring:
        print("the season is spring")
    case .summer:
        print("the season is summer")
    case .autumn:
        print("the season is autumn")
    case .winter:
        print("the season is winter")
    }
}else{
    print("no such season")
}

image-20220111222331640

while循环中使用可选项绑定

// 遍历数组,将遇到的正数都加起来,如果遇到负数或者非数字,停止遍历var strs = ["10", "20", "abc", "-20","30"]
var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
sum += num
index += 1
}
print(sum)

image-20220123105107656

空合并运算符??

a ?? b
  • a是可选项
  • b是可选项或者不是可选项
  • b跟a可选类型必须相同
  • 如果a不为nil那么就返回a
  • 如果b不是可选项,返回a时会自动解包
  • ?? 返回的类型取决于b
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? , nillet a: Int? = 1
let b: Int = 2
let c = a ?? b // c是Int , 1let a: Int? = nil
let b: Int = 2
let c = a ?? b // c是Int , 2let a: Int? = nil
let b: Int = 2
// 如果不使用??运算符 代码会是下面这种写法
let c: Int
if let tmp = a { c = tmp
} else { c = b }
多个 ?? 一起使用
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

数据类型取决于最右边数据的类型

??跟if let配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
  print(c)
}
// 类似于if a != nil || b != nil
if let c = a, let d = b {
  print(c)
  print(d)
}
// 类似于if a != nil && b != nil
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
            }
        }
        login(["username" : "卿卿", "password" : "123456"]) // 用户名:卿卿 密码:123456 登陆ing
        login(["password" : "123456"]) // 请输入密码
        login(["username" : "卿卿"]) // 请输入用户名

image-20220123122645398

guard语句
guard 条件 else {
​
// do something....
​
退出当前作用域
​
// return、break、continuethrow error
​
} 
  • 当guard语句的条件为false时,就会执行大括号里面的代码
  • 当guard语句的条件为true时,就会跳过guard语句
  • guard语句特别适合用来“提前退出”
func login(_ info: [String : String]) {
  guard let username = info["username"] else {
  print("请输入用户名")
  return
}
guard let password = info["password"] else {
  print("请输入密码")
return
}
// if username ....
// if password ....
print("用户名:(username)", "密码:(password)", "登陆ing") }

当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用域中使用

隐式解包(自动解包)

  • 在某些情况下,可选项一旦被设定值之后,就会一直拥有值
  • 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
  • 可以在类型后面加个感叹号 ! 定义一个隐式解包的可选项
let num1: Int! = 10
let num2: Int = num1
if num1 != nil {
print(num1 + 6) // 16
}
if let num3 = num1 {
print(num3)
}
let num1: Int! = nil
// Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
let num2: Int = num1

如果Int! = nil 是一定会报错的

字符串插值

  • 可选项在字符串插值或者直接打印时,编译器会发出警告
var age: Int? = 10
print("My age is (age)")
  • 至少有3种方法消除警告
// 强制解包
print("My age is (age!)")
// My age is 10// 可选项描述
print("My age is (String(describing: age))")
// My age is Optional(10)// 空合并运算符,会根据最右边数据来决定会不会解包,很明显最右侧是一个Int类型
print("My age is (age ?? 0)")
// My age is 10

多重可选项

var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
print(num2 == num3) // true
var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1

多重可选项要注意盒子理论 ,当直接一眼看出你对我的这个盒子要放一个nil的时候,我直接就置为nil,但是你拿着个空盒子过来,我会存放成空盒子套空盒子

lldb指令

可以使用lldb指令 frame varibe -R 或者fr v -R查看区别

image-20220123130007874

可选项本质是枚举类型,有some和none两个case