Swift编程指南——枚举和可选项

297 阅读6分钟

1. 枚举

1.1 枚举的基本用法

enum Direction{
    case north
    case south
    case east
    case west
}

var dir = Direction.west
dir = Direction.east
dir = .north
print(dir)//north

switch dir {
case .north:
    print("north")
case .south:
    print("south")
case .east:
    print("east")
case .west:
    print("west")
}

1.2 关联值

  • 有时将枚举的成员值跟其他类型的值关联存储在一起
enum Score{
    case points(Int)
    case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
switch score {
case let .points(i):
    print(i,"points")
case let .grade(i):
    print("grade",i)
}// grade A
enum Date {
    case digit(year:Int,month:Int,day:Int)
    case string(String)
}
var date = Date.digit(year: 2011, month: 9, day: 10)
date = .string("2011-09-10")
switch date {
case .digit(let year,let month, let day):
    print(year,month,day)
case let .string(value):
    print(value)
}
enum Password{
    case number(Int,Int,Int,Int)
    case gesture(String)
}
var pwd = Password.number(3, 5, 7, 8)
pwd = .gesture("12369")
switch pwd {
case let .number(n1, n2, n3, n4):
    print("numer is ",n1,n2,n3,n4)
case let .gesture(str):
    print("gesture is",str)
}

必要时let也可以改为var

1.3 原始值

  • 枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值
enum PokerSuit : Character{
    case spade = "♠️"
    case heart = "♥️"
    case diamond = "♦️"
    case club = "♣️"
}

var suit = PokerSuit.spade
print(suit) // spade
print(suit.rawValue) //♠️
print(PokerSuit.club.rawValue) //♣️

注意:原始值不占用枚举变量的内存

1.4 隐式原始值

enum Direction2 : String{
    case north = "north"
    case south = "south"
    case east = "east"
    case west = "west"
} //等价于
enum Direction3 : String{
    case north,south,east,west
}
print(Direction2.north.rawValue,Direction3.north.rawValue)//north north
enum Season1 : Int{
    case spring,summer,autumn,winter
}
print(Season1.spring.rawValue,Season1.summer.rawValue,Season1.winter.rawValue) //0 1 2 3
enum Season2 : Int{
    case spring = 1,summer = 2,autumn = 3, winter = 4
}
print(Season2.spring.rawValue,Season2.summer.rawValue,Season2.autumn.rawValue,Season2.winter.rawValue) //1 2 3 4

1.5 递归枚举

indirect enum ArithExpr1{
    case number(Int)
    case sum(ArithExpr1,ArithExpr1)
    case difference(ArithExpr1,ArithExpr1)
}
enum  ArithExpr2{
    case number(Int)
    indirect case sum(ArithExpr2,ArithExpr2)
    indirect case difference(ArithExpr2,ArithExpr2)
}
let five = ArithExpr1.number(5)
let four = ArithExpr1.number(4)
let two = ArithExpr1.number(2)
let sum = ArithExpr1.sum(five, four)
let difference = ArithExpr1.difference(sum, two)
func Calculate(_ expr:ArithExpr1) -> 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(difference)

1.6 MemoryLayout和内存布局

  • 可以使用MemoryLayout来获取数据类型占用的内存大小
enum Password2{
    case number(Int,Int,Int,Int)
    case other
}
MemoryLayout<Password2>.stride //40,分配占用空间的大小
MemoryLayout<Password2>.size //33,实际用到的空间大小
MemoryLayout<Password2>.alignment //8,内存对齐参数
var pwd2 = Password2.number(9, 8, 6, 4)
pwd2 = .other
MemoryLayout.stride(ofValue: pwd2) //40
MemoryLayout.size(ofValue: pwd2) // 33
MemoryLayout.alignment(ofValue: pwd2)// 8

2. 可选项

2.1 基本概念

  • 可选项,一般也叫可选类型,它允许将值设置为nil
  • 在类型后面加个问号?,来定义一个可选项
var name:String? = "jack"
name = nil

var age:Int? //默认就是nil
age = 10
age = nil
var array = [1,15,40,29]
func get(_ index:Int) -> Int?{
    if index < 0 || index >= array.count {
        return nil
    }
    return array[index]
}
print(get(1))  //Optional(15)
print(get(-1)) //nil
print(get(4))  //nil

2.2 强制解包

  • 可选项是对其他类型的一层包装,可以将它理解为是一个盒子
  • 如果为nil,那么它是一个空盒子;如果不为nil,那么盒子里装是:被包装类型的数据
var age2:Int? //默认就是nil
age2 = 10
age2 = nil
  • 如果要从可选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号!进行强制解包
var age3:Int? = 10
var ageInt:Int = age3!
ageInt += 10
  • 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误.Fatal error: Unexpectedly found nil while unwrapping an Optional value
var age4:Int?
age4!
  • 判断可选项是否包含值
let number = Int("123")
if number != nil{
    print("字符串转换整数成功:\(number!)")
}else{
    print("字符串转换整数失败")
}//字符串转换整数成功:123

2.3 可选项绑定

  • 可以使用可选项绑定来判断可选项是否包含值。如果包含就自动解包,把赋给一个临时的常量(let)或者变量(var),并返回true,否则返回false
if let number = Int("123"){
    print("字符串转换整数成功:\(number)")
    //number是强制解包之后的Int值,number的作用域仅限于这个大括号
}else{
    print("字符串转换整数失败")
}//字符串转换整数成功:123
enum Season:Int{
    case spring = 1, sumer,autumn,winter
}
if let season = Season(rawValue: 6){
    switch season {
    case .spring:
        print("the season is spring")
    default:
        print("the season is other")
    }
}else {
    print("no such season")
}// no such season
  • 等价写法
if let first = Int("4"){
    if let second = Int("42") {
        if first < second && second < 100 {
            print("\(first)<\(second)<100")
        }//4<42<100
    }
}
if let first = Int("4"),
    let second = Int("42"),
    first<second && second < 100
{
    print("\(first)<\(second)<100")
}//4<42<100

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

2.5 空合并运算符 ??

  • a ?? b 。 a是可选项;b是可选或者非可选项;b和a的存储类型必须相同。
  • 如果a不为nil,就返回a;如果a为nil,就返回b;如果b不是可选项,返回a时会自动解包
let a1:Int? = 1
let b1:Int? = 2
let c1 = a1 ?? b1 //c1是Int?,Optional(1)
let a2:Int? = nil
let b2:Int? = 2
let c2 = a2 ?? b2 //c2是Int?,Optional(2)
let a3:Int? = nil
let b3:Int? = nil
let c3 = a3 ?? b3 //c3是Int?,nil
let a4:Int? = 1
let b4:Int = 2
let c4 = a4 ?? b4 //c4是Int,1
let a5:Int? = nil
let b5:Int = 2
let c5 = a5 ?? b5 //c是Int,2
let a6:Int? = nil
let b6:Int = 2
//如果不使用??运算符
let c6:Int
if let tmp = a6 {
    c6 = tmp
}else{
    c6 = b6
}
  • 多个??一起使用
let a7:Int? = 1
let b7:Int? = 2
let c7 = a7 ?? b7 ?? 3 //c7是Int,1
let a8:Int? = nil
let b8:Int? = 2
let c8 = a8 ?? b8 ?? 3 //c8是Int,2

2.6 ??和if else配合使用

let a9:Int? = nil
let b9:Int? = 2
if let c9 = a9 ?? b9 {
    print(c9)
}//类似于 if a9 != nil || b9 != nil
if let c9 = a9,let d9 = b9 {
    print(c9)
    print(d9)
}//类似于 if a9 != nil && b9 != nil

2.7 if和guard语句实现登录

  • if语句实现登录
func login(_ info:[String:String]){
    let userName:String
    if let tmp = info["userNam"] {
        userName = tmp
    }else{
        print("请输入用户名")
        return
    }
    let password:String
    if let tmp = info["password"] {
        password = tmp
    }else{
        print("请输入密码")
        return
    }
    //if userName... ; if password ...
    print("userName:\(userName),password:\(password),登录中")
}
login(["userName":"jack"]) //请输入密码
  • guard实现登录
    当guard语句的条件为false时,就会执行大括号里面的代码;当guard语句的条件为true时,就会跳过guard语句;guard语句特别适合用来“提前退出”;当使用guard语句来进行可选项绑定时,绑定的常量(let),变量(var)也能在外层作用域中使用。
guard 条件 else {
     do something
    退出当前作用域
    return, break,continue,throw error
}
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:\(userName),password:\(password),登录中")
}

2.8 隐式解包

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

2.9 字符串插值

  • 可选项在字符串插值或者直接打印时,编译器会发出警告
var age4:Int? = 10
print("My age is \(age4)")
  • 至少有三种方法可以消除警告
print("My age is \(age4!)")
print("My age is \(String(describing: age4))")
print("My age is \(age4 ?? 0)")

2.10 多重可选项

var tmp1:Int? = 10
var tmp2:Int?? = tmp1
var tmp3:Int?? = 10
print(tmp2 == tmp3) //true
var temp1:Int? = nil
var temp2:Int?? = temp1
var temp3:Int?? = nil
print(temp2 == temp3) //false
(temp2 ?? 1) ?? 2 //2
(temp3 ?? 1) ?? 2 //1

可以使用LLDB指令 frame variable -R 或者 fr v -R 来查看区别